pax_global_header00006660000000000000000000000064152440221030014503gustar00rootroot0000000000000052 comment=0faff428c8adb909f77d7f5c862e8f4f66511843 SocNetV-3.7/000077500000000000000000000000001524402210300126755ustar00rootroot00000000000000SocNetV-3.7/.appveyor.yml000066400000000000000000000160731524402210300153520ustar00rootroot00000000000000# Build SocNetV for Windows x86_64 using Qt/MSVC2019 platform # https://socnetv.org # Last Update: Apr 2026 # Copyright (c) 2026 by Dimitris Kalamaras # # This script builds SocNetV Windows installers ONLY on tags i.e. 3.0 or 3.0-beta* # created by the developer, as well when the 'continuous' tag is recreated. # # NOTE: # To avoid possible cycles with creating new tags on regular commits or overwriting tags, we # specify tag: $(APPVEYOR_REPO_TAG_NAME) in deployment settings and use the on: key to # conditionally build and deploy only on tags we create manually. # See: https://www.appveyor.com/docs/deployment/github/#promoting-selected-tag-to-github-release version: 'continuous.{build}' #---------------------------------# # conditional building config # #---------------------------------# branches: only: - master - develop - /v\d+\.\d+/ - /v\d+\.\d+\.\d+/ - /v\d+\.\d+\-\w+/ only_commits: message: /\[appveyor\]/ author: dimitris.kalamaras@gmail.com #---------------------------------# # environment configuration # #---------------------------------# image: Visual Studio 2019 clone_folder: c:\projects\socnetv matrix: fast_finish: true install: - ps: Write-Host $env:APPVEYOR_PROJECT_NAME - ps: Write-Host $env:APPVEYOR_PROJECT_SLUG - ps: Write-Host $env:APPVEYOR_BUILD_WORKER_IMAGE - ps: Write-Host $env:APPVEYOR_REPO_BRANCH - ps: Write-Host $env:APPVEYOR_REPO_COMMIT - ps: Write-Host $env:APPVEYOR_REPO_TAG - ps: Write-Host $env:APPVEYOR_REPO_TAG_NAME - cmd: echo "Setting last commit short id" - cmd: git rev-parse --short HEAD > MYVER.txt - cmd: set /p LAST_COMMIT_SHORT= < MYVER.txt - cmd: echo %LAST_COMMIT_SHORT% - cmd: del MYVER.txt - cmd: echo %APPVEYOR_REPO_COMMIT:~0,7% - cmd: echo "Setting SocNetV current version..." # NOTE: This is changed by our update version bash script - cmd: set SOCNETV_VERSION=3.7 - cmd: echo %SOCNETV_VERSION% - cmd: echo "Setting build type" - cmd: set BUILD_TYPE=Release - cmd: echo %BUILD_TYPE% - cmd: echo "Checking if this is a prerelease deployment..." - cmd: set SOCNETV_PRERELEASE=false - cmd: if "%APPVEYOR_REPO_TAG%"=="true" if "%APPVEYOR_REPO_TAG_NAME%"=="continuous" set SOCNETV_PRERELEASE=true - ps: if ($env:APPVEYOR_REPO_COMMIT_MESSAGE.Contains("[appveyor]")) {$env:SOCNETV_PRERELEASE = "true"} - ps: if ($env:APPVEYOR_REPO_COMMIT_MESSAGE.Contains("[travis]")) {$env:SOCNETV_PRERELEASE = "true"} - ps: if ($env:APPVEYOR_REPO_COMMIT_MESSAGE.Contains("[ci]")) {$env:SOCNETV_PRERELEASE = "true"} - ps: if ( $env:APPVEYOR_REPO_TAG_NAME -ne $null ) { if ($env:APPVEYOR_REPO_TAG_NAME.Contains("-beta")) { $env:SOCNETV_PRERELEASE = "true" } } - ps: if ( $env:APPVEYOR_REPO_TAG_NAME -ne $null ) { if ($env:APPVEYOR_REPO_TAG_NAME.Contains("-rc")) { $env:SOCNETV_PRERELEASE = "true" } } - ps: Write-Host $env:SOCNETV_PRERELEASE - cmd: if "%SOCNETV_PRERELEASE%"=="true" echo "This is a prerelease" - cmd: echo "Setting BUILDNAME" - set BUILDNAME=%SOCNETV_VERSION%-%LAST_COMMIT_SHORT% - cmd: echo %BUILDNAME% - cmd: echo "Setting Qt + MSVC environment..." - cmd: echo "Check Qt folders (debug)..." - cmd: dir C:\Qt # Qt 6.8.0 with MSVC2022 64-bit (preinstalled on Appveyor VS2019 image via aqtinstall or preinstalled) # NOTE: Appveyor's VS2019 image ships Qt 6.x via aqtinstall. # Check available versions at: https://www.appveyor.com/docs/windows-images-software/#qt # Adjust path below if Appveyor's preinstalled Qt version differs. - set QTDIR=C:\Qt\6.8.0\msvc2022_64 - cmd: echo "Check project folder..." - cmd: dir C:\projects\socnetv - cmd: echo "Installing Innosetup..." - choco install -y InnoSetup - cmd: echo "Setting paths..." - set PATH=%QTDIR%\bin;%PATH%;"C:\Program Files (x86)\Inno Setup 6" - cmd: echo "Verify qmake and cmake..." - cmd: where qmake - cmd: where cmake - cmd: qmake -v - cmd: cmake --version #---------------------------------# # app building starts here # #---------------------------------# build_script: - cmd: echo "Configuring project with cmake..." - cmd: cmake -S . -B build -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DBUILD_CLI=OFF - cmd: echo "Building %BUILD_TYPE% with cmake..." - cmd: cmake --build build -j4 --config %BUILD_TYPE% -v after_build: - cmd: echo "Check build folder contents" - cmd: dir C:\projects\socnetv\build - cmd: dir C:\projects\socnetv\build\%BUILD_TYPE% - cmd: echo "Executing windeployqt..." - cmd: windeployqt build\%BUILD_TYPE%\socnetv.exe --release --compiler-runtime - cmd: echo "Copying MSVC runtime DLLs..." - cmd: copy "C:\Windows\System32\MSVCP140.dll" build\%BUILD_TYPE%\ - cmd: copy "C:\Windows\System32\MSVCP140_1.dll" build\%BUILD_TYPE%\ - cmd: copy "C:\Windows\System32\MSVCP140_2.dll" build\%BUILD_TYPE%\ - cmd: copy "C:\Windows\System32\VCRUNTIME140.dll" build\%BUILD_TYPE%\ - cmd: copy "C:\Windows\System32\VCRUNTIME140_1.dll" build\%BUILD_TYPE%\ - cmd: copy "C:\Windows\System32\CONCRT140.dll" build\%BUILD_TYPE%\ - cmd: echo "check build folder contents again..." - cmd: dir C:\projects\socnetv\build\%BUILD_TYPE% - cmd: echo "copy COPYING to LICENSE.txt..." - cmd: copy COPYING build\%BUILD_TYPE%\LICENSE.txt - cmd: echo "Running Innosetup..." - cmd: copy C:\projects\socnetv\scripts\innosetup.iss build\innosetup.iss - cmd: echo "Updating RELEASEFOLDER in innosetup.iss..." - ps: | $content = Get-Content "build\innosetup.iss" $content = $content -replace '#define RELEASEFOLDER "release\\"', "#define RELEASEFOLDER `"$env:BUILD_TYPE\\`"" Set-Content "build\innosetup.iss" $content - cmd: echo "Available Innosetup languages:" - cmd: dir "C:\Program Files (x86)\Inno Setup 6\Languages" - cmd: cd build && iscc innosetup.iss - cmd: type Setup-Manifest.txt - cmd: dir SocNetV*.exe - cmd: echo "Renaming installer..." - ps: Get-ChildItem -Path "SocNetV-*installer.exe" | Move-Item -Destination "SocNetV-$env:BUILDNAME-windows-installer.exe" - cmd: dir SocNetV*.exe artifacts: - path: SocNetV-*-installer.exe #---------------------------------# # Deploy to GitHub Releases # #---------------------------------# deploy: - provider: GitHub tag: continuous description: 'SocNetV continuous integration prereleases from commit ${APPVEYOR_REPO_COMMIT}' artifact: /SocNetV.*installer\.exe/ auth_token: secure: rWpgp9XYDS/Tqr6dsQ2CK1x/vgE4nMyQ3W7oyQlG2L3oF4tecU8CEDW1EY5Us0mZ draft: false prerelease: true force_update: true on: branch: $(APPVEYOR_REPO_BRANCH) SOCNETV_PRERELEASE: true - provider: GitHub tag: $(APPVEYOR_REPO_TAG_NAME) description: 'SocNetV release for $(APPVEYOR_REPO_TAG_NAME)' artifact: /SocNetV.*installer\.exe/ auth_token: secure: rWpgp9XYDS/Tqr6dsQ2CK1x/vgE4nMyQ3W7oyQlG2L3oF4tecU8CEDW1EY5Us0mZ draft: false prerelease: false force_update: true on: branch: $(APPVEYOR_REPO_BRANCH) APPVEYOR_REPO_TAG: true SOCNETV_PRERELEASE: false # Notify developer on any change notifications: - provider: Email to: - dimitris.kalamaras@gmail.com subject: '[socnetv/app] build {{status}}' on_build_status_changed: trueSocNetV-3.7/.github/000077500000000000000000000000001524402210300142355ustar00rootroot00000000000000SocNetV-3.7/.github/workflows/000077500000000000000000000000001524402210300162725ustar00rootroot00000000000000SocNetV-3.7/.github/workflows/build-ci.yml000066400000000000000000001044731524402210300205160ustar00rootroot00000000000000# GitHub Action to CI build SocNetV for all 3 major OSes # Triggered only when the commit message contains [gha] or [ci] name: Build SocNetV (CI) πŸš€ on: push: branches: - develop env: APP_NAME: "SocNetV" UNIXNAME: "socnetv" SOCNETV_VERSION: "3.7" # TODO - READ FROM FILE VERSION: "3.7" # Will be overwritten by workflow with the dynamically determined version QMAKE_PROJECT: "socnetv.pro" PUBLISHER: "Dimitris Kalamaras" QT_MODULES: "qtimageformats qt5compat qtcharts qtwebview" QMAKE_CONFIG: release ## debug # Never use debug. Windows builds will break. CMAKE_CONFIG: Release ## Debug CORES: 16 MAC_ARTIFACT: "" LINUX_ARTIFACT: "" APPDIR_PREFIX: "/usr" WINDOWS_ARTIFACT: "" UPLOAD_URL: '' BUILD_CLI: "OFF" # Set to ON to also build the headless regression CLI (socnetv-cli) jobs: cleanup: runs-on: ubuntu-latest permissions: contents: write if: contains(github.event.head_commit.message, '[ci]') || contains(github.event.head_commit.message, '[gha]') steps: - uses: actions/checkout@v7 # Checkout the repository to access the code and use gh CLI - name: πŸ—‘οΈ Delete old continuous assets run: | ASSETS=$(gh release view continuous --json assets -q '.assets[].name') if [[ -z "$ASSETS" ]]; then echo "βœ… No previous assets found." else for ASSET in $ASSETS; do echo "❌ Removing $ASSET..." gh release delete-asset continuous "$ASSET" --yes done echo "βœ… Old assets deleted." fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ci_build: needs: cleanup permissions: contents: write # Required to upload release assets strategy: fail-fast: false matrix: os: [ubuntu-22.04, ubuntu-latest, macos-latest, windows-2022] # NOTE: # - We will CI build only for Qt6 LTS releases, see: https://doc.qt.io/qt-6/qt-releases.html # - For the Qt Versions supported by aqtinstall, see: https://ddalcino.github.io/aqt-list-server/ # - Single version tested here (was ['6.5.3', '6.8.0']): macOS builds against both broke in # Jul 2026 when the macos-latest runner image moved to an Xcode/SDK that no longer ships # AGL.framework, which Qt's macOS OpenGL module links against unconditionally before the # open-source fix in 6.9.2. 6.8.4 also has the fix but is commercial-only right now. # See docs/README_DEVELOPER_NOTES.md for the full writeup. qt-version: ['6.9.3'] # exclude: # - os: ubuntu-latest # qt-version: '6.6.3' # - os: windows-2022 # qt-version: '6.6.3' # - os: macos-latest # qt-version: '6.6.3' # include: # - os: macos-latest # qt-version: '6.6.3' # # Snapcraft # - os: ubuntu-22.04 # qt-version: '6.5.3' runs-on: ${{ matrix.os }} if: contains(github.event.head_commit.message, '[ci]') || contains(github.event.head_commit.message, '[gha]') steps: - name: πŸ€– Job information, on branch ${{ github.ref }} run: | echo "πŸŽ‰ The job was automatically triggered by a ${{ github.event_name }} event, by actor ${{ github.actor }}." echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" echo "πŸ”Ž The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - name: πŸ“‚ Check out repository ${{ github.repository }} uses: actions/checkout@v7 - name: πŸ’‘ List files cloned from the ${{ github.repository }} run: | ls ${{ github.workspace }} echo "πŸ’‘ The ${{ github.repository }} repository has been cloned to the runner." echo "πŸ–₯️ The workflow is now ready to test your code on the runner." - name: πŸ’‘ Set version dynamically shell: bash id: set_version run: | # Check if this is a tagged commit if [ -n "${GITHUB_REF}" ] && [[ "${GITHUB_REF}" == refs/tags/* ]]; then VERSION=${GITHUB_REF#refs/tags/} # Extract tag name else LAST_COMMIT_SHORT=$(git rev-parse --short HEAD) VERSION="${SOCNETV_VERSION}-${LAST_COMMIT_SHORT}" # Use custom versioning for non-tagged commits fi # Export VERSION as an environment variable for subsequent steps echo "VERSION=${VERSION}" >> $GITHUB_ENV - name: Determined build version ${{ env.VERSION }} run: echo "VERSION is set to ${{ env.VERSION }}" - name: Check Rate Limit uses: actions/github-script@v9 id: get_ratelimit with: script: | const { data: rateLimit } = await github.rest.rateLimit.get(); console.log(rateLimit); core.setOutput("rate_limit", rateLimit); github-token: ${{ secrets.GITHUB_TOKEN }} - name: Use script to get continuous release Upload URL id: get_release uses: actions/github-script@v9 with: script: | const { data: releases } = await github.rest.repos.listReleases({ owner: context.repo.owner, repo: context.repo.repo, }); const release = releases.find(r => r.tag_name === "continuous"); if (!release) { console.log("Continuous release not found. Exiting gracefully."); core.setOutput("upload_url", ""); } else { core.setOutput("upload_url", release.upload_url); } result-encoding: string github-token: ${{ secrets.GITHUB_TOKEN }} - name: Fetch 'continuous' release upload URL shell: bash id: fetch_release run: | echo "steps.get_ratelimit.outputs.rate_limit = ${{ steps.get_ratelimit.outputs.rate_limit }}" echo "steps.get_release.outputs.upload_url = ${{ steps.get_release.outputs.upload_url }}" if [ -z "${{ steps.get_release.outputs.upload_url }}" ]; then echo "No continuous release found in get_release step." fi RELEASE_INFO=$(gh api -H "Accept: application/vnd.github+json" https://api.github.com/repos/${{ github.repository }}/releases/tags/continuous) UPLOAD_URL=$(echo "$RELEASE_INFO" | jq -r '.upload_url' | sed 's/{?name,label}//') echo "UPLOAD_URL=${UPLOAD_URL}" >> $GITHUB_ENV echo "UPLOAD_URL=${UPLOAD_URL}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # # Install dependencies (build tools, cmake, etc) # - if: contains( matrix.os, 'windows') name: πŸͺŸ Prepare for buiilding on ${{matrix.os}} run: | echo 'βš™οΈ Install dependencies for building....' # DONT NEED IT. FOR DEBUG ONLY # pip install aqtinstall # aqt list-qt windows desktop # aqt list-qt windows desktop --arch ${{ matrix.qt-version }} # aqt list-qt windows desktop --modules ${{ matrix.qt-version }} win64_mingw - if: contains( matrix.os, 'ubuntu') name: 🐧 Prepare for building on ${{matrix.os}} run: | echo 'βš™οΈ Install dependencies for building....' sudo apt-get update sudo apt-get install -y --fix-missing build-essential libssl-dev cmake ninja-build \ libxkbcommon-x11-dev libxcb-cursor-dev zlib1g-dev libcups2-dev libvulkan-dev \ desktop-file-utils patchelf libglu1-mesa-dev libfontconfig1 libfreetype6 libx11-dev libxext-dev \ libxrandr-dev libxrender-dev libxcb1-dev libx11-xcb-dev libxcb-glx0-dev # libfuse2 was renamed to libfuse2t64 on Ubuntu 24.04 (noble) sudo apt-get install -y libfuse2t64 2>/dev/null || sudo apt install -y libfuse2 2>/dev/null || true # DONT NEED IT. FOR DEBUG ONLY # pip install aqtinstall # aqt list-qt linux desktop --long-modules ${{ matrix.qt-version }} win64_mingw - if: contains( matrix.os, 'macos') name: 🍎 Prepare for building on ${{matrix.os}} run: | echo 'βš™οΈ Install dependencies for building....' ls # DONT NEED IT. FOR DEBUG ONLY # pip install aqtinstall # aqt list-qt mac desktop --modules ${{ matrix.qt-version }} # # Install Qt (using https://github.com/jurplel/install-qt-action) # - if: contains( matrix.os, 'windows') name: Make sure MSVC is found uses: ilammy/msvc-dev-cmd@v1 - if: contains( matrix.os, 'windows') && startsWith( matrix.qt-version, '6.' ) name: Install Qt ${{ matrix.qt-version }} on ${{ matrix.os }} uses: jurplel/install-qt-action@v4 with: aqtversion: '==3.1.*' # Use the default aqtinstall version version: ${{ matrix.qt-version }} # Qt version to install # arch: win64_mingw # NOTE: We build with default arch: # win64_msvc2019_64 if Qt < 6.8 # win64_msvc2022_64 if Qt >= 6.8 # see https://github.com/jurplel/install-qt-action modules: ${{env.QT_MODULES}} cache: true - if: contains( matrix.os, 'ubuntu') && startsWith( matrix.qt-version, '6.' ) name: Install Qt 6 on ${{ matrix.os }} uses: jurplel/install-qt-action@v4 with: aqtversion: '==3.1.*' # Use the default aqtinstall version version: ${{ matrix.qt-version }} # Qt version to install modules: ${{env.QT_MODULES}} cache: true - if: contains( matrix.os, 'macos') && startsWith( matrix.qt-version, '6.' ) name: Install Qt 6 on macOS uses: jurplel/install-qt-action@v4 with: aqtversion: '==3.1.*' # Use the default aqtinstall version version: ${{ matrix.qt-version }} modules: ${{env.QT_MODULES}} cache: true # # BUILD FOR LINUX # # Test building with qmake (only tested here - see the macOS/Windows build sections for why) - if: matrix.os == 'ubuntu-22.04' name: Build ${{ env.VERSION }} for ${{matrix.os}} with Qt${{matrix.qt-version}} using qmake run: | echo "πŸ”Ž Check openssl version:" echo `openssl version` echo "πŸ”Ž Check output of 'which qmake6':" which qmake6 echo "πŸ”Ž Check qmake6 version:" qmake6 -v echo "πŸ”§ Running qmake on ubuntu 22.04 with ${{env.QMAKE_CONFIG}}..." qmake6 CONFIG+=${{env.QMAKE_CONFIG}} echo "🚧 πŸ› οΈ Compiling for linux with make -j${{env.CORES}}. Please wait..." make -j${{env.CORES}} echo "πŸ‘‰ Building finished. Listing current directory with find for verification:" find . # Test building with cmake - if: matrix.os == 'ubuntu-22.04' name: Build ${{ env.VERSION }} for ${{matrix.os}} with Qt${{matrix.qt-version}} using cmake run: | echo "πŸ”Ž Check openssl version:" echo `openssl version` echo "πŸ’‘ Checking current directory..." pwd echo "πŸ’‘ List current directory..." ls echo "πŸ’‘ Deleting old build subdirectory..." rm -rf build echo "πŸ”§ Configuring project using 'cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.CMAKE_CONFIG}} -DCMAKE_INSTALL_PREFIX=${{env.APPDIR_PREFIX}} -DBUILD_CLI=${{env.BUILD_CLI}}' ..." cmake -S . -B build \ -DCMAKE_BUILD_TYPE=${{env.CMAKE_CONFIG}} \ -DCMAKE_INSTALL_PREFIX=${{env.APPDIR_PREFIX}} \ -DBUILD_CLI=${{env.BUILD_CLI}} echo "πŸ”Ž Verifying ./build directory (before compiling)..." if [[ -d "./build" ]]; then echo "πŸŽ‰ ./build directory created! Contents:" ls -lh ./build/ else echo "❌ Error! ./build directory was not created!" exit 1 fi echo "🚧 Compiling the project..." cmake --build build -j$(nproc) echo "πŸ”Ž Entering build directory..." cd build ls -lh . echo "πŸ”Ž Search for built executable ./${{env.UNIXNAME}}*..." if [[ -f "./${{env.UNIXNAME}}" ]]; then echo "πŸŽ‰ executable found!" ls -ls "${{env.UNIXNAME}}" else echo "❌ Error! Executable was not created!" exit 1 fi # Build AppImage on ubuntu-22.04 # Uses linuxdeploy + linuxdeploy-plugin-qt (supports Ubuntu 22.04+) - if: matrix.os == 'ubuntu-22.04' && matrix.qt-version == '6.9.3' name: Create AppImage ${{ env.VERSION }} for ${{matrix.os}} with Qt${{matrix.qt-version}} run: | echo "πŸ’‘ Checking current directory..." pwd cd ./build/ echo "πŸ’‘ List current directory (./build)..." ls -lh . echo "πŸ”§ Installing the application into AppDir..." cmake --install . --prefix AppDir/usr if [[ ! -f "./AppDir/usr/bin/${{env.UNIXNAME}}" ]]; then echo "❌ Error: Installation failed, binary not found in AppDir!" exit 1 fi echo "πŸŽ‰ SocNetV installed into AppDir successfully!" echo "πŸ”§ Downloading linuxdeploy and Qt plugin..." wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage chmod a+x linuxdeploy-x86_64.AppImage linuxdeploy-plugin-qt-x86_64.AppImage echo "πŸš€ Creating AppImage using linuxdeploy..." QMAKE=$(which qmake) \ LINUXDEPLOY_OUTPUT_VERSION="tmp-out" \ ./linuxdeploy-x86_64.AppImage \ --appdir AppDir \ --plugin qt \ --output appimage ARTIFACT_FN="${{env.APP_NAME}}-${{env.VERSION}}-x86_64.AppImage" # linuxdeploy names the output with the app name from the .desktop file CREATED_APPIMAGE=$(ls ./*.AppImage 2>/dev/null | grep -v linuxdeploy | head -1) if [[ -z "$CREATED_APPIMAGE" ]]; then echo "❌ AppImage creation failed! No AppImage found." exit 1 fi mv "$CREATED_APPIMAGE" "$ARTIFACT_FN" echo "πŸŽ‰ AppImage created: $ARTIFACT_FN" ls -lh ./*.AppImage echo "LINUX_ARTIFACT=${ARTIFACT_FN}" >> $GITHUB_ENV # Upload the AppImage artifact for Linux if it was created successfully - if: matrix.os == 'ubuntu-22.04' && matrix.qt-version == '6.9.3' && env.LINUX_ARTIFACT != '' name: πŸ“€ Upload ${{matrix.os}} build artifacts of ${{env.APP_NAME}} ${{ env.VERSION }} to GitHub ${{ env.UPLOAD_URL }} run: | echo "πŸ” Verifying artifact: ${{ env.LINUX_ARTIFACT }}" pwd ls build if [ ! -f "./build/${{ env.LINUX_ARTIFACT }}" ]; then echo "❌ Error: Artifact not found!" exit 1 fi echo "πŸ“€ Uploading artifact to GitHub release..." gh release upload continuous "./build/${{ env.LINUX_ARTIFACT }}" --repo "${{ github.repository }}" \ --clobber env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # In latest ubuntu, we are test building with cmake. We do not upload the artifact. - if: matrix.os == 'ubuntu-latest' name: Build ${{ env.VERSION }} for ${{matrix.os}} with Qt ${{matrix.qt-version}} using cmake run: | echo "πŸ”Ž Check openssl version:" echo `openssl version` echo "πŸ’‘ Checking current directory..." pwd echo "πŸ’‘ List current directory..." ls echo "πŸ’‘ Deleting old build subdirectory..." rm -rf build echo "πŸ”§ Configuring project using 'cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.CMAKE_CONFIG}} -DCMAKE_INSTALL_PREFIX=${{env.APPDIR_PREFIX}} -DBUILD_CLI=${{env.BUILD_CLI}}' ..." cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.CMAKE_CONFIG}} -DCMAKE_INSTALL_PREFIX=${{env.APPDIR_PREFIX}} -DBUILD_CLI=${{env.BUILD_CLI}} echo "πŸ”Ž Verifying ./build directory (before compiling)..." if [[ -d "./build" ]]; then echo "πŸŽ‰ ./build directory created! Contents:" ls -lh ./build/ else echo "❌ Error! ./build directory was not created!" exit 1 fi echo "🚧 Compiling the project..." cmake --build build -j$(nproc) echo "πŸ”Ž Entering build directory..." cd build ls -lh . echo "πŸ”Ž Search for built executable ./${{env.UNIXNAME}}*..." if [[ -f "./${{env.UNIXNAME}}" ]]; then echo "πŸŽ‰ executable found!" ls -ls "${{env.UNIXNAME}}" else echo "❌ Error! Executable not found in build directory!" exit 1 fi # # BUILD FOR MACOS # # qmake is legacy-supported and only compile-tested on ubuntu-22.04 (see below) - not # re-tested per-platform, since the failure modes we've actually hit (missing source files # in socnetv.pro) are platform-agnostic. We build with cmake and upload the DMG artifact if # it was created successfully. - if: contains( matrix.os, 'macos') name: Build ${{env.APP_NAME}} ${{ env.VERSION }} for ${{matrix.os}} with Qt ${{matrix.qt-version}} using cmake timeout-minutes: 360 # 6 hours maximum run: | echo "🍎 Preparing macOS build..." echo "πŸ”Ž Check openssl version:" echo `openssl version` echo "πŸ’‘ Checking current directory..." pwd echo "πŸ’‘ List current directory..." ls echo "πŸ’‘ Deleting old build subdirectory..." rm -rf build echo "πŸ”§ Configuring project using 'cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.CMAKE_CONFIG}} -DCMAKE_INSTALL_PREFIX=${{env.APPDIR_PREFIX}} -DBUILD_CLI=${{env.BUILD_CLI}}' ..." cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.CMAKE_CONFIG}} -DCMAKE_INSTALL_PREFIX=${{env.APPDIR_PREFIX}} -DBUILD_CLI=${{env.BUILD_CLI}} echo "πŸ”Ž Verifying ./build directory (before compiling)..." if [[ -d "./build" ]]; then echo "πŸŽ‰ ./build directory created! Contents:" ls -lh ./build/ else echo "❌ Error! ./build directory was not created!" exit 1 fi echo "🚧 Compiling the project..." cmake --build build -j$(sysctl -n hw.ncpu) echo "πŸ”Ž Entering build directory..." cd build ls -lh . echo "πŸ”Ž Search for built ${{matrix.os}} bundle (DIRECTORY!)./${{env.APP_NAME}}.app ..." if [[ -d "./${{env.APP_NAME}}.app" ]]; then echo "πŸŽ‰ ${{matrix.os}} bundle created! Bundle contents:" find "${{env.APP_NAME}}.app" else echo "❌ Error! ${{matrix.os}} bundle not found !" exit 1 fi # Verify the binary with lipo echo "πŸ”Ž Verifying architectures in the ${{matrix.os}} bundle..." if [[ -f "${{env.APP_NAME}}.app/Contents/MacOS/${{env.APP_NAME}}" ]]; then lipo -info "${{env.APP_NAME}}.app/Contents/MacOS/${{env.APP_NAME}}" else echo "❌ Error: ${{env.APP_NAME}}.app/Contents/MacOS/${{env.APP_NAME}} file not found!" exit 1 fi echo "πŸ”‘ Import macOS Developer Certificate" echo "${{ secrets.MACOS_CERTIFICATE }}" | base64 --decode > certificate.p12 security create-keychain -p "" build.keychain security default-keychain -s build.keychain security unlock-keychain -p "" build.keychain security import certificate.p12 -k build.keychain -P "${{ secrets.MACOS_CERTIFICATE_PASSWORD }}" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple: -k "" build.keychain rm -f certificate.p12 echo "πŸ› οΈ Store Apple ID Credentials for Notarization" xcrun notarytool store-credentials "AC_PASSWORD" \ --apple-id "${{ secrets.AC_APPLE_ID }}" \ --team-id "${{ secrets.AC_TEAM_ID }}" \ --password "${{ secrets.AC_PASSWORD }}" echo "πŸ”§ Checking bundle structure before packaging..." # Remove any PkgInfo file in the root of the app bundle if [[ -f "${{env.APP_NAME}}.app/PkgInfo" ]]; then echo "⚠️ Found PkgInfo in root directory - removing it" rm "${{env.APP_NAME}}.app/PkgInfo" fi # Ensure PkgInfo exists in the Contents directory if [[ ! -f "${{env.APP_NAME}}.app/Contents/PkgInfo" ]]; then echo "Creating PkgInfo in Contents directory" echo "APPL????" > "${{env.APP_NAME}}.app/Contents/PkgInfo" fi # Clear extended attributes that could interfere with signing find "${{env.APP_NAME}}.app" -type f -exec xattr -c {} \; # First run macdeployqt WITHOUT creating DMG yet echo "πŸš€ Running macdeployqt to bundle required libraries..." macdeployqt "${{env.APP_NAME}}.app" -verbose=3 || { echo "Error: macdeployqt failed." exit 1 } # Now sign the .app bundle AFTER macdeployqt has added all dependencies echo "πŸ” Sign the application bundle" codesign --deep --force --verbose \ --options runtime \ --entitlements ../scripts/entitlements.plist \ --sign "Developer ID Application: Dimitris Kalamaras (${{ secrets.AC_TEAM_ID }})" \ "${{env.APP_NAME}}.app" # Verify the signature echo "πŸ” Verifying signature..." codesign --verify --verbose "${{env.APP_NAME}}.app" # Create the DMG from the signed app echo "πŸ“¦ Creating DMG from signed application..." hdiutil create -volname "${{env.APP_NAME}}" -srcfolder "${{env.APP_NAME}}.app" -ov -format UDZO "${{env.APP_NAME}}.dmg" # Sign the DMG echo "πŸ” Signing the DMG..." codesign --force --sign "Developer ID Application: Dimitris Kalamaras (${{ secrets.AC_TEAM_ID }})" "${{env.APP_NAME}}.dmg" # Before starting notarization, set keychain to not timeout echo "πŸ” Setting keychain to not timeout..." security set-keychain-settings -t 72000 -l build.keychain # Notarize the signed DMG echo "πŸ“œ Notarize the DMG" SUBMIT_OUTPUT=$(xcrun notarytool submit "${{env.APP_NAME}}.dmg" --keychain-profile "AC_PASSWORD") NOTARY_SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep "id:" | head -1 | awk '{print $2}') echo "Notarization submission ID: $NOTARY_SUBMISSION_ID" if [[ -z "$NOTARY_SUBMISSION_ID" ]]; then echo "❌ Error: Notarization submission ID is empty!" exit 1 fi # Store the submission id echo "NOTARY_SUBMISSION_ID=$NOTARY_SUBMISSION_ID" >> $GITHUB_ENV echo "πŸ•’ Waiting for notarization to complete (with timeout)..." START_TIME=$(date +%s) TIMEOUT_SECONDS=1800 # 30 minutes timeout while true; do # Check if we've exceeded the timeout CURRENT_TIME=$(date +%s) ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) if [[ $ELAPSED_TIME -gt $TIMEOUT_SECONDS ]]; then echo "⚠️ Notarization timeout after $(($TIMEOUT_SECONDS / 60)) minutes" break fi # Explicitly unlock the keychain again before checking status echo "πŸ”“ Unlocking keychain before checking notarization status..." security unlock-keychain -p "" build.keychain # Check notarization status echo "πŸ” Checking notarization status..." NOTARY_INFO=$(xcrun notarytool info --keychain-profile "AC_PASSWORD" "$NOTARY_SUBMISSION_ID" 2>&1) echo "$NOTARY_INFO" # Extract status NOTARY_STATUS=$(echo "$NOTARY_INFO" | grep -i "status:" | awk '{print $2}' | tr -d '[:space:]') if [[ "$NOTARY_STATUS" == "Accepted" ]]; then echo "βœ… Notarization successful!" # Staple the notarization ticket to the DMG echo "πŸ“Œ Stapling notarization ticket to DMG..." xcrun stapler staple "${{env.APP_NAME}}.dmg" # Rename DMG file after successful notarization and stapling DMG_NAME="${{env.APP_NAME}}-${{env.VERSION}}.dmg" mv "${{env.APP_NAME}}.dmg" "${DMG_NAME}" echo "πŸŽ‰ Build, signing, notarization and stapling complete. Final DMG: ${DMG_NAME}" ls -lh *.dmg echo "MAC_ARTIFACT=${DMG_NAME}" >> $GITHUB_ENV break elif [[ "$NOTARY_STATUS" == "Invalid" || "$NOTARY_STATUS" == "Rejected" ]]; then echo "❌ Notarization failed. Details:" echo "$NOTARY_INFO" # Get logs for failure details xcrun notarytool log --keychain-profile "AC_PASSWORD" "$NOTARY_SUBMISSION_ID" # Still create the artifact, but with a different name to indicate it's not notarized DMG_NAME="${{env.APP_NAME}}-${{env.VERSION}}-unsigned.dmg" mv "${{env.APP_NAME}}.dmg" "${DMG_NAME}" echo "⚠️ Created unsigned DMG: ${DMG_NAME}" echo "MAC_ARTIFACT=${DMG_NAME}" >> $GITHUB_ENV break elif [[ "$NOTARY_STATUS" == "In Progress" ]]; then echo "⏳ Notarization still in progress, waiting..." elif [[ "$NOTARY_STATUS" == "InProgress" ]]; then echo "⏳ Notarization still in progress, waiting..." else echo "⚠️ Unexpected status: $NOTARY_STATUS" echo "$NOTARY_INFO" fi echo "⏳ Still waiting for notarization... ($(($ELAPSED_TIME / 60)) minutes elapsed)" sleep 60 # Check every minute done # Upload artifact for macOS if it was created - if: contains( matrix.os, 'macos') && matrix.qt-version == '6.9.3' && env.MAC_ARTIFACT != '' name: πŸ“€ Upload ${{matrix.os}} build artifacts of ${{env.APP_NAME}} ${{ env.VERSION }} to GitHub ${{ env.UPLOAD_URL }} run: | echo "πŸ” Verifying artifact: ${{ env.MAC_ARTIFACT }}" pwd ls build if [ ! -f "./build/${{ env.MAC_ARTIFACT }}" ]; then echo "❌ Error: Artifact not found!" exit 1 fi echo "πŸ“€ Uploading artifact to GitHub release..." gh release upload continuous "./build/${{ env.MAC_ARTIFACT }}" --repo "${{ github.repository }}" \ --clobber env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # # BUILD FOR WINDOWS # # qmake is legacy-supported and only compile-tested on ubuntu-22.04 (see above) - not # re-tested per-platform, since the failure modes we've actually hit (missing source files # in socnetv.pro) are platform-agnostic. Build with cmake, then create installer with Inno # Setup. # - if: contains( matrix.os, 'windows') name: Build ${{env.APP_NAME}} ${{ env.VERSION }} for ${{matrix.os}} with Qt ${{matrix.qt-version}} using cmake. run: | echo "πŸ”Ž Listing some directories" dir D:\a\app\Qt\ echo "πŸ”Ž Verifying Qt installation path..." where qmake where windeployqt echo "πŸ’‘ Creating build dir" mkdir build echo "πŸ”§ Running 'cmake -S . -B build' to configure the project..." cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.CMAKE_CONFIG}} -DBUILD_CLI=${{env.BUILD_CLI}} echo "🚧 πŸ› οΈ Compiling into build/ with 'cmake --build build -j${{env.CORES}} --config ${{env.CMAKE_CONFIG}}'. Please wait..." cmake --build build -j${{env.CORES}} --config ${{env.CMAKE_CONFIG}} -v echo "πŸ‘‰ Building finished. Entering build/ and listing it for verification: " cd build dir echo "πŸ”§ Running windeployqt on built executable (and ensuring required .dlls are copied into the folder)..." windeployqt ${{env.CMAKE_CONFIG}}\socnetv.exe --release --compiler-runtime echo "πŸ”§ Manually copying MSVC runtime DLLs..." if (!(Test-Path "C:\Windows\System32\MSVCP140_1.dll")) { echo "❌ MSVCP140_1.dll NOT FOUND in System32! This may cause runtime errors." exit 1 # Fail the build when the file is missing } if (!(Test-Path "C:\Windows\System32\MSVCP140_2.dll")) { echo "❌ MSVCP140_2.dll NOT FOUND in System32! This may cause runtime errors." exit 1 # Fail the build when the file is missing } copy "C:\Windows\System32\MSVCP140.dll" ${{env.CMAKE_CONFIG}}\ copy "C:\Windows\System32\MSVCP140_1.dll" ${{env.CMAKE_CONFIG}}\ copy "C:\Windows\System32\MSVCP140_2.dll" ${{env.CMAKE_CONFIG}}\ copy "C:\Windows\System32\VCRUNTIME140.dll" ${{env.CMAKE_CONFIG}}\ copy "C:\Windows\System32\VCRUNTIME140_1.dll" ${{env.CMAKE_CONFIG}}\ copy "C:\Windows\System32\CONCRT140.dll" ${{env.CMAKE_CONFIG}}\ echo "πŸ”§ Copying license file to build directory..." copy ..\COPYING ${{env.CMAKE_CONFIG}}\LICENSE.txt echo "πŸ‘‰ Checking deployed DLLs..." dir ${{env.CMAKE_CONFIG}} if (!(Get-Command "iscc.exe" -ErrorAction SilentlyContinue)) { echo "πŸ”§ Installing Inno Setup..." choco install -y InnoSetup } else { echo "Inno Setup is already installed. Skipping installation." } echo "πŸ”§ Adding Inno Setup to PATH..." $env:PATH += ";C:\Program Files (x86)\Inno Setup 6\" echo "πŸ”§ Verifying Inno Setup Installation..." iscc.exe /? echo "πŸ”§ Copying InnoSetup script..." copy ..\scripts\innosetup.iss innosetup.iss echo "Updating RELEASEFOLDER in innosetup.iss with ${{env.CMAKE_CONFIG}}..." $config = "${{env.CMAKE_CONFIG}}" $replacement = "#define RELEASEFOLDER `"$config\\`"" (Get-Content innosetup.iss) -replace '#define RELEASEFOLDER "release\\"', $replacement | Set-Content innosetup.iss echo "Updated innosetup.iss:" type innosetup.iss echo "πŸ”§ Running Inno Setup to create installer..." & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" "innosetup.iss" echo "πŸ‘‰ Checking if installer exists in current directory ..." dir $originalInstaller = Get-ChildItem -Path "SocNetV-*installer.exe" -ErrorAction SilentlyContinue if (-not $originalInstaller) { echo "❌ Error: Installer file not found!" exit 1 } echo "πŸ”§ Renaming installer..." $installerName = "SocNetV-${{env.VERSION}}-windows-installer.exe" Get-ChildItem -Path "SocNetV-*installer.exe" | Move-Item -Destination $installerName dir echo "πŸŽ‰ Exporting installer name to environment variable..." echo "WINDOWS_ARTIFACT=$installerName" >> $env:GITHUB_ENV echo "πŸŽ‰ Windows build complete. Artifacts are ready!" # # Upload artifacts from Windows build to 'continuous' pre-release tag. # NOTE: this qt-version check is a no-op with a single matrix entry, kept so # it fails loudly (no upload) instead of silently double-uploading if a # second qt-version is ever added back to the matrix. # - if: contains( matrix.os, 'windows') && matrix.qt-version == '6.9.3' && env.WINDOWS_ARTIFACT != '' name: πŸ“€ Upload ${{matrix.os}} build artifacts of ${{env.APP_NAME}} ${{ env.VERSION }} to GitHub ${{ env.UPLOAD_URL }} run: | echo "πŸ” Verifying artifact: ${{ env.WINDOWS_ARTIFACT }}" echo "Current working directory:" pwd echo "Contents of build directory:" Get-ChildItem -Path build if (!(Test-Path -Path "./build/${{ env.WINDOWS_ARTIFACT }}")) { echo "❌ Error: Artifact not found!" exit 1 } echo "πŸ“€ Uploading artifact to GitHub release..." gh release upload continuous "./build/${{ env.WINDOWS_ARTIFACT }}" ` --repo "${{ github.repository }}" ` --clobber env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: echo "🍏 This job's status is ${{ job.status }}." # # Update the continuous release description (only once, from ubuntu) # - if: matrix.os == 'ubuntu-latest' && matrix.qt-version == '6.9.3' name: πŸ”„ Update continuous release description uses: actions/github-script@v9 with: script: | const commitSha = context.sha; const shortSha = commitSha.slice(0, 7); const commitDate = new Date().toISOString().split('T')[0]; const { data: commit } = await github.rest.git.getCommit({ owner: context.repo.owner, repo: context.repo.repo, commit_sha: commitSha }); const commitMessage = commit.message.split('\n')[0]; const newDescription = `Latest Continuous Build. Built from commit \`${shortSha}\` on ${commitDate}. This is an automated continuous build. These builds are generated automatically from the latest \`develop\` code and are intended for testing purposes.`; try { // Find the continuous release const { data: releases } = await github.rest.repos.listReleases({ owner: context.repo.owner, repo: context.repo.repo, }); const release = releases.find(r => r.tag_name === "continuous"); if (release) { // Update the release description await github.rest.repos.updateRelease({ owner: context.repo.owner, repo: context.repo.repo, release_id: release.id, body: newDescription }); console.log("Release description updated successfully"); } else { console.log("Continuous release not found"); } } catch (error) { console.error("Error updating release description:", error); } env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SocNetV-3.7/.github/workflows/build-release.yml000066400000000000000000000463171524402210300215450ustar00rootroot00000000000000# GitHub Action to build SocNetV releases for all 3 major OSes # Triggered when a v* tag is pushed; job aborts unless the tag’s commit is on origin/master name: Release SocNetV πŸ“¦ on: push: tags: - 'v*' env: APP_NAME: "SocNetV" UNIXNAME: "socnetv" QMAKE_PROJECT: "socnetv.pro" PUBLISHER: "Dimitris Kalamaras" QT_MODULES: "qtimageformats qt5compat qtcharts qtwebview" QMAKE_CONFIG: release # Never use debug. Windows builds will break. CMAKE_CONFIG: Release CORES: 16 MAC_ARTIFACT: "" LINUX_ARTIFACT: "" APPDIR_PREFIX: "/usr" WINDOWS_ARTIFACT: "" BUILD_CLI: "OFF" # Set to ON to also build the headless regression CLI (socnetv-cli) jobs: # # PREPARE RELEASE (single runner) # release_prepare: permissions: contents: write if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-22.04 steps: - name: πŸ€– Job information - Prepare release on tag ${{ github.ref }} run: | echo "πŸŽ‰ Preparing release for tag ${{ github.ref }}" echo "🐧 This job is now running on a ${{ runner.os }} server" - name: πŸ“‚ Check out repository ${{ github.repository }} uses: actions/checkout@v7 with: fetch-depth: 0 # Need full history to verify tags and create source archives - name: πŸ’‘ Set version from tag shell: bash run: | VERSION=${GITHUB_REF#refs/tags/} VERSION=${VERSION#v} echo "VERSION=${VERSION}" >> $GITHUB_ENV echo "Set VERSION to ${VERSION}" - name: πŸ”’ Verify tag is on master shell: bash run: | TAG_COMMIT=$(git rev-list -n 1 "${GITHUB_REF}") git fetch origin master if ! git merge-base --is-ancestor "$TAG_COMMIT" origin/master; then echo "❌ Tag commit is not on master. Aborting." exit 1 fi echo "βœ… Tag commit is on master. Proceeding." - name: 🏷️ Create GitHub Release if it doesn't exist uses: actions/github-script@v9 with: script: | const tagName = context.ref.replace('refs/tags/', ''); try { await github.rest.repos.getReleaseByTag({ owner: context.repo.owner, repo: context.repo.repo, tag: tagName, }); console.log(`Release for tag ${tagName} already exists.`); } catch (e) { if (e.status !== 404 && e.status !== 422) throw e; if (e.status === 404) { console.log(`Creating new release for tag ${tagName}`); await github.rest.repos.createRelease({ owner: context.repo.owner, repo: context.repo.repo, tag_name: tagName, name: `SocNetV ${tagName.replace(/^v/, '')}`, draft: false, prerelease: false, generate_release_notes: true, make_latest: "true" }); } else { console.log(`Release already exists or was just created by another runner (422).`); } } github-token: ${{ secrets.GITHUB_TOKEN }} - name: πŸ“¦ Create and Upload Source Code Archives shell: bash run: | echo "πŸ“¦ Creating source code archives..." TAG_NAME=${GITHUB_REF#refs/tags/} SRC_TARBALL="${{env.APP_NAME}}-${{env.VERSION}}-src.tar.gz" git archive --format=tar.gz --prefix=${{env.APP_NAME}}-${{env.VERSION}}/ \ -o "$SRC_TARBALL" "$TAG_NAME" SRC_ZIP="${{env.APP_NAME}}-${{env.VERSION}}-src.zip" git archive --format=zip --prefix=${{env.APP_NAME}}-${{env.VERSION}}/ \ -o "$SRC_ZIP" "$TAG_NAME" echo "πŸ“€ Uploading source archives to GitHub release..." gh release upload "$TAG_NAME" "$SRC_TARBALL" "$SRC_ZIP" --repo "${{ github.repository }}" --clobber env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # # BUILD + UPLOAD BINARIES (matrix runners) # release_build: permissions: contents: write if: startsWith(github.ref, 'refs/tags/') needs: release_prepare strategy: fail-fast: false matrix: os: [ubuntu-22.04, macos-latest, windows-2022] qt-version: ['6.9.3'] runs-on: ${{ matrix.os }} steps: - name: πŸ€– Job information - Build release on tag ${{ github.ref }} run: | echo "πŸŽ‰ Building release for tag ${{ github.ref }}" echo "🧱 This job is now running on a ${{ runner.os }} server" - name: πŸ“‚ Check out repository ${{ github.repository }} uses: actions/checkout@v7 with: fetch-depth: 0 - name: πŸ’‘ Set version from tag shell: bash run: | VERSION=${GITHUB_REF#refs/tags/} VERSION=${VERSION#v} echo "VERSION=${VERSION}" >> $GITHUB_ENV echo "Set VERSION to ${VERSION}" # (Optional but harmless) verify again; ensures we fail early if something weird happens - name: πŸ”’ Verify tag is on master shell: bash run: | TAG_COMMIT=$(git rev-list -n 1 "${GITHUB_REF}") git fetch origin master if ! git merge-base --is-ancestor "$TAG_COMMIT" origin/master; then echo "❌ Tag commit is not on master. Aborting." exit 1 fi echo "βœ… Tag commit is on master. Proceeding." # # Install dependencies (build tools, cmake, etc) # - if: contains(matrix.os, 'windows') name: πŸͺŸ Prepare for building on ${{ matrix.os }} run: | echo 'βš™οΈ Install dependencies for building....' - if: contains(matrix.os, 'ubuntu') name: 🐧 Prepare for building on ${{ matrix.os }} run: | echo 'βš™οΈ Install dependencies for building....' sudo apt-get update sudo apt-get install -y --fix-missing build-essential libssl-dev cmake ninja-build \ libxkbcommon-x11-dev libxcb-cursor-dev zlib1g-dev libcups2-dev libvulkan-dev \ desktop-file-utils patchelf libglu1-mesa-dev libfontconfig1 libfreetype6 libx11-dev libxext-dev \ libxrandr-dev libxrender-dev libxcb1-dev libx11-xcb-dev libxcb-glx0-dev sudo apt-get install -y libfuse2t64 2>/dev/null || sudo apt-get install -y libfuse2 2>/dev/null || true - if: contains(matrix.os, 'macos') name: 🍎 Prepare for building on ${{ matrix.os }} run: | echo 'βš™οΈ Install dependencies for building....' ls # # Install Qt (using https://github.com/jurplel/install-qt-action) # - if: contains(matrix.os, 'windows') name: Make sure MSVC is found uses: ilammy/msvc-dev-cmd@v1 - if: contains(matrix.os, 'windows') && startsWith(matrix.qt-version, '6.') name: Install Qt ${{ matrix.qt-version }} on ${{ matrix.os }} uses: jurplel/install-qt-action@v4 with: aqtversion: '==3.1.*' version: ${{ matrix.qt-version }} modules: ${{ env.QT_MODULES }} cache: true - if: contains(matrix.os, 'ubuntu') && startsWith(matrix.qt-version, '6.') name: Install Qt 6 on ${{ matrix.os }} uses: jurplel/install-qt-action@v4 with: aqtversion: '==3.1.*' version: ${{ matrix.qt-version }} modules: ${{ env.QT_MODULES }} cache: true - if: contains(matrix.os, 'macos') && startsWith(matrix.qt-version, '6.') name: Install Qt 6 on macOS uses: jurplel/install-qt-action@v4 with: aqtversion: '==3.1.*' version: ${{ matrix.qt-version }} modules: ${{ env.QT_MODULES }} cache: true # # Build SocNetV # # BUILD + PACKAGE FOR UBUNTU - if: matrix.os == 'ubuntu-22.04' name: Build ${{ env.VERSION }} for ${{ matrix.os }} with Qt${{ matrix.qt-version }} using cmake run: | echo "πŸ”Ž Check openssl version:" echo `openssl version` pwd ls rm -rf build cmake -S . -B build \ -DCMAKE_BUILD_TYPE=${{ env.CMAKE_CONFIG }} \ -DCMAKE_INSTALL_PREFIX=${{ env.APPDIR_PREFIX }} \ -DBUILD_CLI=${{ env.BUILD_CLI }} if [[ -d "./build" ]]; then ls -lh ./build/ else echo "❌ Error! ./build directory was not created!" exit 1 fi cmake --build build -j$(nproc) cd build ls -lh . if [[ -f "./${{ env.UNIXNAME }}" ]]; then ls -ls "${{ env.UNIXNAME }}" else echo "❌ Error! Executable ${{ env.UNIXNAME }} not found in build directory!" exit 1 fi - if: matrix.os == 'ubuntu-22.04' name: Create AppImage ${{ env.VERSION }} for ${{ matrix.os }} with Qt${{ matrix.qt-version }} run: | echo "πŸ“¦ Creating AppImage ${{ env.VERSION }} for ${{ matrix.os }}" cd ./build/ ls -lh . cmake --install . --prefix AppDir/usr if [[ ! -f "./AppDir/usr/bin/${{ env.UNIXNAME }}" ]]; then echo "❌ Error: Installation failed, binary not found in AppDir!" exit 1 fi wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage chmod a+x linuxdeploy-x86_64.AppImage linuxdeploy-plugin-qt-x86_64.AppImage QMAKE=$(which qmake) \ LINUXDEPLOY_OUTPUT_VERSION="tmp-out" \ ./linuxdeploy-x86_64.AppImage \ --appdir AppDir \ --plugin qt \ --output appimage ARTIFACT_FN="${{ env.APP_NAME }}-${{ env.VERSION }}-x86_64.AppImage" CREATED_APPIMAGE=$(ls ./*.AppImage 2>/dev/null | grep -v linuxdeploy | head -1) if [[ -z "$CREATED_APPIMAGE" ]]; then echo "❌ AppImage creation failed! No AppImage found." exit 1 fi mv "$CREATED_APPIMAGE" "$ARTIFACT_FN" ls -lh ./*.AppImage echo "LINUX_ARTIFACT=${ARTIFACT_FN}" >> $GITHUB_ENV # BUILD + PACKAGE FOR MACOS - if: contains(matrix.os, 'macos') name: Build ${{ env.APP_NAME }} ${{ env.VERSION }} for ${{ matrix.os }} with Qt ${{ matrix.qt-version }} using cmake timeout-minutes: 360 run: | echo "🍎 Preparing macOS build..." echo `openssl version` pwd ls rm -rf build cmake -S . -B build \ -DCMAKE_BUILD_TYPE=${{ env.CMAKE_CONFIG }} \ -DCMAKE_INSTALL_PREFIX=${{ env.APPDIR_PREFIX }} \ -DBUILD_CLI=${{ env.BUILD_CLI }} if [[ -d "./build" ]]; then ls -lh ./build/ else echo "❌ Error! ./build directory was not created!" exit 1 fi cmake --build build -j$(sysctl -n hw.ncpu) cd build ls -lh . if [[ -d "./${{ env.APP_NAME }}.app" ]]; then find "${{ env.APP_NAME }}.app" else echo "❌ Error! ${{ matrix.os }} bundle not found!" exit 1 fi if [[ -f "${{ env.APP_NAME }}.app/Contents/MacOS/${{ env.APP_NAME }}" ]]; then lipo -info "${{ env.APP_NAME }}.app/Contents/MacOS/${{ env.APP_NAME }}" else echo "❌ Error: binary not found!" exit 1 fi echo "${{ secrets.MACOS_CERTIFICATE }}" | base64 --decode > certificate.p12 security create-keychain -p "" build.keychain security default-keychain -s build.keychain security unlock-keychain -p "" build.keychain security import certificate.p12 -k build.keychain -P "${{ secrets.MACOS_CERTIFICATE_PASSWORD }}" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple: -k "" build.keychain rm -f certificate.p12 xcrun notarytool store-credentials "AC_PASSWORD" \ --apple-id "${{ secrets.AC_APPLE_ID }}" \ --team-id "${{ secrets.AC_TEAM_ID }}" \ --password "${{ secrets.AC_PASSWORD }}" if [[ -f "${{ env.APP_NAME }}.app/PkgInfo" ]]; then rm "${{ env.APP_NAME }}.app/PkgInfo" fi if [[ ! -f "${{ env.APP_NAME }}.app/Contents/PkgInfo" ]]; then echo "APPL????" > "${{ env.APP_NAME }}.app/Contents/PkgInfo" fi find "${{ env.APP_NAME }}.app" -type f -exec xattr -c {} \; macdeployqt "${{ env.APP_NAME }}.app" -verbose=3 || exit 1 codesign --deep --force --verbose \ --options runtime \ --entitlements ../scripts/entitlements.plist \ --sign "Developer ID Application: Dimitris Kalamaras (${{ secrets.AC_TEAM_ID }})" \ "${{ env.APP_NAME }}.app" codesign --verify --verbose "${{ env.APP_NAME }}.app" hdiutil create -volname "${{ env.APP_NAME }}" -srcfolder "${{ env.APP_NAME }}.app" -ov -format UDZO "${{ env.APP_NAME }}.dmg" codesign --force --sign "Developer ID Application: Dimitris Kalamaras (${{ secrets.AC_TEAM_ID }})" "${{ env.APP_NAME }}.dmg" security set-keychain-settings -t 72000 -l build.keychain SUBMIT_OUTPUT=$(xcrun notarytool submit "${{ env.APP_NAME }}.dmg" --keychain-profile "AC_PASSWORD") NOTARY_SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep "id:" | head -1 | awk '{print $2}') if [[ -z "$NOTARY_SUBMISSION_ID" ]]; then echo "❌ Error: Notarization submission ID is empty!" exit 1 fi START_TIME=$(date +%s) TIMEOUT_SECONDS=1800 while true; do CURRENT_TIME=$(date +%s) ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) if [[ $ELAPSED_TIME -gt $TIMEOUT_SECONDS ]]; then echo "⚠️ Notarization timeout after $(($TIMEOUT_SECONDS / 60)) minutes" break fi security unlock-keychain -p "" build.keychain NOTARY_INFO=$(xcrun notarytool info --keychain-profile "AC_PASSWORD" "$NOTARY_SUBMISSION_ID" 2>&1) echo "$NOTARY_INFO" NOTARY_STATUS=$(echo "$NOTARY_INFO" | grep -i "status:" | awk '{print $2}' | tr -d '[:space:]') if [[ "$NOTARY_STATUS" == "Accepted" ]]; then xcrun stapler staple "${{ env.APP_NAME }}.dmg" DMG_NAME="${{ env.APP_NAME }}-${{ env.VERSION }}.dmg" mv "${{ env.APP_NAME }}.dmg" "${DMG_NAME}" ls -lh *.dmg echo "MAC_ARTIFACT=${DMG_NAME}" >> $GITHUB_ENV break elif [[ "$NOTARY_STATUS" == "Invalid" || "$NOTARY_STATUS" == "Rejected" ]]; then xcrun notarytool log --keychain-profile "AC_PASSWORD" "$NOTARY_SUBMISSION_ID" DMG_NAME="${{ env.APP_NAME }}-${{ env.VERSION }}-unsigned.dmg" mv "${{ env.APP_NAME }}.dmg" "${DMG_NAME}" echo "MAC_ARTIFACT=${DMG_NAME}" >> $GITHUB_ENV break fi sleep 60 done # BUILD + PACKAGE FOR WINDOWS - if: contains(matrix.os, 'windows') name: Build ${{ env.APP_NAME }} ${{ env.VERSION }} for ${{ matrix.os }} with Qt ${{ matrix.qt-version }} using cmake. run: | echo "πŸ”Ž Listing some directories" dir D:\a\app\Qt\ where qmake where windeployqt mkdir build cmake -S . -B build -DCMAKE_BUILD_TYPE=${{ env.CMAKE_CONFIG }} -DBUILD_CLI=${{ env.BUILD_CLI }} cmake --build build -j${{ env.CORES }} --config ${{ env.CMAKE_CONFIG }} -v cd build dir windeployqt ${{ env.CMAKE_CONFIG }}\socnetv.exe --release --compiler-runtime if (!(Test-Path "C:\Windows\System32\MSVCP140_1.dll")) { exit 1 } if (!(Test-Path "C:\Windows\System32\MSVCP140_2.dll")) { exit 1 } copy "C:\Windows\System32\MSVCP140.dll" ${{ env.CMAKE_CONFIG }}\ copy "C:\Windows\System32\MSVCP140_1.dll" ${{ env.CMAKE_CONFIG }}\ copy "C:\Windows\System32\MSVCP140_2.dll" ${{ env.CMAKE_CONFIG }}\ copy "C:\Windows\System32\VCRUNTIME140.dll" ${{ env.CMAKE_CONFIG }}\ copy "C:\Windows\System32\VCRUNTIME140_1.dll" ${{ env.CMAKE_CONFIG }}\ copy "C:\Windows\System32\CONCRT140.dll" ${{ env.CMAKE_CONFIG }}\ copy ..\COPYING ${{ env.CMAKE_CONFIG }}\LICENSE.txt if (!(Get-Command "iscc.exe" -ErrorAction SilentlyContinue)) { choco install -y InnoSetup } $env:PATH += ";C:\Program Files (x86)\Inno Setup 6\" iscc.exe /? copy ..\scripts\innosetup.iss innosetup.iss $config = "${{ env.CMAKE_CONFIG }}" $replacement = "#define RELEASEFOLDER `"$config\\`"" (Get-Content innosetup.iss) -replace '#define RELEASEFOLDER "release\\"', $replacement | Set-Content innosetup.iss & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" "innosetup.iss" $originalInstaller = Get-ChildItem -Path "SocNetV-*installer.exe" -ErrorAction SilentlyContinue if (-not $originalInstaller) { exit 1 } $installerName = "SocNetV-${{ env.VERSION }}-windows-installer.exe" Get-ChildItem -Path "SocNetV-*installer.exe" | Move-Item -Destination $installerName echo "WINDOWS_ARTIFACT=$installerName" >> $env:GITHUB_ENV # # UPLOAD BINARIES TO RELEASE # - if: matrix.os == 'ubuntu-22.04' && env.LINUX_ARTIFACT != '' name: πŸ“€ Upload Linux artifact to GitHub Release shell: bash run: | echo "πŸ” Verifying artifact: ${{ env.LINUX_ARTIFACT }}" ls build if [ ! -f "./build/${{ env.LINUX_ARTIFACT }}" ]; then echo "❌ Error: Artifact not found!" exit 1 fi TAG_NAME=${GITHUB_REF#refs/tags/} gh release upload "$TAG_NAME" "./build/${{ env.LINUX_ARTIFACT }}" --repo "${{ github.repository }}" --clobber env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: contains(matrix.os, 'macos') && env.MAC_ARTIFACT != '' name: πŸ“€ Upload macOS artifact to GitHub Release shell: bash run: | echo "πŸ” Verifying artifact: ${{ env.MAC_ARTIFACT }}" ls build if [ ! -f "./build/${{ env.MAC_ARTIFACT }}" ]; then echo "❌ Error: Artifact not found!" exit 1 fi TAG_NAME=${GITHUB_REF#refs/tags/} gh release upload "$TAG_NAME" "./build/${{ env.MAC_ARTIFACT }}" --repo "${{ github.repository }}" --clobber env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: contains(matrix.os, 'windows') && env.WINDOWS_ARTIFACT != '' name: πŸ“€ Upload Windows artifact to GitHub Release run: | if (!(Test-Path -Path "./build/${{ env.WINDOWS_ARTIFACT }}")) { echo "❌ Error: Artifact not found!" exit 1 } $TagName = "${{ github.ref }}".Replace("refs/tags/", "") gh release upload "$TagName" "./build/${{ env.WINDOWS_ARTIFACT }}" --repo "${{ github.repository }}" --clobber env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: echo "Job status is ${{ job.status }}." SocNetV-3.7/.github/workflows/validate.yml000066400000000000000000000010531524402210300206050ustar00rootroot00000000000000name: Validate-YAMLs on: push: branches: [master, develop] jobs: validate-yamls: runs-on: ubuntu-latest steps: - name: πŸ“‚ Checkout repository uses: actions/checkout@v7 - name: 🐍 Install yamllint run: | python3 -m pip install --upgrade pip python3 -m pip install yamllint - name: βœ… Lint workflow YAML files run: | yamllint \ -c "${{ github.workspace }}/.github/workflows/yamlint.conf" \ "${{ github.workspace }}/.github/workflows/"*.yml SocNetV-3.7/.github/workflows/yamlint.conf000066400000000000000000000003531524402210300206170ustar00rootroot00000000000000extends: default rules: document-start: disable truthy: disable line-length: max: 280 level: warning trailing-spaces: disable new-line-at-end-of-file: enable empty-lines: max: 2 max-start: 0 max-end: 0 SocNetV-3.7/.gitignore000066400000000000000000000005711524402210300146700ustar00rootroot00000000000000CLAUDE.md *.pro.user build build-asan builds/ build-SocNetV* build-tools .directory bin/* build-tests-* .idea cmake-build-* doc/build *.qmlc stage parts prime *.snap snap/.snapcraft/state src/languages/*.qm .vscode/ CMakeFiles/ CMakeCache.txt CMakeLists.txt.user* *.o qrc_*.cpp moc_*.cpp moc_*.h ui_*.h /src/.qmake.stash .qmake.stash /src/Makefile Makefile secrets/* .DS_Store SocNetV-3.7/.travis.yml000066400000000000000000000032251524402210300150100ustar00rootroot00000000000000# Build SocNetV images for Linux and macOS # https://socnetv.org # Last Update: Apr 2026 # Copyright (c) 2026 by Dimitris Kalamaras # # This script builds SocNetV binaries ONLY when the commit message contains [travis] # # Exclude the continuous tag to avoid rebuild loops when uploadtool pushes it branches: except: - /^(?i:continuous)$/ language: cpp env: global: - SOCNETV_VERSION=3.7 - UPLOADTOOL_ISPRERELEASE=true jobs: include: - os: linux compiler: gcc dist: jammy # Ubuntu 22.04 β€” replaces focal (20.04) env: FAILURES=TRUE if: commit_message =~ /\[travis\]/ AND commit_message !~ /\[skip linux\]/ - os: osx compiler: clang env: FAILURES=TRUE osx_image: xcode15.2 # replaces xcode14.2 if: commit_message =~ /\[travis\]/ AND commit_message !~ /\[skip osx\]/ cache: directories: - /opt/homebrew - /Users/travis/Library/Caches/Homebrew exclude: - os: osx compiler: gcc allow_failures: - env: FAILURES=TRUE before_install: - chmod +x scripts/travis_before.sh - ./scripts/travis_before.sh install: - chmod +x scripts/travis_install_deps.sh - ./scripts/travis_install_deps.sh script: - if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then chmod +x scripts/travis_make_build_linux.sh; fi - '[ "$TRAVIS_OS_NAME" != linux ] || ./scripts/travis_make_build_linux.sh' - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then chmod +x scripts/travis_make_build_macos.sh; fi - '[ "$TRAVIS_OS_NAME" != osx ] || ./scripts/travis_make_build_macos.sh' after_success: - chmod +x scripts/travis_upload_packages.sh - ./scripts/travis_upload_packages.shSocNetV-3.7/AUTHORS000066400000000000000000000010021524402210300137360ustar00rootroot00000000000000Developer: Dimitris Kalamaras See my blog at: https://dimitris.apeiro.gr MacPorts packager: - Szabolcs HorvΓ‘t Debian packagers: - Andreas Tille - AdriΓ  GarcΓ­a-AlzΓ³rriz - Caitlin Matos - Serafeim Zanikolas (>0.43) - Alejandro Garrido Mota (<0.44) Fedora packager: - Eugene A. Pivnev Gentoo packager: - Markos Chandras Arch packager: - Tom Tryfonidis Translators: - Greek: None - German: Daniel Pinto dos Santos Donators: - Robert Butscher SocNetV-3.7/CHANGELOG.md000066400000000000000000002216641524402210300145210ustar00rootroot00000000000000# Changelog All notable changes to this project are documented in this file. ## [3.7] – Aug 2026 ### New Features - **Katz Centrality** (#10): new **Analyze β†’ Centrality β†’ Katz Centrality (KC)** measure, with full parity to the app's other prominence indices β€” HTML/CSV report, all 4 Layout β†’ By Prominence Index variants, Filter Nodes by Centrality, Find Node by index score, and the prominence distribution chart. Katz gives each actor credit for indirect connections, discounted the further away they are by a user-supplied attenuation factor alpha (a direct tie counts full price, a 2-hop tie counts alpha times as much, and so on) β€” useful when raw Eigenvector Centrality collapses to zero on directed or disconnected networks. The dialog asks for alpha; the computation rejects and explains itself if alpha is too large to converge (must be smaller than 1 / the network's largest eigenvalue). Computed in closed form via the matrix identity $ I + \alpha A + \alpha^2 A^2 + \dots = (I - \alpha A)^{-1} $ (valid for $ |\alpha| < 1/\lambda_{max} $, the same geometric-series identity used for ordinary numbers, applied to matrices): $ C_{Katz} = \left( (I - \alpha A^T)^{-1} - I \right) \cdot \mathbf{1} $. - **Bonacich Power Centrality** (#39): new **Analyze β†’ Centrality β†’ Bonacich Power Centrality (BPC)** measure, with the same full parity as Katz β€” HTML/CSV report, all 4 Layout β†’ By Prominence Index variants, Filter Nodes by Centrality, Find Node by index score, and the prominence distribution chart. Like Katz Centrality, it gives each actor credit for indirect connections discounted by distance, but adds a second parameter beta that can be **negative** β€” in which case being tied to well-connected others can *hurt* rather than help a node's score (e.g. a buyer connected to powerful sellers has less bargaining power the more powerful those sellers are), and per-node scores can come out negative, unlike every other measure in the app. alpha is a free overall scale factor with no convergence bound; only beta must satisfy $ |\beta| < 1/\lambda_{max} $. Distinct from the existing Gil-Schmidt "Power Centrality (PC)". Computed via $ b = \alpha (I - \beta R)^{-1} R \cdot \mathbf{1} $, where $ R = A^T $ (same directional convention as Katz). - **Node and Graph Connectivity** (#7): two new analyses under **Analyze β†’ Cohesion**. **Node Connectivity** computes the minimum number of nodes that must be removed to disconnect two chosen actors. **Graph Connectivity** computes the same for the whole network β€” the fewest nodes that would need to be removed to disconnect it at its weakest point. For directed networks, both ask whether to respect edge direction (strong) or ignore it (weak), matching the same choice now offered by Connectedness. - **Geodesic distance distribution report** (#89): new **Analyze β†’ Cohesion β†’ Geodesic Distribution** action (also in the Control Panel combo) writes an HTML table of pair-count vs. integer distance bucket across all node pairs. Computation is cache-aware: reuses the APSP result when distances have already been calculated in the same session. - **Shortest path reconstruction and canvas highlighting** (#139): when computing the geodesic distance between two specific nodes (**Analyze β†’ Cohesion β†’ Distance**), the dialog now shows the full sequence of intermediate nodes (e.g. *A β†’ C β†’ D β†’ B*) in addition to the distance value. BFS is used for unweighted graphs, Dijkstra for weighted ones. The path edges are simultaneously **selected on the canvas** β€” exactly as if the user had Shift-clicked each one β€” unlocking move, inspect, and context-menu operations on the whole path. Only edges are selected (not nodes) to avoid highlighting unrelated connected edges. - **Viewport auto-fit after layouts, loads, and window resize** (#249): the canvas now automatically scales and centres to show the full network after every layout algorithm completes, after opening a file, and after generating a random network. When the application window is resized, node positions rescale proportionally and the view re-fits once the window settles β€” eliminating the "network disappeared" problem during window drag. Zoom buttons, Ctrl+scroll, and Reset (Ctrl+0) are now reliable at any zoom level. Small networks are never scaled beyond 100%. - **Tomita pivot selection in clique census** (#64): the Bron–Kerbosch algorithm now selects a pivot vertex $ u \in P \cup X $ that maximises $ |N(u) \cap P| $ before each recursive level, and iterates only over $ P \setminus N(u) $. This can reduce branch count to a single candidate per level on dense graphs, giving dramatic speedups on real-world networks without changing the set of maximal cliques reported. - **CLI: `--encoding` and `--interactive-script`** (#261): `--encoding ` loads the startup file with a given text codec (e.g. `UTF-8`), bypassing the "Preview file & Choose Encoding" dialog. `--interactive-script ` runs a plain-text script after startup, one command per line β€” `delay X` (wait X seconds) or `new` (File β†’ New) β€” letting SocNetV be driven and profiled from the command line without manual clicking. - **Seven more interactive-script commands** (#262): `relation N` (switch relation), `unilateral` (toggle unilateral edges), `erdos N p directed|undirected` (generate an ErdΕ‘s–RΓ©nyi network), `save path` (save as GraphML), and `add-node` / `add-edge source target [weight]` / `add-relation name` for building a network incrementally from a script. All dispatched through the same real event-loop/`graphThread` path a user action would take, not called directly. - **Sixteen more interactive-script commands** (#113): `report-centrality-degree` / `-closeness` / `-closeness-ir` / `-betweenness` / `-stress` / `-eccentricity` / `-power` / `-information` / `-eigenvector`, `report-prestige-degree` / `-proximity` / `-pagerank`, and `report-reciprocity` / `-eccentricity` / `-clustering-coefficient` / `-triad-census` β€” each mirrors its real Analyze menu action exactly, with `[weights] [inverse] [dropisolates] [csv]`-style tokens where applicable β€” giving every one of these report writers a headless benchmark path for the first time. The existing `distances` command also gained a `[csv]` token, matching the new Output format setting below. - **Export analysis reports as CSV** (#113): new **Settings β†’ Reports β†’ Output format** option (HTML/CSV) covers the matrix-family reports β€” Adjacency, Distances, Geodesics, Reachability, Laplacian, Degree, Cocitation, Transpose, Adjacency Inverse, Walks, and the Structural Equivalence Similarity/Pearson/Dissimilarities reports β€” every Centrality/Prestige report: Degree, Closeness, Closeness (Influence Range), Betweenness, Stress, Eccentricity, Power, Information, and Eigenvector centrality, plus Degree, Proximity, and PageRank prestige β€” and Reciprocity, the standalone Eccentricity report, Clustering Coefficient, and Triad Census. CSV files are a lean, table-only alternative to the full HTML report (no prose, summary stats, or charts) and always open in the system's default handler (e.g. a spreadsheet app) instead of the built-in report viewer. Clique Census and Hierarchical Clustering write HTML only, permanently β€” both combine several heterogeneous sub-tables (co-membership matrices, a dendrogram) that don't reduce to one flat CSV table. Connectedness and Node/Graph Connectivity were never file reports to begin with β€” both show their result in a dialog, not a report. ### Improvements - **Canvas rendering internals: correctness and performance pass** (#250): an eight-commit cleanup of `GraphicsWidget` (`ab3edf38`, `74a1ae12`, `b000f1af`, `05cbfeea`, `8e114711`, `f7da5f5e`, `b630aaee`, `51e239b5`). Performance: single-probe hash lookups replace double lookups across a dozen node/edge property setters; scene-selection queries merged into one pass instead of two independent re-queries per rubber-band drag; a per-resize full-scene scan for layout guides replaced with a maintained list; the edge lookup key β€” previously a freshly-allocated string built on every edge operation β€” replaced with an allocation-free packed integer; two hardcoded `reserve()` calls that pre-sized hash buckets for 500,000 edges / 10,000 nodes regardless of actual network size, removed. Correctness fixes found along the way: a double-free / undefined-behaviour bug in guide-item cleanup; a dangling-pointer double-free introduced by the guide-list change above; Select All using viewport pixels instead of scene coordinates; the rotation slider double-applying its transform on every click; and the canvas's hot-path debug logging not actually respecting the Debug Messages toggle. Closed out with a full Doxygen documentation pass and two dead-code removals (`hasNode()`, `setNodeSizeAll()`). All golden regression baselines pass unchanged throughout. ### Bug Fixes - **HCA uses BFS on unweighted networks** (#193): the Hierarchical Clustering Analysis dialog always ran Dijkstra (weighted distances), even on unweighted graphs. `considerWeights` is now derived from `activeGraph->isWeighted()`, so BFS is used on unweighted networks, matching the behaviour of all other distance-based analyses. - **Bezier curves toggle now applied at startup** (#246): edges always rendered as straight lines regardless of the saved setting in **Settings β†’ Edge**. The toggle is now correctly applied on first load. - **Remove misleading "session only" labels** (#247): several preferences in the Edit and Options menus were annotated "session only" despite being persisted between sessions. Labels removed. - **Preserve aspect ratio for custom node images** (#122): non-square images set as node icons via **Edit β†’ Node β†’ Change shape β†’ Custom** were stretched to fill the node bounding box. They are now scaled with `Qt::KeepAspectRatio` and centred within the node. - **Canvas zoom anchors to your current view** (#248): zooming in or out now stays anchored to wherever you last panned. Navigate to a corner of a large network and zoom in β€” the view stays there instead of jumping back to the network centre. - **Auto-fit undersized networks that are close to viewport size** (#253): after the #249 viewport auto-fit landed, networks whose content was only marginally larger than the canvas β€” a few percent, common on large/dense networks from node marker and label overhang β€” were shrunk noticeably below 100%, leaving visible empty margins on the right and bottom. Manually pressing Reset (Ctrl+0) always showed the network at the correct size. Auto-fit now only zooms out for content that's genuinely, substantially larger than the viewport; content close to viewport size uses the same tight, margin-free 100% fit as Reset. - **Layouts and analysis reports no longer freeze the app on large networks** (#254): running a layout by centrality/prestige index (Radial, On Levels, Node Size, or Node Color by prominence index), a force-directed layout (Eades, Fruchterman-Reingold, Kamada-Kawai), or any weighted-centrality/distance report or matrix under the **Analyze** menu (Closeness, Betweenness, Stress, Power, Eccentricity, the distance and geodesics matrices, similarity/Pearson reports, diameter, average distance, geodesic distribution, and more) made the whole application completely unresponsive for the entire computation β€” unable to repaint, receive input, or even respond to window-manager requests β€” on large networks or slow indices (e.g. Betweenness Centrality, Information Centrality). The app now stays fully responsive during all of these, showing a "please wait" indicator (status bar message and busy cursor, plus a progress dialog with a Cancel button) instead of appearing to hang. - **Canvas no longer takes 30+ seconds to clear on large networks** (#260): clearing a large network (loading a new file over one already displayed, or File β†’ New) was slow β€” confirmed 30s to over a minute on a 2000-node/40,000-edge network. Two causes, both fixed: (1) `GraphicsNode`/`GraphicsEdge` destructors did a cascade of individual per-edge unlinking calls during node/edge destruction instead of one bulk teardown β€” `GraphicsWidget::clear()` now signals that a bulk clear is in progress so those destructors can skip the redundant work; (2) the dominant cost β€” a `QComboBox` state-sync call re-entering an edge-mode change handler and forcing a synchronous full-canvas repaint of the still-fully-rendered old network β€” is now correctly guarded against re-entry. - **Removing one direction of a reciprocated edge no longer deletes both** (#251): via **Edit β†’ Remove Edge**, choosing which direction of a bidirectional edge to remove used to depend on *which* direction you picked β€” one choice correctly downgraded the edge to a single directed arc, the other deleted it entirely. Removing either direction now always downgrades to a single directed arc in the other direction, as expected. - **Remove Edge dialog showed "0 --> 0" for a rubber-band-selected edge** (#264): selecting a reciprocated edge by rubber-band (rather than clicking it directly) and then pressing Remove Edge opened the direction-choice dialog with both options showing "0 --> 0" instead of the real node numbers. Found while testing #251. - **Edge Properties dialog didn't ask which direction for a reciprocated edge** (#265): a reciprocated edge (both Aβ†’B and Bβ†’A drawn between two nodes) is one canvas item but two independently-stored directed arcs β€” each can have its own weight/label/color. **Edge Properties** silently always edited whichever direction was created first, with no way to reach the other direction's properties. Now asks which direction to edit, matching the existing behaviour of **Edit β†’ Remove Edge**. Found while testing #251/#264. - **Information Centrality, Eigenvector Centrality, and Walks Total no longer freeze the app** (#263): these three computations never went through the distance engine, so they were correctly out of #254's original scope β€” but still fully blocked the GUI for their own (different-algorithm) computation. Now non-blocking, same as every other long-running analysis. - **Walks Total matrix no longer shows misleadingly precise huge numbers** (#266): on networks past roughly a few dozen nodes, cell values in the **Walks Total** report could reach hundreds of digits, printed in full as if exact β€” walk counts genuinely grow that large (the underlying formula is correct), but `double` only carries ~15-17 significant digits of real precision, so most of those digits were meaningless. Cell values beyond 1000 now render in scientific notation instead, which is honest about the precision actually available. - **Canvas no longer silently falls back to slow software rendering** (WS10): some installs had OpenGL disabled without the user ever choosing that β€” an old default, or a long-forgotten toggle β€” making large networks sluggish and unresponsive with no indication why. OpenGL is now enabled automatically going forward; **Settings β†’ Canvas** still lets you turn it off if you need to. Generating an extremely large random network (e.g. ErdΕ‘s–RΓ©nyi with a very high edge probability) could also crash the app from memory exhaustion β€” this is now refused with a warning instead. - **Statistics Panel now shows the correct edge count after filtering nodes** (#270): the Edges count could be wrong while a node filter (Focus on Node, Focus on Selection, and others) was active, and stayed wrong after clearing the filter. Fixed. - **"Focus on Node" now works on the first node** (#271): applying Focus on Node (Ego Network) β€” or the matching radial-by-ego-network layout β€” to the very first node in a network silently did nothing. Fixed. - **ErdΕ‘s–RΓ©nyi generator now respects low edge probabilities** (#267): generating a random network with edge probability 0.01 always produced zero edges, regardless of node count. Fixed. - **Singular matrices now correctly reported as non-invertible** (#269): the Inverse Adjacency Matrix report could claim a mathematically singular matrix was invertible, showing meaningless huge numbers instead. Information Centrality had the same gap internally, silently computing scores from an empty result instead of reporting that the index isn't defined for that network. Both fixed. - **Self-loop edges no longer reappear after node/edge filtering**: filtering a network (Focus on Node, Focus on Selection, Filter Edges by Weight, and others) could leave a node's self-loop visible even after that node was hidden, showing an orphaned edge with no visible endpoints. Fixed. - **Connectedness now asks weak vs. strong for directed networks** (#272): checking whether a directed network is connected only ever checked weak connectivity (ignoring edge direction), with no way to check strong connectivity from the UI. Now asks which one to check. - **Memory corruption when computing multiple prominence indices with isolated nodes present** (#274, found during pre-3.7-release cross-platform baseline verification): `Graph::vertices()` cached its vertex count without accounting for which drop-isolates/count-all flags produced it, so a measure that always drops isolates (e.g. Information Centrality) could leave a stale, smaller count that a later measure asked to keep isolates (e.g. Eigenvector Centrality) would then inherit - undersizing a working array relative to the real adjacency matrix. Silent on macOS; a hard crash on Linux. Fixed by keying the cache on those flags. Also fixed: several `GraphVertex` score fields (Power/Information/PageRank-Prestige/Proximity-Prestige/ Eigenvector/Katz/Bonacich centrality and their standardized variants) were never zero-initialized, so an isolated node skipped by its own measure's isolate handling could report leftover garbage instead of 0. - **Reciprocity report no longer shows "nan" for actors with no ties** (found during #113): the per-actor Symmetric/nonSymmetric columns divided by an actor's total tie count with no zero-guard, unlike every other ratio in the same report. An actor with zero inbound and outbound edges hit a 0/0 division, showing the literal text "nan" in both columns. Now reports 0 for actors with no ties, matching how the report already handled every other degenerate case. - **Progress dialog no longer lingers behind Node/Graph Connectivity's result dialog**: the busy dialog shown while computing was reset but never actually hidden (`reset()` only hides when `autoClose` is true, which this dialog deliberately disables), so it stayed visibly on screen behind the result dialog until that was dismissed. Now explicitly hidden before the result dialog opens. - **Fixed rare crashes on app quit** (found during WS7, #257): closing the app could crash depending on what you'd done in the session. `closeEvent()` explicitly deleted the canvas widget and its scene mid-handler, while Qt's own window-close teardown continues delivering events to them immediately afterward - a dangling-pointer crash confirmed via `lldb`/AddressSanitizer. A related crash deleted two node-editing actions while still attached to a live toolbar. Separately, exporting to PDF at least once in a session left a dangling internal pointer that `closeEvent()` then double-freed on quit. All three predate this release; none needed the deletes they were doing (the app's own object lifetime already made them redundant), so they're simply no longer deleted at those points. - **Node size reverted after editing a selected node** (#273): changing a node's size via Node Properties or Edit Selection in Data Table applied the new size, but it snapped back to the old one as soon as the node was deselected. The canvas item's deselect-restore cache wasn't updated by an external size change made while still selected. - **Layout by Eigenvector Centrality ignored "drop isolates"** (found during WS11): `layoutByProminenceIndex()` called `centralityEigenvector()` with one argument missing, so the "inverse weights" slot silently received the "drop isolates" value instead, and isolates were always kept regardless of the setting. Affects **Layout β†’ Radial/Level/Node Size/Node Color by Prominence β†’ Eigenvector Centrality** specifically; every other prominence index in the same menus was unaffected. ### Maintenance - `AUTHORS`: added Andreas as Debian package maintainer. - **Shipped Adjacency test dataset failed to load** (#256): one file in the regression suite's shipped-dataset roundtrip check (`TinyAdj_Dir_N3_E4_clucof.adj`) used a delimiter inconsistent with every other Adjacency test file, causing a parse error. Reformatted to match convention. Test data only, not user-facing. - **`GraphVertex` is no longer a `QObject`, edge-visibility updates now batched** (WS3): every node in a loaded network was a `QObject` solely to relay one internal signal β€” dropped in favour of a plain method call on its owning `Graph`, saving 16 bytes per vertex plus a per-vertex heap allocation (`QObjectPrivate`) entirely. Separately, relation switches and unilateral-edge toggles used to notify the canvas once per edge, each crossing to the GUI thread as its own queued dispatch β€” a large network's relation switch could mean thousands of individually-queued updates. Both operations now collect their changes and notify the canvas once, in a single batch, with the same per-edge canvas logic applied in a plain loop instead of via thousands of separate signal dispatches. All golden regression baselines pass unchanged; live-tested with a scripted 2000-node/ 40,000-edge network (see `roadmap_ws12_cli_scripting_mode.md`). - Perf benchmark baselines (`macos-arm64`, `macos-m5`) re-recorded against the `v3.6` tag β€” the committed baselines predated the WS3 M1 DistanceEngine speedup by months, so every benchmark run was (correctly, but confusingly) reporting 30-70% "faster than baseline" on unrelated work, not evidence about whatever change was actually being tested. `linux-x86_64`'s baseline needs the same treatment but isn't reachable from this machine. - **Removed `qDebug()` string-formatting cost from every hot computation path, and made release builds quiet by default** (#268): a plain `qDebug()` call unconditionally builds and formats its message before checking whether anything will actually print it β€” so every debug statement in a hot loop was paying full string-formatting cost on every call, even in a normal release build with debug output off. Converted to Qt's category-gated `qCDebug()`, which skips argument evaluation entirely when its category is disabled (near-zero cost), across the whole codebase β€” `DistanceEngine`, the parsers, `matrix.cpp`, and every other file with a live `qDebug()` call, ~1760 call sites in total. Separately, a no-flag launch could silently inherit a noisy debug setting left over from a previous session (via the Settings dialog's "print debug messages" checkbox) instead of coming up quiet β€” fixed so a plain launch is always quiet regardless of what was last saved. Measured, same machine: `DistanceEngine` CLI operations 43×–59Γ— faster; real GUI runs (which additionally hit contention between `DistanceEngine`'s parallel worker threads all logging to stderr at once) up to ~600Γ— faster; GraphML/Pajek load ~1.4×–3Γ—; Information Centrality ~14Γ— (and its "this may take 2+ minutes" warning dialog, which hadn't been recalibrated since this cost existed, updated with current numbers and a higher threshold). All golden regression baselines pass unchanged throughout β€” purely a logging-mechanism change, no computational or behavioural difference. - **APSP results moved off per-vertex `QHash` onto a centralised `Matrix` pair** (WS5 A2): `GraphVertex::m_distance`/`m_shortestPaths` were replaced with `Graph::m_apspDist`/`m_apspSigma` (`QHash`), removing a hash lookup per predecessor per vertex per source that profiling found responsible for ~73% of `DistanceEngine::finalize()`'s samples. Measured: 15-17% faster at 1,000 nodes, 9% faster at 7,343 nodes (`geom.net`), 1-3% faster end-to-end in the GUI; no measurable change at ≀500 nodes, where this cost is too small to matter. All golden regression baselines pass unchanged. - **`Matrix`'s internal storage is now one contiguous buffer, not N+1 separate allocations** (WS5 A3): replaced per-row heap objects with a single flat buffer plus a precomputed row-pointer index, removing 7,343 of 7,344 allocations for a `geom.net`-scale matrix. Measured (median of 5 runs, N=7,343): 38-64% faster construction, 14-19% faster cell access, depending on network topology; no measurable change to `DistanceEngine`'s own end-to-end time, since matrix access there is a minority of total work next to BFS traversal. Along the way: fixed an out-of-bounds-write risk in `operator*` (wrong result-matrix shape, no current callers) and a `rows()`-vs-`cols()` bug in `swapRows()`/ `multiplyRow()` (only correct for square matrices), and marked one dead method `OBSOLETE`. All golden regression baselines pass unchanged. - **`Matrix::inverse()`/`powerIteration()` are now cancellation-aware** (WS5 A5): Information Centrality and Eigenvector Centrality could not previously be interrupted once started β€” clicking Cancel was accepted but had no effect until the whole computation finished. Both methods now check the cancel flag once per outer-loop iteration (per matrix column for the inverse, per power-iteration step) and stop early instead of running to completion. All golden regression baselines pass unchanged. - **`MainWindow` split from one 18,540-line `.cpp` into 28 files under `src/mainwindow/`** (WS7 MW0, #257): mechanical, behavior-preserving split by responsibility (network I/O, editing, analysis, layout, options, help, CLI scripting, async dispatch), mirroring WS2's earlier split of `graph.cpp`. `MainWindow` remains one class; `mainwindow.h` is unchanged. `mainwindow.cpp` itself is now 588 lines (constructor and destructor only). ## [3.6] – May 26, 2026 ### New Features - **UCINET DL export** (#237): - New **Network β†’ Export β†’ DL…** action writes the current graph to a UCINET DL file in `FULLMATRIX DIAGONAL PRESENT` format. - Multi-relation graphs are fully supported: all relations are written as consecutive matrices in the `DATA:` section under a single `NM=k` header (no file-level separator needed β€” the parser already advances its relation counter when the source row counter exceeds N). - Node labels are exported in `ROW LABELS` / `COLUMN LABELS` sections, enabling label-preserving roundtrip. - `FileType::UCINET` added to `m_graphFileFormatExportSupported`; the CLI io_roundtrip kernel now exercises the format automatically. Two existing baselines (`Bernard_Killworth_Fraternity__FT5`, `StokmanZiegler_Netherlands__FT5`) updated from `performed: false` to `performed: true, equivalent: true`. - **Edge List export β€” weighted and simple** (#238): - New **Network β†’ Export β†’ List…** action writes the active relation as a space-separated edge list file. - When the graph is weighted the user is asked whether to include weights: **Yes** β†’ `source target weight` per line (`.wlst`), **No** β†’ `source target` per line (`.lst`). - Node labels are used as identifiers; spaces in labels are replaced with underscores to keep the delimiter unambiguous. - A warning is shown when multiple relations are present (only the active relation is written). - Both `FileType::EDGELIST_WEIGHTED` and `FileType::EDGELIST_SIMPLE` added to the supported export list; three Tiny edge-list io_roundtrip baselines show `performed: true, equivalent: true`. - **GraphViz DOT export with full roundtrip** (#236): - New **File β†’ Export β†’ GraphViz DOT…** action writes a `.dot` file for the active relation (DOT has no native multi-relation concept; a warning is shown when more than one relation is present). - Export preserves: node label, color, shape, canvas coordinates (`pos="x,y"` β€” compatible with neato/fdp), and all custom node/edge attributes as DOT key=value pairs. - Edge weight, label, color, and custom attributes are written per edge. Undirected graphs suppress the reverse half of each symmetric pair. - Parser extended to read back everything that is written: `readDotProperties` now returns unknown attributes as a `QHash` rather than discarding them; `pos=` is converted to node canvas coordinates; graph name quote-stripping fixes the roundtrip for quoted identifiers (`digraph "Name" { … }`). - `FileType::GRAPHVIZ` added to the supported export list so the CLI io_roundtrip regression kernel exercises the format automatically. - **Subgraph save format expansion** (#220): - The **Save Subgraph As…** dialog (reached via **Edit β†’ Subgraphs β†’ Save visible / Save selected**) now offers all seven supported export formats: **GraphML**, **Pajek**, **Adjacency**, **GraphViz DOT**, **UCINET DL**, **Weighted Edge List**, and **Simple Edge List**. - Format-aware fidelity warnings fire automatically before writing: - Custom node/edge attributes: a confirmation prompt is shown for any format that cannot preserve them (all formats except GraphML and DOT). - Single-relation-only formats (Adjacency, DOT, both Edge List variants) show an info notice when the subgraph has more than one relation. - Previously the dialog only offered GraphML, Pajek, and Adjacency. - **Subgraph extraction** (#218): - New **Edit β†’ Subgraphs** submenu with two actions: - **Save visible nodes as subgraph…** β€” extracts all nodes currently visible on the canvas (i.e. not hidden by any filter) and the edges that run between them into an independent graph file. Always enabled when a network is loaded. - **Save selected nodes as subgraph…** β€” extracts the currently selected nodes and their inter-edges. Enabled when at least one node is selected. - Both actions prompt for a network name, pre-fill the save dialog filename from that name, and save to GraphML (which preserves all custom attributes and relations). - Vertices are renumbered from 1 in the extracted graph; all visual properties, custom node/edge attributes, and multi-relation structure are preserved. - Original graph remains unchanged (non-destructive). - **Compound AND-logic query filter** (#221): - New **Filter β†’ Filter by Query…** action (`Ctrl+X, Ctrl+B`) opens the **Query Builder** dialog β€” a dynamic multi-condition filter composer that supports both nodes and edges. - The user picks a scope (Nodes or Edges), then adds one or more condition rows. Each row specifies an attribute key, an operator (=, β‰ , >, <, β‰₯, ≀, contains), and a value. All rows are ANDed: only nodes/edges that satisfy every condition are kept visible. - Rows can be added ("+ Add condition" button) or removed (per-row "βˆ’" button; the last row's remove button disables itself automatically). The attribute key combo is populated from the graph's current node or edge attribute keys and is also freely editable. - Applies the same non-destructive snapshot/restore mechanism as all other filters: the result appears as a chip in the filter bar and can be individually removed or cleared with Restore All. - Numeric comparisons (>, <, β‰₯, ≀) automatically downcast to `double` when both sides are valid numbers; string comparisons fall back to case-insensitive lexicographic order; "contains" is always case-insensitive substring search. - **Connected components count and color-by-component layout** (#85): - **Analyze β†’ Cohesion β†’ Connectedness** now reports the number of weakly connected components when the network is disconnected, alongside a hint pointing to the new colorize action. - New **Layout β†’ Node Color by Connected Component** (`Ctrl+L, Ctrl+C, Ctrl+0`) colors every node by its weakly connected component: nodes in the same component share the same color, making isolated sub-networks immediately visible. Up to 15 visually distinct palette colors are used; the palette cycles for networks with more components. If the network is already fully connected the action reports "one component" and leaves colors unchanged. - Both directed and undirected networks are supported. Weak connectivity is used throughout: two nodes are in the same component when there is an undirected path between them (edge directions are ignored). This is consistent with Gephi, igraph, and NetworkX defaults and answers the practical "how many disconnected islands?" question for both graph types. Strong connectivity (all-pairs directed reachability) remains available via the SSSP engine. - `Graph::graphWeaklyConnectedComponents()` implements the BFS, caches the count in `m_graphWeaklyConnectedComponents` and per-node component IDs in `m_vertexComponentId`. Cache is invalidated whenever the graph is structurally modified (same trigger as distances / centralities). - **Connectivity semantics** β€” what the app computes and reports: | Graph type | Topology | Components | Connected? | UI message | |---|---|---|---|---| | Undirected | All nodes reachable | 1 | yes | "connected (1 component)" | | Undirected | Isolated sub-networks | >1 | no | "disconnected (N components)" | | Directed | Every pair has a directed path | 1 | yes | "weakly connected (1 component)" | | Directed | Aβ†’B only, not Bβ†’A | 1 | yes | "weakly connected (1 component)" β€” one island, not strongly connected | | Directed | Two separate islands | >1 | no | "disconnected (N weakly connected components)" | The "connected" determination uses weak connectivity (`components == 1`) throughout, for both directed and undirected networks. Weak connectivity ignores edge direction and answers the practical "how many disconnected islands?" question consistently. Strong connectivity (all-pairs directed reachability) is a separate, stricter property available via the geodesic distances computation. - **Clustering Coefficient added to node color layout** (#37): - **Layout β†’ Node Color by prominence index β†’ Clustering Coefficient** (`Ctrl+L, Ctrl+C, Ctrl+G`) colors nodes by their local Watts-Strogatz clustering coefficient using the same blueβ†’red gradient as all other prominence indices: nodes with higher clustering are warmer (red), lower clustering are cooler (blue). - Fully integrated at the same depth as the 12 existing centrality/prestige indices: appears in the Layout control panel combo, the analysis prominence combo, **Filter Nodes by Centrality** (filter and find by CLC threshold), and the prominence distribution chart. - `IndexType::CLC = 13` added to the index enum; `clusteringCoefficient()` now builds the `discreteCLCs` distribution map for charting. - **Zero-weight edge display settings** (#30): - New **Settings β†’ Edges β†’ Show zero-weight edges** checkbox (default: on): when unchecked, zero-weight edges are silently skipped on load β€” no ghost edges, no artifacts. The **Zero valued edge color** picker is automatically disabled when the checkbox is off. - Zero-weight edge color is now **user-configurable**: the hardcoded `"blue"` is replaced by `initEdgeColorZero`, driven by the existing **Settings β†’ Edges β†’ Zero valued edge color** picker which was already present but disconnected from `edgeCreate()`. - **Configurable edge arrow size** (#32): - New **Settings β†’ Edges β†’ Arrow size** spinbox (range 2–20, default 6) lets the user control the size of arrowheads on directed edges. - The change applies to all edges on the canvas immediately and is persisted for future sessions. ### Improvements - **SSSP parallelisation β€” major performance boost for large networks** (#241, #242): - All shortest-path computations (distances, betweenness centrality, stress centrality, closeness, eccentricity, power centrality) now run concurrently across all available CPU cores via `QtConcurrent::blockingMap`. - **Phase 1** (#241): per-source SSSP scratch state (BFS stack, predecessor lists, dependency accumulators, distance and sigma arrays, nth-order neighbourhood map) was extracted from `Graph` and `GraphVertex` into a self-contained `PerSourceScratch` struct. Replacing per-edge `QHash` lookups with contiguous `QVector` index reads improved cache locality and gave an immediate 14–23% speedup even on a single thread. - **Phase 2** (#242): the source loop in `DistanceEngine::runAllSources()` is now distributed across all CPU cores. Each worker thread owns a `ThreadLocalState` holding its own `PerSourceScratch`, partial BC/SC accumulator arrays, and running totals for graph-wide aggregates. A single-threaded reduction step after the loop merges everything into graph state with zero contention. APSP write-back is race-free without any mutex because each source vertex is processed by exactly one thread. - **Measured speedup** (Debug build, 24-core Linux): | Network | Centralities | Before | After | Speedup | |---------|-------------|--------|-------|---------| | BA directed N=500 E=1219 | ON | 679 ms | 255 ms | **2.7Γ—** | | N=1000 A=10000 | OFF | 28 423 ms | 3 431 ms | **8.3Γ—** | | N=1000 A=10000 | ON | 47 020 ms | 5 949 ms | **7.9Γ—** | Speedup scales near-linearly with core count because sources are fully independent once per-source scratch state is thread-local. - All 36 golden regression baselines pass; numeric results are bit-for-bit identical to the sequential implementation. - **Canvas rendering performance for large networks** (#180, #240): - The dominant bottleneck on large networks (2000+ nodes, 40000+ edges) was `QGraphicsScene::BspTreeIndex` (the previous default): every `prepareGeometryChange()` call β€” triggered O(E) times per rubber-band drag or CTRL+A β€” paid an O(log N) BSP tree update. Result: selection and drag were nearly unresponsive on large networks. - **Scene index now defaults to `NoIndex`**, eliminating BSP overhead entirely. Rubber-band drag, CTRL+A, and full-network moves are now fast on large networks. Memory consumption is also reduced since the BSP tree structure is not built. - Existing users are migrated automatically on first launch (`settingsMigration = 2`); the setting remains user-controllable via **Settings β†’ Canvas β†’ Scene index method**. - `QPen` and `QBrush` objects in `GraphicsEdge` and `GraphicsNode` are now cached and rebuilt only on property changes, avoiding per-`paint()` heap allocations. - Nine bulk visibility-toggle methods (`setNodeLabelsVisibility`, `setEdgeArrowSize`, `setEdgeHighlighting`, etc.) now wrap their loops with `viewport()->setUpdatesEnabled(false/true)`, coalescing per-item `update()` calls into a single viewport repaint. - `scene()->items()` loops replaced with direct `edgesHash`/`nodeHash` iteration in `setEdgeOffsetFromNode` and `setAllItemsVisibility`. - Hot-path `qDebug` calls removed from `handleSelectionChanged`, `selectedNodes`, and `selectedEdges` (fired on every selection event). - **UI declutter & UX improvements** (#234): - Removed the two "Apply" buttons from the Layout section of the Control Panel. All three layout comboboxes (Prominence Index, Type, Force-Directed Model) now apply immediately on selection change, with no extra click required. - Layout Type defaults to "None" (was "Radial") so opening the panel no longer implies a radial layout is pending. - Mutual exclusion enforced between By-Prominence-Index (Radial/Level types) and Force-Directed Model: selecting a force-directed model resets the Type to None, and vice versa. - Toolbar filter actions regrouped: node-filter icons sit alongside other node actions; edge-filter icons sit alongside edge actions. - Node Properties and Edge Properties toolbar actions are now selection-aware: nothing selected β†’ status-bar hint; exactly one node/edge β†’ single-item dialog; multiple selected β†’ bulk-edit dialog. - Statistics Panel sectioned into five collapsible groups (β–Ύ/β–΄ toggle): NETWORK, SELECTION, CLICKED NODE, CLICKED EDGE, DISTRIBUTION. Each section collapses and expands independently. - In-Degree/Out-Degree rows in the Clicked Node section and Weight/Reciprocal rows in the Clicked Edge section auto-hide until a node or edge is clicked. - Left Control Panel wrapped in a scroll area: when the Data Table dock reduces available vertical space the panel scrolls rather than overlapping its widgets. - **Better feedback during long operations**: the status bar now shows a "Computing … Please wait…" message for every analysis and layout operation that can take noticeable time (centrality measures, prestige indices, matrix computations, structural equivalence, force-directed and prominence-index layouts, and more). The message is painted to the screen immediately, so users always know the app is working. ### Bug Fixes - **UCINET DL two-mode networks now load correctly** (#63): three bugs in `parser_dl.cpp` caused two-mode (NR Γ— NC affiliation) files to silently produce wrong node counts, wrong node numbers, and misrouted labels. Fixed: (1) `totalNodes` is now set to `NR + NC` when two-mode is detected; (2) row and col nodes are created with explicit sequential numbers (1..NR and NR+1..NR+NC) instead of a broken auto-numbered batch; (3) `COL LABELS:` is recognised as an alias for `COLUMN LABELS:`. Two golden IO-roundtrip baselines added (`TinyDL_TwoMode_3x3_labeled`, `TinyDL_TwoMode_3x3_nolabels`). **Note:** UCINET two-mode files always load as a bipartite directed graph in SocNetV (directed edges from mode-1 row nodes to mode-2 column nodes). Projection to person-network or event-network is not yet supported for the DL format; it is tracked as a separate feature request. - **Galaskiewicz famous network now loads correctly** (#15): the `.2sm` data file was missing from `data.qrc`, so `writeFamousNetwork()` returned early with an empty temp file β€” causing "no data rows found" for all three import modes. Added the resource entry and removed the now-dead hardcoded data rows from `graph_reports.cpp`. - **Edge mode now follows relation switches** (#53): switching to a different relation now restores that relation's directed/undirected state. Each relation tracks its own flag in `m_relationsDirected`; `relationSet()` saves the departing state and restores the incoming one, recomputes `m_graphIsSymmetric` from actual edges, and emits `signalGraphDirectedChanged` so the edge-mode combo and the arrows action in the toolbar update without mutating any edges. - **Zero-weight edges: five pre-existing computation bugs fixed** (#30): - `outEdgesCount()` now skips weight-0 edges β†’ **density** no longer overcounted. - `graphReciprocity()` now skips weight-0 edges, preventing a false `edgeExists(v2,v1)==0` match against absent reverse edges that inflated the reciprocated-ties count and could produce a 0/0 arc-reciprocity ratio. - `clusteringCoefficientLocal()` now excludes weight-0 neighbours from the neighbourhood *k*, keeping the k*(kβˆ’1) denominator correct. - BFS (`bfsSSSP`) now skips weight-0 edges so they are not traversed as valid 1-hop connections (affected geodesic distances, reachability, and all BFS-derived centralities). - Dijkstra (`dijkstraSSSP`) now skips weight-0 edges, preventing free paths (`dist_w = dist_u + 0`) and a division-by-zero crash when `inverseWeights` is enabled (`1/0 = ∞`). - **Canvas: Shift+click now adds a node to the current selection** (#235): Shift+left-clicking a node toggles it into or out of the selection without clearing previously selected nodes. Qt's item-level mouse handler in `RubberBandDrag` mode only treats `Ctrl` as additive; the fix intercepts `Shift+LeftButton` in `GraphicsWidget::mousePressEvent` and calls `node->setSelected(!node->isSelected())` directly, bypassing Qt's default clear-and-select path. Shift+click on empty canvas space (rubber- band extension) and plain click behaviour are unchanged. - **DOT parser: self-contained `graph [...]` block skips next node** (#236 follow-up): when our exporter writes `graph [label="Name"];` on a single line, the parser was entering multi-line netProperties mode and then treating the very next node declaration as the block terminator, silently discarding it. The skipped node was re-created with its DOT identifier as label ("n1" instead of "1") when the first edge statement was processed, producing wrong edge endpoints in the reloaded graph. Fix: only enter multi-line mode when `]` is absent from the `graph [` line. All three TinyGraphviz DOT io_roundtrip tests now show `ROUNDTRIP_EQUIV=1`. - **Multi-relation graph incorrectly reported as non-weighted** (#236 follow-up): `isWeighted()` correctly scans only the current relation (fixed in #82). However, the io_roundtrip regression kernel queried `isWeighted()` after iterating all relations and restoring to relation 0, so a graph whose relation 0 is binary but relation 1 is weighted was reported as `weighted: false`. Added `Graph::isAnyRelationWeighted()`, which checks every relation, and updated the kernel to use it for the `graph.weighted` JSON field. - **Keyboard shortcut conflicts resolved**: - `Ctrl+T` (Data Table toggle) conflicted with the `Ctrl+T, Ctrl+*` prefix used by all four Structural Equivalence analysis actions β€” those sequences were silently unreachable on every platform. Data Table is now `Ctrl+D`. - `Ctrl+X, Ctrl+S` (Focus on Selection filter, WS9) duplicated the pre-existing `Ctrl+X, Ctrl+S` shortcut for "Selected nodes β†’ Star" subgraph operation. Focus on Selection is now `Ctrl+X, Ctrl+O`. - `Ctrl+X, Ctrl+Q` (Query Builder filter) mapped to `⌘+Q` on macOS β€” the system-level Quit β€” when used as the second chord of a sequence. Query Builder is now `Ctrl+X, Ctrl+B`. - Duplicate `Ctrl+Shift+Left/Right` shortcuts removed from the rotate toolbar buttons (the menu actions already own those sequences). - **Bezier curve edges now work** (#149): the **Options β†’ Edges β†’ Bezier Curves** toggle was present but non-functional. Fixed and fully implemented: edges now draw as smooth quadratic arcs, arrowheads follow the curve tangent at each endpoint (including on reciprocated edges), the lens-shaped fill artifact is gone, new edges drawn while the toggle is on are born as curves, and the setting persists across sessions. ## [3.5] – May 8, 2026 ### New Features - **Graph exploration filters** (WS9): - Focus on Node (Ego Network): hides all nodes except the selected node and its direct neighbors, and all non-incident edges. Available in the Filter menu and node right-click context menu (#211). - Focus on Selection: hides all nodes not in the current selection and all edges whose endpoints are not both selected. Action `Ctrl+X, Ctrl+S`; available in Filter menu and node right-click context menu (#210). - Restore All Nodes: restores all nodes hidden by any filter. Available in Filter menu and node right-click context menu. - Restore All Edges: re-enables all edges hidden by the weight filter. Action `Ctrl+E, Ctrl+R`; available in the Filter menu (#213). - All node-visibility filters (ego network, selection, centrality) now share a unified non-destructive snapshot/restore history stack β€” Restore All works across all filter types (#216). - **Ego-centered radial layout**: places a selected node at the canvas center, its 1-hop out-neighbors on an inner ring, and all remaining nodes on an outer ring. Available via Layout menu (`Ctrl+Alt+E`) and node right-click context menu (#214). - **Node/edge attribute system** (#224): - Single-key node attribute API: `Graph::vertexCustomAttributeSet()` / `vertexCustomAttributeRemove()`. - Edge custom attribute storage: `GraphVertex::m_outEdgeCustomAttributes`, `Graph::edgeCustomAttributesSet()` / `edgeCustomAttributes()`. - Edge Properties dialog (`DialogEdgeEdit`): edit label, weight, color and arbitrary custom key/value pairs; accessible from the toolbar and edge right-click context menu. - GraphML roundtrip for edge custom attributes: unique keys exported as `d2000+` `` definitions; per-edge `` tags written on save and parsed back on load. - Filter Nodes By Attribute: `Graph::vertexFilterByAttribute(key, value)` β€” non-destructive snapshot/restore filter; available in the Filter menu (`Ctrl+X, Ctrl+A`). - **Attribute-based filtering** (#217): - `FilterCondition` struct: scope (Nodes/Edges/Both), key, operator (`=` `β‰ ` `>` `<` `β‰₯` `≀` `contains`), value; `label()` for future filter bar chips. - `DialogFilterByAttribute`: scope selector, editable key combo populated from the graph's existing node/edge attribute keys, operator dropdown, free-text value field. - `Graph::vertexFilterByAttribute(FilterCondition)`: refactored to accept the full condition struct; numeric-aware comparison (tries `toDouble()`, falls back to lexicographic). - `Graph::edgeFilterByAttribute(FilterCondition)`: hides edges not matching the condition; uses the same snapshot/restore stack as node filters. - Filter combo added to the Control Panel (Network group) for one-click access to all filter actions. - Dedicated toolbar filter group with distinct icons for each filter action. - **Filter bar with chips** (#219): - Persistent `FilterBarWidget` strip between toolbar and canvas; hidden when no filter is active, auto-shows when any filter is applied. - Each active filter condition appears as a labelled chip (e.g. `Nodes: ego network`, `Edges: weight filter`, `Nodes: type = investor`). - Γ—-close on the most recently applied chip removes it and pops one entry from the snapshot/restore stack. Earlier chips show a disabled Γ— with a tooltip explaining the order constraint. - "Clear all" button drains the full node filter stack and resets the edge filter in one click. - All five filter actions emit chips: centrality, ego network, selection, attribute (Nodes/Edges/Both), and edge weight filter. - Bar stays in sync when filters are removed via menu or toolbar actions. - **Node/edge data table dock** (#225): - New `GraphTableWidget` dockable panel (Ctrl+T, Options menu and Edit menu) with two tabs β€” Nodes and Edges β€” each backed by a `QAbstractTableModel` cache. - Node tab: fixed columns (#, Label, Visible, Shape, Size, Color) plus one column per custom attribute key. Label, Size, Color and custom attribute cells are inline-editable (double-click); #, Visible, Shape are read-only and rendered with a muted background. - Edge tab: fixed columns (Source, Target, Relation, Weight, Label, Color) plus custom attrs. Weight, Label, Color and custom attribute cells are editable; Source, Target, Relation are read-only and shaded. - All edits write back to the graph immediately via the Graph API. - Live search bar filters all columns (case-insensitive); column headers are sortable; a Refresh button reloads data from the current graph. - Panel auto-refreshes on file load and graph reset when it is open. - Action now has a dedicated `data_table_48px.svg` icon. - **Structured CSV/JSON export** (#226): - `TableExport::toCSV()` and `TableExport::toJSON()` free functions (`src/graph/io/table_export.*`) β€” QtCore only, no UI dependency. - Each tab in the Data Table dock gains **Export CSV** and **Export JSON** buttons; they export the currently visible (search-filtered) rows so what you see is what you get. Tooltip makes the scope explicit. - `Network β†’ Export to other...` gains four new actions: **Nodes as CSV**, **Edges as CSV**, **Nodes as JSON**, **Edges as JSON** β€” these always export all rows regardless of any active search filter. - Status bar reports the export path on success. - **Structured CSV/JSON attribute import** (#227, refs #169): - `TableImport::fromCSV()` and `TableImport::fromJSON()` free functions (`src/graph/io/table_import.*`) β€” RFC 4180 CSV parser and JSON array-of-objects parser; QtCore only, no UI dependency. - `DialogImportAttributes`: file-browse + 8-row preview table + column-mapping controls. Nodes scope: **ID column** selector (node number or label matching). Edges scope: **Source** and **Target** column selectors with auto-detection of common names (`source`, `src`, `target`, `tgt`, `dest`). Import button is disabled until a valid file is loaded. - `Graph::vertexAttributesImport()` and `Graph::edgeAttributesImport()`: smart column routing β€” editable native columns (`Label`, `Size`, `Color` for nodes; `Weight`, `Label`, `Color` for edges) are routed to their proper setters; read-only native columns (`Visible`, `Shape`, `Relation`) are silently skipped; all other columns become custom attributes. - Each tab in the Data Table dock gains **Import CSV** and **Import JSON** buttons; the table auto-refreshes and the status bar reports the number of matched rows after import. - Enables a full lossless exportβ†’import roundtrip: re-importing an exported file produces no duplicate columns and no data loss. - **Spreadsheet-based bulk attribute editing workflow** (#232): - Emergent capability unlocked by combining #226 (export) and #227 (import): export the data table to CSV or JSON, edit it freely in any spreadsheet tool (Excel, LibreOffice, Google Sheets), and re-import to update attributes in bulk. Each node/edge can carry different values β€” unlike in-app bulk operations (#228) which assign one value to many. - Native columns updated via their proper setters on re-import; no duplicate custom-attribute columns created. ### Improvements - Force-directed layouts improved for large graphs: - Fruchterman-Reingold: pre-cached adjacency (O(1) edge lookup in inner loop), initial random placement, early convergence detection. - Kamada-Kawai: canvas clamping replaces random teleport on out-of-bounds particles. ### Bug Fixes - Fixed Kamada-Kawai crash when node filters are active. - Fixed crash on graph reset: guard edge creation in `setEdgeVisibility` when the edge has already been removed (#231). - Fixed visibility history stack not cleared on graph clear / `initApp`. - Fixed custom node attribute key/id mismatch in GraphML export (#208). - Fixed Pajek parser: use default node shape as fallback when no Pajek shape keyword is present (#179). - Fixed `DialogClusteringHierarchical` signal/slot mismatch (#194). - Fixed Node Properties dialog UX issues for custom attributes (#130). - Fixed `graphTriadCensus()` appending stale zeros on repeated runs. ### Refactoring - New `Graph::vertexOutNeighborsSet()`: returns enabled 1-hop out-neighbors in the current relation; parametric for directed/undirected use. - Renamed `vertexNeighborhoodList/Set` β†’ `vertexReciprocalNeighborsList/Set` to reflect that only reciprocal edges are considered. ### Testing / CI - New `socnetv-cli` clustering kernel v6 with golden baselines and benchmark coverage. - Aligned clustering benchmarks with CLI behavior; documented `--type` semantics. ### Build / Packaging - RPM spec fixes: conditional `Qt5Compat` BuildRequires per distro family, correct `qt6-qttools-devel` for Fedora, dropped redundant license/doc macros. ## [3.4] – March 2026 ### New Features - Two-mode sociomatrix import: correctly handles bipartite networks in parser (#15). - Faithful Eades (1984) Spring Embedder implementation (#207). - New `layoutRandomInMemory()` replaces `layoutRandom()` in force-directed pipelines (#206). ### Bug Fixes - Progress dialog / Cancel (#52): comprehensive fix across all computation paths: - Wired Cancel into centrality, prestige, reachability, walks, matrix, report, layout, clique, and subgraph computations. - Fixed cancel-then-retry regression (reset canceled flag + invalidate distance cache). - Fixed stacked progress dialogs in multi-phase computations (KK layout, matrix functions). - All `write*` report functions converted to `bool` return; MW slots guarded on cancel. - All random network generators (`Erdos-Renyi`, `Small-World`, `Scale-Free`, `Regular`, `Lattice`, `Ring-Lattice`) fixed: bool return, cancel guards, progress max corrections. - Layouts: - Fixed division-by-zero, NaN/Inf and logic errors in Kamada-Kawai layout (#198). - Fixed FR simmering temperature derivation from canvas width (#199). - Batched `setNodePos` emissions in all force-directed layouts (#205, #206). - Centrality: - Fixed eigenvector centrality isolate reset and N==0 handling (#202). - Fixed Information Centrality isolate handling and degenerate cases (#201). - Fixed wrong vertex checked for `isIsolated` in `createMatrixAdjacencyInverse()` (#190). - Fixed clustering coefficient computation for directed networks (#58). - Fixed wrong weighted flag when switching relations (#82). - Parsers / IO: - Fixed Pajek `*Matrix` header parsing for relation labels (#188). - Fixed Pajek multirelational export as `*Matrix` blocks (#184). - Fixed quoted relation name normalization in Pajek headers (#185). - Fixed inline GML node/edge block parsing (#186). - Fixed arc doubling when loading undirected DOT graphs (#187). - Fixed platform-dependent `weighted=true` from uninitialized `initEdgeWeight` in DOT parser (#189). - Fixed `Graph::setDirected()` logic bugs. - Fixed lattice network edge deduplication and progress tracking. - Fixed version comparison in update-check (component-wise instead of integer). - Fixed `#133` (see commit). ### Refactoring (WS4 – IO/Parser) - Completed WS4: IO/Parser refactor into focused modules: - Extracted edgelist, adjacency, UCINET DL, DOT, GML, Pajek, GraphML parsers into separate files. - Introduced `IGraphParseSink` explicit mutation surface and `GraphParseSinkGraph` bridge. - Switched GUI and headless load paths to sink-backed parser mutations. - Removed legacy `Parserβ†’Graph` signal wiring. - `Parser::load` and adjacency parser use `ParseConfig`. ### Toolchain / Testing - New `socnetv-cli` schema v5 `io_roundtrip` kernel for IO/parser regression protection. - Added IO roundtrip timing regression to benchmarks. - Expanded golden comparison suite with many new IO roundtrip cases and small deterministic test networks. - Added `run_io_roundtrip_shipped_datasets.sh` and `run_golden_io_roundtrip.sh` scripts. - Added UCINET FT5 io_roundtrip golden baselines. - Fixed `run_golden_compares.sh` argument parsing. - Fixed headless parser lifetime and signal race condition. ### i18n - Added `update_translations.sh` script; updated translation files. ### Build / Packaging - Debian packaging: switched to CMake build, series-aware Qt6 deps, added OpenGL/Vulkan/XKB build deps. - CMake: `.qm` files now generated via `qt_add_lrelease`. - Fixed Windows linker `/VERSION` for PE header. - Help menu now links to `socnetv.org/manual/`. - Many bugfixes, see: [GitHub Issues](https://github.com/socnetv/app/issues?q=is%3Aissue%20state%3Aclosed%20milestone%3A3.4). ## [3.3] – February 2026 - Major internal refactor: `Graph` is now a faΓ§ade/coordinator; functionality has been split into focused `src/graph/*` modules. - Extracted and stabilized DistanceEngine; added deterministic golden regression outputs and performance benchmark guardrails. - New headless regression harness `socnetv-cli` (modular kernels + schema-versioned JSON): - distance (v1), reachability (v2), walks_matrix (v3), prominence (v4) - strict JSON dump/compare mode with committed baselines. - New feature: filter vertices by centrality and prestige indices. - Fixed Pajek parsing edge cases (mixed files with overlapping *Arcs/*Edges blocks). - Fixed UCINET/DL import edge cases (line wrapping, diagonal handling). - Fixed walks computation (`walksBetween()` / walks matrix parameter issues). - Improved tie/link counting semantics on load (canonical ties + derived SNA links; density exposed in regression JSON). - UI polish: improved disabled widget styling and custom SVG checkbox/radio styling. - Cross-platform build & packaging fixes (Qt6/CMake, Debian packaging updates, openSUSE spec fixes, macOS arm64 linker fix). - Many bugfixes, see: [GitHub Issues](https://github.com/socnetv/app/issues?q=is%3Aissue%20state%3Aclosed%20milestone%3A3.3). ## [3.2] – April 2025 - Support custom attributes (metadata) in nodes (via the Node Properties dialog). - Support for node labels in adjacency matrix formatted files. - New CMake-based build system. - Updated look and behavior of Filter Edges by Weight functionality. - Many bugfixes, see: [GitHub Issues](https://github.com/socnetv/app/issues?q=is%3Aissue%20state%3Aclosed%20milestone%3A3.2). ## [3.1] – June 2023 - Version 3.1 released, our first Qt6-only version. - Improved large file loading and responsiveness with large networks (>20,000 edges). - Reduced memory footprint. - Fixed edge filtering (see issue #140). - Enhanced "Find Node by Index Score" dialog for more meaningful comparisons. - Fixed numerous bugs. - Improved usability and help messages. ## [3.0] – July 2021 - Version 3.0 released for Windows, macOS, and Linux, with improved graph calculation speed and new command-line parameters. - First version to support hardware-accelerated (OpenGL) rendering of networks. - Improved Web Crawler: - Tests for OpenSSL support in the OS and provides user hints if OpenSSL is missing. - Fixed delay between requests. - Fixed a serious bug in weighted network centrality computations (see issue #123). - Note: To run SocNetV 3.0 AppImage in Fedora 34 (which uses Wayland by default), use: `env GDK_BACKEND=x11 ./SocNetV-3.0-dev-x86_64.AppImage` - OBS repositories are working again. Fedora/openSUSE packages can be downloaded from: [OBS Repositories](https://download.opensuse.org/repositories/home:/oxy86/) ## [2.9] – June 2021 - Version 2.9 released, bugfixes - Version 3.0 development. To run 3.0-dev in Fedora, use: env GDK_BACKEND=x11 ./SocNetV-3.0-dev-x86_64.AppImage ## [2.8] – Jan 2021 - Version 2.8, with some bugfixes ## [2.7] – Dec 2020 - Version 2.6 and 2.7 release with bugfixes and new features. ## [2.5] – Feb 2019 - Version 2.5 released with new features and bugfixes. - Prominence scores distribution in reports and in-app mini chart. - Support for custom node icons (PNG, JPEG, SVG, etc). - Edge dichotomization algorithm. - High quality theme, inspired by Material Design, for uniform look and feel of SocNetV across all OSes. - Support for (double) edge weights in all formats. - Improved export to PDF and Image (with lots of new formats). - Improved web crawler. - Lattice network generator. - Improved memory consumption and faster measure computations. - Search and select multiple nodes by their numbers, labels, or prominence scores. - Many bug fixes. ## [2.4] – Feb 2018 - Version 2.4 released with many new features. - New Force-Directed Placement layout: Kamada-Kawai. - New layout type by prominence score: Node colors. - Less clutter in visualization due to reciprocated edges. These are now being drawn in a single line. - Improved memory consumption during user interaction with large networks - Improved web crawler with pattern include and exclude options - Improved Statistics Panel. - Performance options in Settings dialog - Improved UCINET format support (fullmatrix two-mode and edgelist). - New "Check for updates" procedure. - Much improved stability. See Changelog for bugs closed. ## [2.3] – Jul 2017 - Version 2.3 released with bugfixes and new features: - Dyad and Actor/Ego reciprocity - Zero-weighted edge support and zero-weighted edge color selection functionality in Settings - Bug Closed: - #28 Edges with values in [-1,0) are not visible - #29 Settings: Negative edge colour preferences break positive edge colours ## [2.2] – Jan 2017 - Version 2.2 released with major new features. - Hierarchical Clustering Analysis (HCA) - Pearson correlation coefficients - Actor Similarities - Tie profile dissimilarities - Maximal clique census - New network symmetrization methods: Strong Ties, Cocitation - Multi-relational data read and write in GraphML - GML format support - Support for EdgeLists with labels - Support for Pajek multirelational directed networks - Adjacency matrix plotting - Better reports (in HTML with JS) - Improved performance and GUI ## [2.1] – Sep 2016 - Version 2.1 released with a few new features but lots of bug fixes. This version brings a new algorithm for d-regular random network generation, and also a nice new dialog to control it. See ChangeLog for a complete log of new features and bugfixes. - Version 2.0 released with major code overhaul, new GUI layout and lots of bugfixes and improvements. The new version brings stability, great performance boost, and nice new features such as separate modes for graphs and digraphs, permanent settings/preferences functionality, edge labeling, recent files, keyboard shortcuts, etc. Also there are improvements in Force-Directed layouts, i.e. Fructherman-Reingold. See ChangeLog for a complete overview of the new features. - The SocNetV Manual is now build with Doxygen and it is available at http://socnetv.sf.net/documentation ## [1.9] – June 2015 - Version 1.9 released with lots of bugfixes and a faster matrix inverse routine using LU decomposition. Also Information Centrality is greatly improved in terms of computation speed. PageRank Prestige algorithm corrected to compute PR using the correct formula. The initial PR score of each node is now 1/N. Bugs closed: #1463069 wrong average distance when there are isolates #1365037 certain sparse matrices crash socnetv on invertMatrix method #1365582 centralityInformation() is slow when network N>100 #1463095 edge filter works but the user cannot undo #1464422 wrong pagerank results #1464430 socnetv refuses to read pajek files not starting with *Network #1465774 edges do not always follow relations #1463082 edge color change is not taking place #1464418 socnetv crashes on pagerank computation on isolated nodes - Version 1.8 released with the following new features: New clique census routine to compute maximal cliques with up to 4 vertices. New Scale-free random generation methods. Improved Erdos-Renyi generation to include G(n,M) model. Fixed bug in Clustering Coefficient - SocNetV now computes CluCof correctly in all cases. New improved dialogs for easy random network generation (Scale-free, Erdos-Renyi, and Small-World) Fixed bug in Node Properties dialog. It is now populated with current node settings. ## [1.7] – May 2015 - Version 1.7 released. New node group select/edit functionality and file previewer supporting different codecs - Version 1.6 released. New and improved web crawler functionality. See Changelog for more. ## [1.5] – Oct 2014 - Version 1.5 released. First version with dijkstra algorithm for the SSSP in weighted nets. See Changelog for more. ## [1.4] – Sep 2014 - Version 1.4 released. Brought new layout type (nodal size by prominence index), edgelist1 UCINET format import method and many bugfixes. ## [1.3] – Aug 2014 - Version 1.3 released. - First time SocNetV works with multigraphs ## [1.2] – Aug 2014 - Version 1.2 released. It features a major GUI overhaul and brings in a new "prominence indices" conceptualization based on Wasserman & Faust. In general, Centrality indices focus on outLinks (choices given) while Prestige indices consider inLinks (choices received). Added 3 Prestige indices (Degree, Proximity and PageRank), new reachability measures (Walks, Connectedness, and Reachability Matrix) and fixed a slew of bugs in indices calculation. All algorithms are now tested to report 100% correct results. - Version 1.1 released with major bug fixes. See ChangeLog. - First time distribution of a disk image for installation in Mac OS X ## [1.0] – Feb 2014 - Version 1.0 released, starting a new 1.x series based on Qt5. The 0.x series is no longer maintained. Please upgrade :) - PageRank calculation and layout - SRS Documentation by Vagelis Motesnitsalis ## July 2013 - Moved project code to git/BB - Started development for Qt5 ## Oct 2010 - Version 0.90 released - New Power & Information Centralities ## Jan 2010 - Version 0.80 - New List import feature - New Triad Census feature - Various Bug Fixes ## June 2009 - Version 0.70 - First web crawler implementation ## May 2009 - Version 0.6 (release) - GraphML becomes native SocNetV load format ## Feb 2009 - Version 0.51 (bugfix release) - Version 0.50 (released) - Small world creation - Clustering coefficient - Exporting to PDF - Printing works OK. ## Jan 2009 - Version 0.49 (released) - Ubuntu repository created. ## Sep 2008 - New logo - New openSUSE package repo. - Version 0.48 released - Version 0.47 released - Version 0.46 released. Lots of bugfixes. New features: - Node sizes may reflect degree. ## Aug 2008 - New Debian Package - Version 0.45 released. New features: - GraphML initial support. - New man page and updated online documentation. - HtmlViewer renders online help with the help of QtWebKit (openSUSE: libQtWebKit-devel) - New widget for network rotation. - New widget for zooming replaces the old one. - Nodes may have 4 different shapes: circles, diamonds, triangles, boxes and ellipses are supported. - There was a bug in Qt 4.3 QGraphicsView causing redraw delays. Is fixed in Qt 4.4 :) - Cosmetic changes, i.e. new icons, new layout for the left dock. - Code clean-up in MainWindows Class and Matrix. - Deleted obsolete members and functions such as nodeExists(), mousePosGW(), Dijkstra, etc. - Bug-fixes on loading Pajek networks and layout algorithm. ## May 2008 - Version 0.44 released one year after v.0.43. New features: Ported to Qt4: Code rewritten almost from scratch. Splitted MainWindow/GUI from algorithms via a new Graph Class. Improved GUI with docks. Network zooming via mouse wheel. Spring Embedder: Dynamic network reallocation Thread support. Much faster calculation of distances and centralities (BFS/dijkstra). Betweenness centrality now is much more efficiently calculated. Changed license to GPL3 Layout in circles and levels by centrality. Better graphics and antialiasing (disabled - enable by pressing F8). New centrality index: Eccentricity. ## Sep 2006 - version 0.43 released with new layout features. ## June 2006 - version 0.42 released with updated help files. ## May 2006 - Did some work on the webpages at http://socnetv.org. Hope it is better now. ## March 2006 - version 0.41 released. ## February 2006 - version 0.40 released. Efforts to be a pretty trustworthy release. - sourceforge project downloads are more than enough daily, but there is no feedback yet for versions 0.38 and 0.39. - version 0.39 released. Somewhat rushed release. - constant changes in the homepage. - updated links in www.insna.org ## January 2006 - version 0.38 released after one year of silence. - The project moved to sourceforge.net - The homepage is https://socnetv.orgSocNetV-3.7/CMakeLists.txt000066400000000000000000000516671524402210300154540ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # # CMakeLists.txt - Build configuration for SocNetV (Social Network Visualizer) # # Last Update: Apr 2026 # # This file defines the build system for SocNetV, an open-source social network # analysis and visualization application built with Qt6. It is designed to support # cross-platform builds on Linux, macOS, and Windows. Below is an overview of its # key components and functionality: # # 1. **Project Setup**: # - Sets the project name, version, and description. # - Configures the C++17 standard as required. # - Includes `GNUInstallDirs` for standardized installation paths. # # 2. **Source and Resource Management**: # - Defines lists for source files, UI forms, and resources (e.g., icons). # - Automatically handles Qt-specific features like `AUTOMOC`, `AUTOUIC`, and `AUTORCC`. # - Copies the license file to the build directory during configuration. # # 3. **Platform-Specific Configurations**: # - Sets platform-specific binary names and properties: # - **Windows**: Configures the executable as a GUI application and embeds metadata. # - **macOS**: Creates a macOS app bundle with proper metadata, icons, and `PkgInfo`. # - **Linux**: Installs binaries, icons, desktop entry, man pages, and documentation # to standard filesystem locations. # # 4. **Qt Integration**: # - Locates required Qt6 modules (e.g., Core, Widgets, OpenGLWidgets, Charts). # - Links the application with necessary Qt libraries. # # 5. **Compiler and Build Options**: # - Adds compiler warnings for GCC, Clang, and MSVC to ensure code quality. # - Configures include directories for header files. # # 6. **Installation and Packaging**: # - Defines installation rules for binaries, icons, desktop files, and documentation. # - Ensures compatibility with platform-specific installation standards. # # 7. **Debugging and Finalization**: # - Prints debug information during the configuration phase. # - Finalizes the Qt-specific setup for the executable. # cmake_minimum_required(VERSION 3.16) # 3.21 or 3.22 for better features and compatibility with modern tools. option(BUILD_CLI "Build headless regression CLI" ON) option(BUILD_MATRIX_BENCH "Build standalone APSP storage benchmark tool (WS5 A2.0)" OFF) # ============================================================================== # 1. Project Setup # ============================================================================== # Set our project name and version: set(APP_NAME SocNetV) set(APP_NAME_LOWER socnetv) project(SocNetV VERSION 3.7 DESCRIPTION "SocNetV: Open-source social network analysis application based on Qt." HOMEPAGE_URL "https://socnetv.org" LANGUAGES CXX ) # Set the C++ standard set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) # Some options here (UNUSED for now...): option(DEV_MODE "Build in developer mode" OFF) # Include GNUInstallDirs for standardized install locations # Defines a set of variables for installation directories: # CMAKE_INSTALL_PREFIX Root directory for installation /usr/local or C:/Program Files/ # CMAKE_INSTALL_BINDIR Directory for executable binaries bin # CMAKE_INSTALL_LIBDIR Directory for libraries lib or lib64 # CMAKE_INSTALL_INCLUDEDIR Directory for header files include # CMAKE_INSTALL_DATAROOTDIR Root directory for read-only data share # CMAKE_INSTALL_SYSCONFDIR Directory for configuration files etc include(GNUInstallDirs) # Define platform-specific binary names if(WIN32) set(TARGET_NAME SocNetV) elseif(APPLE) set(TARGET_NAME SocNetV) else() set(TARGET_NAME socnetv) endif() # ============================================================================== # 2. Source and Resource Management # ============================================================================== # Copy and rename our COPYING license file (Configure Time) configure_file(${CMAKE_SOURCE_DIR}/COPYING ${CMAKE_BINARY_DIR}/LICENSE.txt COPYONLY) set(ENGINE_SOURCES src/engine/graph_distance_progress_sink.cpp src/engine/distance_engine.cpp ) set(GRAPH_SOURCES src/graph.cpp src/graph/util/graph_type_strings.cpp src/graph/core/graph_structure_metrics.cpp src/graph/core/graph_state_flags.cpp src/graph/core/graph_metadata.cpp src/graph/storage/graph_vertices.cpp src/graph/storage/graph_edges.cpp src/graph/storage/graph_subgraphs.cpp src/graph/io/graph_io.cpp src/graph/io/graph_parse_sink_graph.cpp src/graph/io/table_export.cpp src/graph/io/table_import.cpp src/graph/relations/graph_relations.cpp src/graph/filters/graph_edge_filters.cpp src/graph/filters/graph_node_filters.cpp src/graph/filters/graph_query_filters.cpp src/graph/ui/graph_ui_facade.cpp src/graph/ui/graph_ui_prominence_distribution.cpp src/graph/ui/graph_canvas.cpp src/graph/ui/graph_selection.cpp src/graph/ui/graph_vertex_style.cpp src/graph/ui/graph_edge_style.cpp src/graph/distances/graph_distance_facade.cpp src/graph/distances/graph_distance_cache.cpp src/graph/centrality/graph_centrality.cpp src/graph/centrality/graph_centrality_katz.cpp src/graph/centrality/graph_centrality_bonacich.cpp src/graph/centrality/graph_prestige.cpp src/graph/prominence/graph_prominence_distribution.cpp src/graph/matrices/graph_matrix_adjacency.cpp src/graph/matrices/graph_matrix_distances.cpp src/graph/matrices/graph_matrix_reachability.cpp src/graph/generators/graph_random_networks.cpp src/graph/crawler/graph_crawler.cpp src/graph/layouts/graph_layouts_basic.cpp src/graph/layouts/graph_layouts_force.cpp src/graph/reachability/graph_reachability_walks.cpp src/graph/cohesion/graph_cliques.cpp src/graph/cohesion/graph_connectivity.cpp src/graph/clustering/graph_triad_census.cpp src/graph/clustering/graph_clustering_coefficients.cpp src/graph/clustering/graph_clustering_hierarchical.cpp src/graph/similarity/graph_similarity_matrices.cpp src/graph/reporting/graph_reports.cpp src/graph/reporting/graph_reports_settings.cpp ) set(MAINWINDOW_SOURCES src/mainwindow.cpp src/mainwindow/lifecycle/mainwindow_lifecycle.cpp src/mainwindow/init/mainwindow_init_settings.cpp src/mainwindow/init/mainwindow_init_actions.cpp src/mainwindow/init/mainwindow_init_menu.cpp src/mainwindow/init/mainwindow_init_panels.cpp src/mainwindow/init/mainwindow_init_app.cpp src/mainwindow/network/mainwindow_network_file.cpp src/mainwindow/network/mainwindow_network_import_export.cpp src/mainwindow/network/mainwindow_network_random.cpp src/mainwindow/network/mainwindow_network_web.cpp src/mainwindow/network/mainwindow_network_view.cpp src/mainwindow/edit/mainwindow_edit_node.cpp src/mainwindow/edit/mainwindow_edit_edge.cpp src/mainwindow/edit/mainwindow_edit_selection.cpp src/mainwindow/edit/mainwindow_edit_relation.cpp src/mainwindow/edit/mainwindow_edit_filters.cpp src/mainwindow/analyze/mainwindow_analyze_centrality.cpp src/mainwindow/analyze/mainwindow_analyze_distance.cpp src/mainwindow/analyze/mainwindow_analyze_matrices.cpp src/mainwindow/analyze/mainwindow_analyze_cohesion.cpp src/mainwindow/analyze/mainwindow_analyze_clustering.cpp src/mainwindow/analyze/mainwindow_analyze_similarity.cpp src/mainwindow/analyze/mainwindow_analyze_prominence.cpp src/mainwindow/analyze/mainwindow_analyze_shared.cpp src/mainwindow/dispatch/mainwindow_dispatch.cpp src/mainwindow/scripting/mainwindow_interactive_script.cpp src/mainwindow/layout/mainwindow_layout.cpp src/mainwindow/options/mainwindow_options.cpp src/mainwindow/help/mainwindow_help.cpp src/mainwindow/edit/mainwindow_edit_shared.cpp ) set(PARSER_SOURCES src/parser.cpp src/parser/parser_common.cpp src/parser/parser_graphml.cpp src/parser/parser_edgelist.cpp src/parser/parser_adjacency.cpp src/parser/parser_dl.cpp src/parser/parser_dot.cpp src/parser/parser_gml.cpp src/parser/parser_pajek.cpp ) set(CLI_SOURCES src/tools/socnetv_cli.cpp src/tools/cli/cli_common.cpp src/tools/headless_graph_loader.cpp src/tools/cli/kernels/kernel_distance_v1.cpp src/tools/cli/kernels/kernel_reachability_v2.cpp src/tools/cli/kernels/kernel_walks_v3.cpp src/tools/cli/kernels/kernel_prominence_v4.cpp src/tools/cli/kernels/kernel_io_roundtrip_v5.cpp src/tools/cli/kernels/kernel_clustering_v6.cpp src/tools/cli/kernels/kernel_connectivity_v7.cpp src/tools/cli/kernels/kernel_matrix_v8.cpp src/tools/cli/kernels/kernel_vertex_connectivity_v9.cpp ) # Define sources and headers set (FORMS src/forms/dialogfilteredgesbyweight.ui src/forms/dialogfilternodesbycentrality.ui src/forms/dialogsettings.ui src/forms/dialogsysteminfo.ui src/forms/dialogwebcrawler.ui src/forms/dialogdatasetselect.ui src/forms/dialograndsmallworld.ui src/forms/dialograndscalefree.ui src/forms/dialogranderdosrenyi.ui src/forms/dialograndregular.ui src/forms/dialograndlattice.ui src/forms/dialogsimilaritypearson.ui src/forms/dialogsimilaritymatches.ui src/forms/dialogdissimilarities.ui src/forms/dialogclusteringhierarchical.ui src/forms/dialognodeedit.ui src/forms/dialogedgeedit.ui src/forms/dialognodefind.ui src/forms/dialogfilterbyattribute.ui src/forms/dialogbulkedit.ui src/forms/dialogedgedichotomization.ui src/forms/dialogexportpdf.ui src/forms/dialogexportimage.ui src/forms/dialogcentralitykatz.ui src/forms/dialogcentralitybonacich.ui # Other UI files ) set(SOURCES src/main.cpp src/texteditor.cpp ${ENGINE_SOURCES} ${GRAPH_SOURCES} ${MAINWINDOW_SOURCES} ${PARSER_SOURCES} src/graphvertex.cpp src/matrix.cpp src/webcrawler.cpp src/chart.cpp src/graphicswidget.cpp src/graphicsedge.cpp src/graphicsedgeweight.cpp src/graphicsedgelabel.cpp src/graphicsguide.cpp src/graphicsnode.cpp src/graphicsnodelabel.cpp src/graphicsnodenumber.cpp src/forms/dialogfilternodesbycentrality.cpp src/forms/dialogfilteredgesbyweight.cpp src/forms/dialogfilterbyattribute.cpp src/forms/dialogquerybuilder.cpp src/forms/dialogimportattributes.cpp src/widgets/filterbarwidget.cpp src/widgets/nodetablemodel.cpp src/widgets/edgetablemodel.cpp src/widgets/graphtablewidget.cpp src/forms/dialogedgedichotomization.cpp src/forms/dialogwebcrawler.cpp src/forms/dialogdatasetselect.cpp src/forms/dialogpreviewfile.cpp src/forms/dialognodeedit.cpp src/forms/dialogedgeedit.cpp src/forms/dialogbulkedit.cpp src/forms/dialogranderdosrenyi.cpp src/forms/dialograndsmallworld.cpp src/forms/dialograndregular.cpp src/forms/dialograndscalefree.cpp src/forms/dialogsettings.cpp src/forms/dialogsimilaritypearson.cpp src/forms/dialogsimilaritymatches.cpp src/forms/dialogdissimilarities.cpp src/forms/dialogclusteringhierarchical.cpp src/forms/dialograndlattice.cpp src/forms/dialognodefind.cpp src/forms/dialogexportpdf.cpp src/forms/dialogexportimage.cpp src/forms/dialogsysteminfo.cpp src/forms/dialogcentralitykatz.cpp src/forms/dialogcentralitybonacich.cpp # Add remaining source files here ) set(RESOURCES src/images.qrc src/data.qrc ) # List source files message(STATUS "Source files to be compiled: ${SOURCES}") # ============================================================================== # 3. Platform-Specific Configurations # ============================================================================== # Set icons set(ICON_FILE src/images/${APP_NAME_LOWER}.png) if (APPLE) set(ICON_FILE src/images/${APP_NAME_LOWER}.icns) endif() if (WIN32) set(ICON_FILE src/images/${APP_NAME_LOWER}.ico) endif() # ============================================================================== # 4. Qt Integration # ============================================================================== # Find Qt6 packages find_package(Qt6 REQUIRED COMPONENTS Core OpenGLWidgets Gui Core5Compat Widgets PrintSupport Network Charts Svg Xml LinguistTools Concurrent) # find_package_handle_standard_args(Qt6 DEFAULT_MSG Qt6_FOUND) # Check if Qt6 was found if (NOT Qt6_FOUND) message(FATAL_ERROR "Qt6 not found. Please install Qt6 and set the CMAKE_PREFIX_PATH.") endif() set(SOCNETV_TS ${CMAKE_SOURCE_DIR}/translations/socnetv_de.ts ${CMAKE_SOURCE_DIR}/translations/socnetv_es.ts ) # The MANUAL_FINALIZATION option allows for additional # configuration before finalization qt_add_executable(${TARGET_NAME} MANUAL_FINALIZATION ${SOURCES} ${FORMS} ${RESOURCES} ${ICON_FILE} ) qt_add_lrelease(${TARGET_NAME} TS_FILES ${SOCNETV_TS} QM_FILES_OUTPUT_VARIABLE SOCNETV_QM ) # These are generated during the build set_source_files_properties(${SOCNETV_QM} PROPERTIES GENERATED TRUE) # Add include directories for headers (equivalent to INCLUDEPATH) target_include_directories(${TARGET_NAME} PUBLIC "${CMAKE_SOURCE_DIR}/src" ) # ============================================================================== # 5. Compiler and Build Options # ============================================================================== # Set default target properties set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON AUTOMOC ON AUTOUIC ON AUTORCC ON ) # Add compiler options if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") target_compile_options(${TARGET_NAME} PRIVATE -Wall -Wextra -Wpedantic # WS14: qCDebug(category) << ... (the standard Qt streaming-log idiom, used by every # converted call site) passes zero arguments to qCDebug's variadic macro parameter. # That's only standard-legal in C++20; on our C++17 standard it relies on the # GNU/Clang comma-swallowing extension, which -Wpedantic flags. Functionally correct # (this is how lcGW/lcEngine already worked before this flag was added) and would # otherwise be thousands of warnings once every qDebug() call site is converted. # GCC silently ignores -Wno-* flags it doesn't recognize, so this is safe to share # across both compilers in this branch. -Wno-variadic-macro-arguments-omitted) elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") target_compile_options(${TARGET_NAME} PRIVATE /W4) endif() # Link libraries target_link_libraries(${TARGET_NAME} PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::OpenGLWidgets Qt6::Core5Compat Qt6::PrintSupport Qt6::Network Qt6::Charts Qt6::Svg Qt6::Xml Qt6::Concurrent ) # ============================================================================== # 6. Installation and Packaging # ============================================================================== # Add platform-specific properties if(WIN32) # Add Windows-specific metadata and icon set_target_properties(${TARGET_NAME} PROPERTIES WIN32_EXECUTABLE TRUE VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION} ) # Embed version and metadata into the binary using a resource file target_sources(${TARGET_NAME} PRIVATE src/icon.rc) # Optionally copy the target to a specific directory after build install(TARGETS ${TARGET_NAME} RUNTIME DESTINATION release/ # Install to release/ folder ) install(FILES ${SOCNETV_QM} DESTINATION release/ ) elseif(APPLE AND NOT IOS) set_source_files_properties("${CMAKE_SOURCE_DIR}/src/images/socnetv.icns" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") target_sources(${TARGET_NAME} PRIVATE "${CMAKE_SOURCE_DIR}/src/images/socnetv.icns") set_target_properties(${TARGET_NAME} PROPERTIES MACOSX_BUNDLE TRUE MACOSX_BUNDLE_BUNDLE_NAME ${TARGET_NAME} MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_GUI_IDENTIFIER "org.socnetv.app" MACOSX_BUNDLE_COPYRIGHT "Β© 2024 Dimitris Kalamaras. Licensed under GNU GPL v3." MACOSX_BUNDLE_ICON_FILE "socnetv.icns" ) # Ensure PkgInfo is explicitly created in the app bundle add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND echo -n "APPL????" > "${CMAKE_BINARY_DIR}/${TARGET_NAME}.app/Contents/PkgInfo" COMMENT "πŸ” Ensuring PkgInfo exists in the macOS app bundle" ) # Install the main app bundle install(TARGETS ${TARGET_NAME} BUNDLE DESTINATION "${CMAKE_INSTALL_PREFIX}/Applications" ) foreach(qm ${SOCNETV_QM}) set_source_files_properties(${qm} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/translations" ) endforeach() target_sources(${TARGET_NAME} PRIVATE ${SOCNETV_QM}) elseif(UNIX AND NOT APPLE) # Set the installation paths and files install(TARGETS ${TARGET_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" # Binaries go to /usr/bin # LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" # ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) # Install the pixmap (icon file) install(FILES src/images/socnetv.png DESTINATION "${CMAKE_INSTALL_DATADIR}/pixmaps" ) # Install the desktop entry file install(FILES socnetv.desktop DESTINATION "${CMAKE_INSTALL_DATADIR}/applications" ) # Install the man page install(FILES man/socnetv.1 DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/man/man1" ) # Install the AppStream metadata file install(FILES socnetv.appdata.xml DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo" ) # Install translations install(FILES ${SOCNETV_QM} DESTINATION "${CMAKE_INSTALL_DATADIR}/socnetv" ) # Install documentation install(FILES README.md TODO AUTHORS DESTINATION "${CMAKE_INSTALL_DATADIR}/doc/socnetv" ) # Install license install(FILES COPYING DESTINATION "${CMAKE_INSTALL_DATADIR}/doc/socnetv" ) # Install upstream changelog with Debian-friendly name install(FILES CHANGELOG.md DESTINATION "${CMAKE_INSTALL_DATADIR}/doc/socnetv" RENAME changelog ) endif() # ============================================================================== # 7. Debugging and Finalization # ============================================================================== # Print debug information message(STATUS "Building ${PROJECT_NAME} (${TARGET_NAME}) ${PROJECT_VERSION}") # NOTE: To develop/build in Visual Studio Code, do these steps: # 1. Install the extensions: Qt Extension Pack and CMake Tools # 2. Open Command Palette and select "Open User Settings". # 3. Search for "cmake.configureSettings", click on "Edit in settings.json" and in the file add a line with your 'CMAKE_PREFIX_PATH'. # i.e. "CMAKE_PREFIX_PATH": "/home//Qt/6.8.3/gcc_64" message(STATUS "CMAKE_PREFIX_PATH: " ${CMAKE_PREFIX_PATH}) message(STATUS "CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}") message(STATUS "CMAKE_SOURCE_DIR: " ${CMAKE_SOURCE_DIR}) message(STATUS "CMAKE_CURRENT_LIST_DIR: " ${CMAKE_CURRENT_LIST_DIR}) message(STATUS "CMAKE_RUNTIME_OUTPUT_DIRECTORY: " ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) message(STATUS "CMAKE_MODULE_PATH: " ${CMAKE_MODULE_PATH}) message(STATUS "CMAKE_INSTALL_DATADIR: " ${CMAKE_INSTALL_DATADIR}) # The root directory for the installation. # Defaults to /usr/local on Unix systems and C:/Program Files/ on Windows. # You can override this with -DCMAKE_INSTALL_PREFIX=. message(STATUS "CMAKE_INSTALL_PREFIX: " ${CMAKE_INSTALL_PREFIX}) if(BUILD_CLI) set(CLI_TARGET socnetv-cli) qt_add_executable(${CLI_TARGET} MANUAL_FINALIZATION ${CLI_SOURCES} ${ENGINE_SOURCES} ${GRAPH_SOURCES} ${PARSER_SOURCES} src/graphvertex.cpp src/matrix.cpp src/webcrawler.cpp ) target_include_directories(${CLI_TARGET} PUBLIC "${CMAKE_SOURCE_DIR}/src" ) target_link_libraries(${CLI_TARGET} PRIVATE Qt6::Core Qt6::Core5Compat Qt6::Network Qt6::Xml Qt6::Widgets Qt6::Gui Qt6::Charts Qt6::Concurrent ) set_target_properties(${CLI_TARGET} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON AUTOMOC ON AUTOUIC OFF AUTORCC OFF ) qt_finalize_executable(${CLI_TARGET}) endif() if(BUILD_MATRIX_BENCH) set(MATRIX_BENCH_TARGET matrix-storage-bench) qt_add_executable(${MATRIX_BENCH_TARGET} MANUAL_FINALIZATION src/tools/matrix_storage_bench.cpp src/matrix.cpp ) target_include_directories(${MATRIX_BENCH_TARGET} PUBLIC "${CMAKE_SOURCE_DIR}/src" ) target_link_libraries(${MATRIX_BENCH_TARGET} PRIVATE Qt6::Core ) set_target_properties(${MATRIX_BENCH_TARGET} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON AUTOMOC OFF AUTOUIC OFF AUTORCC OFF ) qt_finalize_executable(${MATRIX_BENCH_TARGET}) endif() # Finalize the executable qt_finalize_executable(${TARGET_NAME}) SocNetV-3.7/COPYING000066400000000000000000001032741524402210300137370ustar00rootroot00000000000000GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . SocNetV-3.7/INSTALL000066400000000000000000000073441524402210300137360ustar00rootroot00000000000000Installation ============ SocNetV is multi-platform, which means that it can be installed and run in every Operating System supported by the Qt toolkit. The project offers binaries and installers for the three major Operating Systems: Windows, MacOS and Linux. If there is no binary package for your OS, please download and compile the source code, as explained further below. Installers & Binaries You can download an installer or a binary package for your Operating System from the project's Downloads page: https://socnetv.org/downloads Follow the instructions below to install it in your system. Install in Windows To install SocNetV in Windows, download the latest version from the Downloads page, and double-click on the SocNetV installer executable. Click Next and Accept the License (GPL) to install the program. Afterwards you can find the application in Start > Programs. Install in MacOS To install SocNetV in Mac, download the latest SocNetV MacOS installer from the Downloads page, and double-click on the executable to start the installation. The application will be installed in your Applications. Please note that the first time you run SocNetV, you may need to double click on the SocNetV application icon holding down the META key. Install in Linux To run the latest version of SocNetV in Linux, download the latest Linux AppImage from the project's Downloads page. Make the file executable and double-click on it to run SocNetV. Note, however, that your system needs to have libfuse2 installed. On latest Ubuntu releases, you need to instal it with: sudo apt install libfuse2t64 Please note that a version of SocNetV is available in the repositories of most Linux distributions. However that is not always the most recent and updated version. To install the latest and greatest SocNetV version, users of openSUSE, Fedora and Ubuntu/Debian are advised to add our own repositories to their systems. In Debian and Ubuntu, install SocNetV from our repos with these commands: sudo add-apt-repository ppa:dimitris-kalamaras/ppa sudo apt-get update sudo apt-get install socnetv In Fedora and openSUSE, choose and add the correct repository from here: https://software.opensuse.org/download.html?project=home%3Aoxy86&package=socnetv Once you add the repo, install SocNetV using the command (Fedora): sudo yum install socnetv or (openSUSE): sudo zypper in socnetv Compile from source code To compile and install SocNetV from source you need the Qt toolkit development libraries, version 6. Qt is an open source C++ toolkit, for Windows, Linux and MacOS. Windows and MacOS users should download and install Qt6 from https://www.qt.io/developers Linux users need to install the following packages: openSUSE: libqt6-qtbase, libqt6-qtbase-devel, libQt6Charts6-devel, qt6-tools Fedora: qt6-qtbase, qt6-qtbase-devel, qt6-qtcharts-devel, qt6-linguist, qt6-qt5compat Debian: qt6-base-dev, qt6-base-dev-tools, qt6-charts-dev, qt6-svg-dev, qt6-5compat-dev, libqt6opengl6-dev Once you have Qt installed, you are ready to compile SocNetV from source. Download the archive with the source code of the latest version from https://github.com/socnetv/app/releases/latest. You will get a compressed file like app-3.0.tar.gz Then type in the following commands in order to decompress the SocNetV tarball and build it. Replace 3.X with the version you downloaded. 1) untar zxfv app-3.X.tar.gz (or use 7unzip in Windows/Mac) 2) cd app-3.X 3) qmake 4) make 5) make install # only for linux users, to install socnetv in /usr/local/bin Probably you have already done the first 2 steps, so just type in 'qmake' or 'qmake-qt6'. When you finish compiling and installing, run the application typing: socnetv Questions ========= For any questions, email us at: info@socnetv.org SocNetV-3.7/README.md000066400000000000000000000347621524402210300141700ustar00rootroot00000000000000# SocNetV [![version](https://img.shields.io/github/release/socnetv/app.svg?logo=c%2B%2B)](https://github.com/socnetv/app/releases) [![Build Status GH CI](https://github.com/socnetv/app/actions/workflows/build-ci.yml/badge.svg)](https://github.com/socnetv/app/actions/workflows/build-ci.yml) [![Build Status GH Release](https://github.com/socnetv/app/actions/workflows/build-release.yml/badge.svg)](https://github.com/socnetv/app/actions/workflows/build-release.yml) [![langs](https://img.shields.io/github/languages/top/socnetv/app.svg)](https://github.com/socnetv/app.git) [![downloads](https://img.shields.io/github/downloads/socnetv/app/total.svg?logo=github)](https://socnetv.org/downloads) [![license](https://img.shields.io/github/license/socnetv/app.svg)](https://github.com/socnetv/app/blob/master/COPYING) [![website](https://img.shields.io/website-up-down-green-red/https/socnetv.org.svg)](https://socnetv.org) [![socnetv](/src/images/socnetv.png)](https://socnetv.org) SocNetV - Social Network Visualizer --- ## 1. Overview Social Network Visualizer (SocNetV) is a cross-platform, user-friendly free software application for social network analysis and visualization. With SocNetV you can: - Draw social networks with a few clicks on a virtual canvas, load your field data from a file in a supported format (GraphML, GraphViz, EdgeList, GML, Adjacency, Edgelist, Pajek, UCINET, etc.), automatically recreate famous data sets or crawl the internet to create a social network of connected webpages. - Export graphs to any of the supported formats: **GraphML**, **Pajek**, **Adjacency Matrix**, **GraphViz DOT**, **UCINET DL**, **Edge List (weighted or simple)**. - Edit actors and ties through point-and-click, analyse graph and social network properties, produce beautiful HTML reports and embed visualization layouts to the network. [![socnetv](https://socnetv.org/data/uploads/screenshots/36/BreadBoardz_2Level_Managers_Dir_Bezier_FR.webp)](https://socnetv.org) ## 2. Features - Standard graph-theoretic and network cohesion metrics: density, diameter, geodesics, connectedness (weak or strong component count, for directed networks), eccentricity, clustering coefficient, walks, reciprocity, and more. - Vertex connectivity: the minimum number of nodes that must be removed to disconnect two chosen actors (Node Connectivity), or to disconnect the network at its weakest point (Graph Connectivity) β€” a measure of robustness to node removal. - Matrix routines: Adjacency, Laplacian, Degree, Cocitation, and more. - Advanced centrality and prestige indices: eigenvector, closeness, betweenness, information, power, Katz, Bonacich power centrality, PageRank prestige, and more. - Community detection algorithms: triad census, clique census, and more. - Structural equivalence analysis using hierarchical clustering, actor similarities, and Pearson coefficients. - Multiple layout algorithms: prominence-based (circular, nodal sizes by centrality), force-directed (Kamada-Kawai, Fruchterman-Reingold), and ego-centered radial layout. Node colors can also be assigned by connected component, making disconnected sub-networks immediately visible. - Non-destructive graph exploration filters: focus on selection, ego network, centrality threshold, attribute-based filtering (with `=`, `β‰ `, `>`, `<`, `β‰₯`, `≀`, `contains` operators), and edge weight threshold. All filters are reversible and stack non-destructively. Active filters shown in a persistent filter bar. - Subgraph extraction: save the currently visible (filtered) nodes or the currently selected nodes β€” together with their inter-edges β€” as an independent GraphML file. All visual properties, custom attributes, and multi-relation structure are preserved. - Custom node and edge attributes: add, edit and remove arbitrary key/value pairs. Fully persisted in GraphML. - Data table dock (Ctrl+T): inspect and inline-edit all node and edge properties β€” including custom attributes β€” with live search and sortable columns. - Graph format export: save the current graph (or just the active relation) to **GraphML**, **Pajek**, **Adjacency Matrix**, **GraphViz DOT**, **UCINET DL**, **Weighted Edge List**, or **Simple Edge List** via the **Network β†’ Export** menu. - Zero-weight edge support: edges with weight `0` are drawn (configurable color, set in Settings) but excluded from all SNA computations. Optionally saved to GraphML for full data fidelity. - Structured import/export: export node or edge data to CSV or JSON; re-import to update attributes in bulk (supports a full spreadsheet editing workflow). See the [manual](https://socnetv.org/manual/intro/) for details. - Random network generators: ErdΕ‘s–RΓ©nyi, Watts–Strogatz, scale-free, regular lattice, and more. - One-click recreation of well-known social network datasets such as Padgett's Florentine families. - Multirelational networks: load or build networks with multiple relations and switch between them. - Built-in web crawler for automatic network construction from URLs. - Comprehensive documentation available [online](https://socnetv.org/manual/intro/) and within the application. - Binary packages and installers for Windows, Linux and macOS. ## 3. Availability & License Official Website: Email: Author: Dimitris B. Kalamaras Blog: SocNetV is a cross-platform application developed in C++ and Qt, an open source software development platform published under the GPL. This means you can compile and run SocNetV on any Operating System supported by Qt. See available packages and installation instructions below. SocNetV is Free Software, distributed under the General Public Licence Version 3 (see the COPYING file for details). The documentation is also Free, licensed under the Free Documentation License (FDL). The application is not a "finished" product. Therefore, there is no warranty of efficiency, correctness or usability. Nevertheless, we are looking forward to help you if you experience any problems with SocNetV! See bug reporting below. ## 4. Installation SocNetV is multi-platform, which means that it can be installed and run in every Operating System supported by the Qt toolkit. The project offers binaries and installers for the three major Operating Systems: Windows, macOS and Linux. If there is no binary package for your OS, please download and compile the source code, as explained further below. ### a. Install a binary package or installer (Linux/macOS/Windows) You can download an installer or a binary package for your Operating System from the project's Downloads page: Follow the instructions below to install it in your system. > The macOS disk image, Windows installer, and Linux AppImage are built automatically via > GitHub Actions against a single pinned Qt6 version (see `.github/workflows/build-release.yml` > for the exact version currently in use). The Debian/Ubuntu PPA and the openSUSE/Fedora > repositories below are built separately, against each distribution's own Qt6 packages. #### Install in Windows To install SocNetV in Windows, download the latest SocNetV Windows installer from the [Downloads](https://socnetv.org/downloads) page, and double-click on the executable to start the installation. Note: You might see a Windows pop up about unknown software origin/publisher. Please ignore it and proceed, as we do not sign our Windows packages with a commercial code signing certificate. Click Next and Accept the License (GPL) to install the program. The program will be installed in the usual Windows Program Files directory and a new Start Menu shortcut will be created. Afterwards you can run the application from your Start menu. #### Install in macOS To install SocNetV in macOS, download the latest SocNetV macOS package from the [Downloads](https://socnetv.org/downloads) page. Then right-click on it and select Open. If the package is an installer, the installation will start immediately and the application will be installed automatically in your Applications. Otherwise, if the package is just a macOS disk image (a file with a .dmg extension), then double-click on it to open it. You will see a new window with the SocNetV executable icon inside. Right-click on it and select Open to run the application. Note: On some macOS versions, the system may warn you that it cannot verify the software publisher the first time you open it. If that happens, press Cancel (not Move to Bin!), then right-click the SocNetV app again and select Open to proceed normally. After that, to permanently install SocNetV, simply drag the SocNetV icon into your Applications folder. Alternatively, there is a SocNetV port in MacPorts (thanks to Szabolcs HorvΓ‘t!). It can be installed with `port install socnetv`. #### Install in Linux To run the latest and greatest version of SocNetV in Linux, download the latest Linux AppImage from the project's [Downloads](https://socnetv.org/downloads) page. Then, make the .AppImage file executable and double-click on it to run SocNetV. That's it! Note: Your system needs to have `libfuse2` installed for AppImages to work: - On **Ubuntu 24.04+**: `sudo apt install libfuse2t64` - On **Ubuntu 22.04 and earlier**: `sudo apt install libfuse2` > SocNetV is also available in the [repositories of most Linux distributions](https://repology.org/project/socnetv/versions). However, that is not always the most recent version. We urge you to use the AppImage of the latest version available from our website instead. Alternatively, users of openSUSE, Fedora and Ubuntu/Debian can install SocNetV from our own repositories. For Debian and Ubuntu, use the following commands to add our repository and install SocNetV: ```bash sudo add-apt-repository ppa:dimitris-kalamaras/ppa sudo apt-get update sudo apt-get install socnetv ``` In Fedora and openSUSE, choose and add the correct repository for your distro version from here: https://software.opensuse.org/download.html?project=home%3Aoxy86&package=socnetv Once you add the repo, install SocNetV using the command (Fedora): ```bash sudo yum install socnetv ``` or (openSUSE): ```bash sudo zypper in socnetv ``` ### b. Compile from Source Code To compile and install SocNetV from source you need the Qt toolkit development libraries, version 6.2 or later (tested with Qt 6.9). Qt is an open source C++ toolkit, for Windows, Linux and macOS. Windows and macOS users should download and install Qt6 from Linux users need to install the following packages: openSUSE: `libqt6-qtbase`, `libqt6-qtbase-devel`, `libQt6Charts6-devel`, `qt6-tools` Fedora: `qt6-qtbase`, `qt6-qtbase-devel`, `qt6-qtcharts-devel`, `qt6-linguist`, `qt6-qt5compat` Debian/Ubuntu: `qt6-base-dev`, `qt6-base-dev-tools`, `qt6-charts-dev`, `qt6-svg-dev`, `qt6-5compat-dev`, `libqt6opengl6-dev` Once you have Qt installed, you are ready to compile SocNetV from source. Download the archive with the source code of the latest version from . You will get a compressed file like `app-3.3.tar.gz`. #### Build with CMake (recommended) CMake is the recommended build system for SocNetV 3.7. Replace `3.X` with the version you downloaded. ```bash tar zxfv app-3.X.tar.gz cd app-3.X cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/Qt/6.x/gcc_64/lib/cmake cmake --build build ``` The executable `socnetv` will be placed inside the `build` folder. To install system-wide: ```bash sudo cmake --install build ``` #### Build with qmake (legacy) If you prefer qmake, ensure the `qmake` (or `qmake6`) command is in your PATH: ```bash tar zxfv app-3.X.tar.gz cd app-3.X qmake make sudo make install # or: su -c 'make install' ``` When you finish compiling and installing, run the application by typing: ```bash socnetv ``` or go to Start Menu > Mathematics > SocNetV. ## 5. Command Line Options SocNetV is primarily a GUI program. Nevertheless, some command line options are available: ``` Options: -h, --help Displays this help. -v, --version Displays version information. -p, --progress Force showing progress dialogs/bars during computations. --nm, --notmaximized Do not show the app maximized. -f, --fullscreen Show in full screen mode. -d, --debug Print debug messages to stdout/console. Available verbosity s: 'none', 'min' or 'full'. Default: 'min'. --encoding Load the startup file with this text encoding (e.g. 'UTF-8'), bypassing the encoding preview dialog. --interactive-script Run a plain-text script after startup, one command per line, driving the real GUI/event loop β€” useful for scripted demos, profiling, or regression testing without manual clicking. Arguments: file Network file to load on startup. You can load a network from a file using `socnetv file.net` where file.net/csv/dot/graphml must be of valid format. See README. ``` For example, type: ```bash ./socnetv net.graphml ``` to start SocNetV and immediately load network file named 'net.graphml' (in current folder). ### Headless CLI (socnetv-cli) Starting with version 3.3, SocNetV also ships a headless command-line tool, `socnetv-cli`, for batch network analysis without a graphical interface. It supports multiple analysis kernels (distances, reachability, walks, prominence centralities, connectivity, and more) and produces deterministic JSON output suitable for scripting and regression testing. This is a separate binary from the `--interactive-script` option above: `socnetv-cli` runs analysis kernels headlessly with no GUI at all, while `--interactive-script` drives the real, full `socnetv` GUI application through a scripted sequence of actions. ```bash socnetv-cli --help ``` This tool is intended for power users, automated pipelines, and developers running regression test suites. ## 6. Usage & documentation To help you work with the application, there are tooltips and What's This help messages inside the application, when running SocNetV. To see the full documentation, press F1. It will open a browser window with the SocNetV Manual, which is available online at the project's website: For a full list of changes, see our CHANGELOG.md ## 7. Bug reporting & contact If you have a bug report or a feature request, please file it in our GitHub issue tracker: https://github.com/socnetv/app/issues To contact us directly, send an email to: SocNetV-3.7/TODO000066400000000000000000000003471524402210300133710ustar00rootroot00000000000000KNOWN ISSUES ============ - Negative weights break centralities TODO ==== To request a feature and/or see the TODO list of SocNetV, open a new report at our github Issues page: https://github.com/socnetv/app/issues SocNetV-3.7/docs/000077500000000000000000000000001524402210300136255ustar00rootroot00000000000000SocNetV-3.7/docs/ARCHITECTURAL_REFACTORING_ROADMAP.md000066400000000000000000000155661524402210300211440ustar00rootroot00000000000000# ARCHITECTURAL REFACTORING ROADMAP This document describes the **architectural direction of SocNetV**: where we are, where we are going, and how we get there. Detailed workstream execution plans live in: ``` docs/roadmaps/ ``` --- # Where We Are SocNetV has a layered, modular architecture: ``` UI (MainWindow + dialogs + graphics) ↓ Graph (thin faΓ§ade / coordinator) ↓ Algorithm slices / engines β”œβ”€β”€ distances / centralities β”œβ”€β”€ clustering / cohesion β”œβ”€β”€ reachability / connectivity β”œβ”€β”€ filters / subgraphs β”œβ”€β”€ layouts / generators β”œβ”€β”€ IO (Parser β†’ IGraphParseSink β†’ Graph) └── matrices ``` The `Graph` object is a faΓ§ade and state coordinator β€” not a monolith. Algorithm logic lives in dedicated slices under `src/graph/`. A headless CLI regression harness (9 kernels) guards against silent regressions. --- # Completed Workstreams | WS | Name | Status | Roadmap | |------|-------------------------------------|-------------------------------|---------| | WS1 | Algorithm extraction | βœ” complete | [`roadmap_ws1_distances_geodesic_engine.md`](roadmaps/roadmap_ws1_distances_geodesic_engine.md) | | WS2 | Graph faΓ§ade | βœ” complete | [`roadmap_ws2_ui_graph_facade.md`](roadmaps/roadmap_ws2_ui_graph_facade.md) | | WS3 | Architecture & Performance | βœ” complete (v3.6/v3.7) | [`roadmap_ws3_architecture_performance.md`](roadmaps/roadmap_ws3_architecture_performance.md) | | WS4 | IO / Parser modernization | βœ” complete | [`roadmap_ws4_io_parser_refactor.md`](roadmaps/roadmap_ws4_io_parser_refactor.md) | | WS5 | Matrices Modernization | βœ” complete (v3.7) | [`roadmap_ws5_matrices_modernization.md`](roadmaps/roadmap_ws5_matrices_modernization.md) | | WS9 | Graph exploration & data workflows | βœ” shipped v3.5/v3.6 | [`roadmap_ws9_graph_exploration.md`](roadmaps/roadmap_ws9_graph_exploration.md) | | WS14 | Logging Cost & Release-Build Hygiene| βœ” complete (v3.7, #268) | [`roadmap_ws14_logging_cost.md`](roadmaps/roadmap_ws14_logging_cost.md) | | WS16 | Report CSV Export | βœ” complete (v3.7, #113) | [`roadmap_ws16_report_csv_export.md`](roadmaps/roadmap_ws16_report_csv_export.md) | | WS7 | MainWindow Decomposition (MW0) | βœ” complete (v3.7, #257) | [`roadmap_ws7_mainwindow_decomposition.md`](roadmaps/roadmap_ws7_mainwindow_decomposition.md) | --- # Active Workstreams See "Priorities" further down for the current ranking β€” no single workstream is pinned as "the" active focus right now. ## WS6 β€” Testing / CI / Regression (SUPPORTING, ongoing) Roadmap: [`docs/roadmaps/roadmap_ws6_testing_ci_regression.md`](roadmaps/roadmap_ws6_testing_ci_regression.md) Expand golden baselines, dataset coverage, and benchmarking, supporting every other workstream. **Queued, deferred to post-v3.7**: WS6.8 β€” independently audit pre-existing golden baselines for mathematical correctness (not just regression-stability). See the WS6 roadmap doc for why and how. --- ## WS8 β€” IO Layer Stabilization Roadmap: [`docs/roadmaps/roadmap_ws8_io_layer_stabilization.md`](roadmaps/roadmap_ws8_io_layer_stabilization.md) Consolidate per-format IO dispatch behind a single `FormatHandler` registry, replacing hand-maintained per-format switch statements. --- ## WS10 β€” GraphicsWidget: Canvas Rendering & Features Roadmap: [`docs/roadmaps/roadmap_ws10_graphicswidget_overhaul.md`](roadmaps/roadmap_ws10_graphicswidget_overhaul.md) Ongoing GraphicsWidget canvas rendering and feature work, separate from WS3. --- ## WS11 β€” Algorithm Additions Roadmap: [`docs/roadmaps/roadmap_ws11_algorithm_additions.md`](roadmaps/roadmap_ws11_algorithm_additions.md) New analysis algorithms against the existing `src/graph/` slice architecture β€” centrality, cohesion, clustering, structural equivalence. --- ## WS12 β€” CLI Interactive/Scripting Mode Roadmap: [`docs/roadmaps/roadmap_ws12_cli_scripting_mode.md`](roadmaps/roadmap_ws12_cli_scripting_mode.md) Drive SocNetV from the command line without manual clicking β€” scripted demos, automation, and reproducible profiling/testing of GUI flows `socnetv-cli` can't reach. --- ## WS13 β€” Undo/Redo Roadmap: [`docs/roadmaps/roadmap_ws13_undo_redo.md`](roadmaps/roadmap_ws13_undo_redo.md) General undo/redo for graph-mutating operations, extending WS9's `GraphVisibilitySnapshot` pattern to structural mutations and attribute edits. --- ## WS15 β€” App Responsiveness Contract (Dispatch, Cancellation, Busy-Guard & Parallelization) Roadmap: [`docs/roadmaps/roadmap_ws15_cancellation_progress_unification.md`](roadmaps/roadmap_ws15_cancellation_progress_unification.md) App responsiveness contract β€” non-blocking dispatch, working cancellation, busy-guard coverage, and internal parallelization β€” checked independently per operation, so fixing one property can't be mistaken for having fixed all four. --- # Priorities 1. **WS15** β€” app responsiveness contract. P1-P3 done and live-verified; P4's parallelization audit done, implementation not started. 2. **WS6** β€” regression safety (ongoing support β€” continuously active underneath every other workstream, not "next in queue"). 3. **WS10** β€” GraphicsWidget canvas rendering & features. Phase 1 (#250), #260, and the rendering-perf regression kernel (WS6.6) all shipped; the rest of the Performance/Feature checklists remain scoped but not prioritised yet. 4. **WS8** β€” IO layer stabilization. Roadmap scoped; zero code written yet. 5. **WS11** β€” algorithm additions. Started: #7 and #272 shipped; rest of the backlog not prioritised yet. 6. **WS12** β€” CLI scripting mode. Thirty-four commands shipped across several passes since #261/#262; further commands added on demand, not prioritised as a standing backlog. 7. **WS13** β€” undo/redo. Just created; not prioritised yet, no code written. --- # Target Architecture No standing plan to split `Graph` into a separate domain-model layer β€” see `roadmap_ws3_architecture_performance.md` for why that turned out unnecessary. `Graph` stays the faΓ§ade indefinitely. Future structural change should follow WS3 M1's pattern: a specific, measured problem found first, architecture second. --- # Guiding Principles - Preserve functionality and numeric results - Preserve performance β€” no regressions - Keep changes incremental: **build β†’ run β†’ compare** - Prefer vertical slices over large rewrites - Let real usage drive abstraction boundaries - Avoid premature modularization --- # Contribution Workflow 1. Identify the relevant workstream 2. Follow its roadmap in `docs/roadmaps/` 3. Keep commits small and focused 4. After each structural change: ``` build ./scripts/run_golden_compares.sh ./scripts/run_benchmarks.sh ``` Golden baselines and benchmarks must remain stable. SocNetV-3.7/docs/README_DEVELOPER_NOTES.md000066400000000000000000000664121524402210300174720ustar00rootroot00000000000000# SocNetV Developer Notes Technical reference for contributors. For the architectural direction and workstream roadmap, see: * [`ARCHITECTURAL_REFACTORING_ROADMAP.md`](ARCHITECTURAL_REFACTORING_ROADMAP.md) Detailed workstream plans: ``` docs/roadmaps/ ``` > **Before committing any structural change:** run `./scripts/run_golden_compares.sh` (see > "Regression discipline" under Development Workflow, below, for the full script list). All golden > JSON baselines must still pass. --- # Architecture Overview SocNetV is a Qt-based desktop application for social network analysis and visualization. The architecture is layered across two threads: ``` β”Œβ”€ main thread ──────────────────────────────────────────────────┐ β”‚ β”‚ β”‚ MainWindow + dialogs (menus, panels, status bar) β”‚ β”‚ ↕ signals β”‚ β”‚ GraphicsWidget (QGraphicsView canvas) β”‚ β”‚ └─ GraphicsNode / GraphicsEdge (QGraphicsItem scene items) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↕ queued cross-thread signals (setNodePos, etc.) β”Œβ”€ graphThread ──────────────────────────────────────────────────┐ β”‚ β”‚ β”‚ Graph (faΓ§ade β€” state, invariants) β”‚ β”‚ └─ Algorithm slices (src/graph//) β”‚ β”‚ └─ UI faΓ§ade layer (src/graph/ui/ β€” charts, export) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` `Graph` runs on `graphThread`; all canvas mutation goes through queued signals to the main thread. Do **not bypass this flow** when adding new features. --- # Graph as FaΓ§ade `Graph` is a state coordinator and invariant guardian β€” not a monolith. It holds graph state and delegates all computation to algorithm slices. `graph.cpp` contains only: ``` Graph::Graph(...) Graph::~Graph() Graph::clear(...) ``` Everything else lives under `src/graph/`, organized by responsibility: ``` centrality/ clustering/ cohesion/ core/ crawler/ distances/ filters/ generators/ io/ layouts/ matrices/ prominence/ reachability/ relations/ reporting/ similarity/ storage/ ui/ util/ ``` --- # Structural Boundary Inside `src/graph/` A strict separation is enforced between computation and rendering. ## Algorithm slices - compute data only - may use QtCore - must **not** construct QtWidgets / QtCharts objects - must **not** emit UI signals directly Examples: ``` src/graph/prominence/graph_prominence_distribution.cpp src/graph/centrality/graph_centrality.cpp ``` ## UI faΓ§ade layer (`src/graph/ui/`) - constructs QtWidgets / QtCharts objects - renders visualizations - exports PNG charts - emits UI update signals to `MainWindow` Example: ``` src/graph/ui/graph_ui_prominence_distribution.cpp ``` **Must run on the GUI thread.** `src/graph/ui/` functions are plain `Graph::` methods β€” same `QObject`, same thread affinity as `Graph` itself, which runs on `graphThread`, not the GUI thread. Constructing real Qt GUI objects (e.g. `QtCharts`) off the GUI thread is undefined behaviour. Wrap the entire function body in `Graph::runOnGuiThread(std::function)` (`graph_ui_facade.cpp`) to force GUI-thread execution regardless of the calling thread β€” see the `uiProminenceDistribution{Spline,Area,Bars}()` functions for the pattern. ## Rule for New Code If you add new analytics: 1. **compute results in algorithm slices** 2. **render in the UI faΓ§ade** This separation is mandatory. --- # Distance Engine Shortest-path algorithms run through a dedicated engine: ``` src/engine/ distance_engine.cpp distance_progress_sink.h graph_distance_progress_sink.cpp per_source_scratch.h ← introduced in WS3 Phase 1 thread_local_state.h ``` The engine runs from both the GUI and the CLI regression harness. Call flow: ``` DistanceEngine::compute(computeCentralities, considerWeights, inverseWeights, dropIsolates) initRun() runAllSources() ← parallel via QtConcurrent::blockingMap (WS3 Phase 2) finalize() ``` Scratch state is layered by scope β€” never held on `Graph`/`GraphVertex` directly: | Struct | Scope | Location | |---|---|---| | `DistanceScratch` | Per run (N/E snapshots, iterators, connectivity) | `distance_engine.h` | | `PerSourceScratch` | Per source vertex β€” reused within a thread | `src/engine/per_source_scratch.h` | | `ThreadLocalState` | Per worker thread β€” owns a `PerSourceScratch` + partial BC/SC arrays | `src/engine/thread_local_state.h` | **WS3 parallelisation is complete (Phases 1 and 2 shipped).** The source loop in `DistanceEngine::runAllSources()` runs concurrently across all CPU cores via `QtConcurrent::blockingMap`. Each worker thread owns a `ThreadLocalState` holding a reused `PerSourceScratch`, partial BC/SC accumulators, and running totals for graph-wide aggregates. A single-threaded reduction step after the loop merges everything into graph state. Benchmark results (Debug build, 24-core Linux): 2.7×–8.3Γ— speedup depending on network size and whether centralities are computed. **APSP results (geodesic distances, sigma counts) live in `Graph::m_apspDist`/`m_apspSigma`** (`QHash`, row = source vertex position, column = target vertex position; read via `Graph::apspDistance()`/`apspShortestPaths()`) β€” not on `GraphVertex` (WS5 A2 migrated this off a per-vertex `QHash` for lookup speed and memory; see `roadmap_ws5_matrices_modernization.md`). Do not add new per-source mutable state to `Graph` or `GraphVertex` β€” put it in `PerSourceScratch`. --- # Matrix Storage `Matrix` (`src/matrix.h`/`.cpp`) is the general-purpose dense matrix used throughout SocNetV for adjacency, distance, similarity, and sociomatrix data (`AM`, `invAM`, `DM`, `WM`, and the other named fields on `Graph`). **Storage: 2D access on a 1D array, via a precomputed row-pointer index.** Every cell lives in one contiguous allocation (`m_data`, row-major) β€” one allocation for the whole grid, not one per row. A second, much smaller array (`m_rowPtr`) records where each row starts inside `m_data`, rebuilt once whenever the matrix is constructed or resized (`rebuildRowPtr()`). Reading or writing cell `(r,c)` is then: look up row `r`'s start address in `m_rowPtr` (one array read), step `c` cells forward from it (one pointer offset) β€” no multiplication at access time, regardless of matrix size. This matters because `m_cols` is a runtime value, not a compile-time constant: computing a cell's address as `r*m_cols+c` on every access is a genuine multiply every time, not something the compiler can fold away β€” precomputing each row's address once avoids repeating that multiply on every single cell access in the O(NΒ²)/O(NΒ³) algorithm loops that dominate `matrix.cpp`. **API:** `item()`/`setItem()`/`clearItem()` (copy-in/copy-out) are the normal read/write path used everywhere in the codebase. `operator[]` (`a[i][j]`, returns a raw `qreal*` via `m_rowPtr`) exists only because `ludcmp()`/`lubksb()`/`inverse()` need to modify cells in place with compound assignment (`a[i][j] -= x`), which a copy-in/copy-out getter/setter pair can't express β€” every other caller should use `item()`/`setItem()`. **`QHash` vs `Matrix` for per-relation lookup tables** (`Graph::m_apspDist`/`m_apspSigma`, see Distance Engine above): `Matrix` always allocates NΒ² cells regardless of reachability; `QHash` only stores entries for reachable pairs. Measured across connected/disconnected/realistic topologies: `Matrix` wins on both memory and lookup speed for any topology dominated by one giant connected component β€” which is what real networks look like. `QHash` only wins on memory for artificial graphs with many equal-sized disconnected islands, a shape that doesn't occur in practice. Default to `Matrix` for this kind of table unless a specific network's topology is known to be many disconnected islands. **A win in isolated cell-access throughput does not guarantee an end-to-end win.** Confirmed when evaluating the `Matrix` flat-buffer refactor: raw `item()`/`setItem()` throughput improved measurably in isolation, but `DistanceEngine`'s real BFS-driven compute showed no measurable end-to-end change β€” old and new code overlapped well within normal run-to-run noise. Cause: `DistanceEngine`'s BFS traversal is O(NΒ·(V+E)), matrix writes are only O(NΒ²); for typical graphs traversal work dominates, so a real win in matrix-access cost alone doesn't move the total by enough to rise above measurement noise. When evaluating a `Matrix`-layer optimization, measure the actual call site's end-to-end time, not just isolated access throughput β€” the two can disagree. --- # Parsing and I/O The parsing pipeline: ``` Parser ↓ IGraphParseSink ↓ Graph ``` Key components: ``` src/graph/io/graph_parse_sink.h src/graph/io/graph_parse_sink_graph.cpp ``` Typical mutation calls: ``` createNode(...) createNodeAtPosRandom(...) createNodeAtPosRandomWithLabel(...) createEdge(...) setRelation(...) addNewRelation(...) removeDummyNode(...) fileLoaded(...) ``` GUI and CLI share the same mutation pipeline β€” deterministic behavior is guaranteed. Legacy Qt mutation signals from `Parser` to `Graph` no longer exist; `IGraphParseSink` is the sole mutation plane. **Waiting for a load to finish (headless loaders):** block on `Graph::signalGraphLoaded` (preferred) with `Parser::finished(QString)` as a fallback β€” `finished` is lifecycle completion only, `signalGraphLoaded` is the actual "mutations are done" signal. **Format parsers** live one-per-translation-unit under `src/parser/` (`parser.cpp` is just the constructor/destructor, sink wiring, and the `load()` dispatcher): ``` src/parser/ parser_common.cpp ← shared helpers (isComment, ...) parser_edgelist.cpp parser_adjacency.cpp parser_dl.cpp (UCINET) parser_pajek.cpp parser_graphml.cpp parser_gml.cpp parser_dot.cpp (GraphViz) ``` Adding a new format: add its own `parser_.cpp`, wire it into `Parser::load()`'s dispatcher, add a golden `io_roundtrip` baseline (`socnetv-cli --kernel io_roundtrip ... --dump-json`, committed under `src/tools/baselines/io_roundtrip/`, wired into `run_golden_compares.sh`). --- # Filter Layer Non-destructive node/edge visibility filtering β€” the graph itself is never mutated, only visibility state: - `vertexFilterByEgoNetwork()`, `vertexFilterBySelection()`, `vertexFilterByAttribute()`, `vertexFilterByQuery()` - `edgeFilterByWeight()`, `edgeFilterByAttribute()`, `edgeFilterByQuery()` - `vertexFilterRestoreAll()` β€” drains the full stack back to the base graph Every filter pushes a `FilterSpec` (`src/graph/filters/filter_spec.h`) onto `m_visibilityHistory`. **Arbitrary removal**, not just undo-the-last-one: `vertexFilterRemoveAt(int stackIndex)` restores the base graph, then replays every *remaining* spec in order via `vertexFilterReplaySpec()` β€” a full dispatch table over every `FilterSpec::Type`. This only works for filter types that store enough in their spec to replay (`Attribute`, `EdgeAttribute`, `EdgeWeight`, `Query`, `EdgeQuery`); `Selection`/`Ego`/`Centrality` chips currently only support removal from the top of the stack. Attribute/structural queries are built from `FilterCondition` (`src/graph/filters/filter_condition.h` β€” scope/key/op/value, numeric-aware comparison) and `GraphQuery` (`src/graph/filters/graph_query.h` β€” a list of conditions, AND-only today). **Subgraph extraction** (non-destructive filtering's natural counterpart β€” makes a filtered view permanent): `Graph::subgraphExtract()` / `subgraphExtractFromSelection()`, both built on private `Graph::subgraphFromVertexList()` β€” renumbers vertices from 1, mirrors all relations, preserves custom attributes. **Structured table data** (node/edge table dock, CSV/JSON import/export) lives in `src/graph/io/table_export.*` / `table_import.*` (`TableExport`/`TableImport`, free functions, QtCore-only, any `QAbstractItemModel*`) plus `NodeTableModel`/`EdgeTableModel`/`GraphTableWidget` (`src/widgets/`) for the UI side. --- # Dispatching Long-Running Graph Operations (MainWindow β†’ Graph) `Graph` lives on `graphThread`, not the GUI thread β€” but a plain C++ method call always executes on the *caller's* thread regardless of the callee `QObject`'s thread affinity. Calling a slow `Graph` method directly from a `MainWindow` slot freezes the whole application for the computation's duration (confirmed: computing a weighted/inverted centrality index on a 7,343-node network showed 795-900% CPU with the window completely unresponsive to input or repaint for several minutes β€” #254). **Always dispatch long-running `Graph` operations via `MainWindow::runGraphOperationAsync(operation, waitMessage, doneMessage)`** (`mainwindow.cpp`), which posts `operation` onto `graphThread` via `QMetaObject::invokeMethod(activeGraph, ..., Qt::QueuedConnection)` and shows an indeterminate, `ApplicationModal` `QProgressDialog` for the duration β€” which also prevents the user from mutating `Graph` from the GUI thread mid-operation, since `Graph` has no internal synchronization against that. Not `QtConcurrent::run()`: `DistanceEngine::compute()` already parallelizes internally via `blockingMap` over the *global* thread pool, so a second `QtConcurrent::run()` task would contend with those worker tasks for the same pool. Two gotchas already hit with this pattern: - Close the progress dialog with `reset()`, not `close()` β€” `close()` triggers `QProgressDialog`'s internal `cancel()` path, which silently marks the (actually successful) operation as cancelled. - Any `src/graph/ui/` function invoked this way that constructs real Qt GUI objects (charts, etc.) must wrap its body in `Graph::runOnGuiThread()` β€” see "UI faΓ§ade layer" above. --- # Graph β†’ Canvas Rendering System ## Threading model `Graph` runs on a dedicated `graphThread`. All communication with `GraphicsWidget` (canvas) is via **queued signal/slot connections** across the thread boundary. ``` graphThread: Graph ↓ emit setNodePos(nodeNum, x, y) [queued β€” cross-thread] main thread: GraphicsWidget::moveNode() ↓ node->setPos(x, y) ↓ ItemPositionHasChanged β†’ adjust() on all connected edges ``` `moveNode()` is intentionally one line. Do not add logic there. ## Layout signal discipline Force-directed layouts (Eades, Fruchterman-Reingold, Kamada-Kawai) run all iterations on `graphThread` and emit `setNodePos` **once per node at the end**, not once per node per iteration. This avoids flooding the main thread's event queue. Static layouts (radial, levels, BC-based) emit exactly N signals in a single pass β€” the minimum possible without a new bulk signal type. **Do not add per-iteration `setNodePos` emissions** to any layout. ## Scene index method `QGraphicsScene` defaults to `BspTreeIndex`, which rebuilds a spatial BSP tree on every `prepareGeometryChange()` call. On large networks this is the dominant performance cost: selecting or dragging nodes triggers O(E) edge `adjust()` calls, each paying an O(log N) BSP update. **The app defaults to `NoIndex`** (set via `appSettings["canvasIndexMethod"]` at startup). This eliminates BSP overhead entirely. Hit-test cost becomes O(N) per click, which is imperceptible for network analysis workloads. The setting is user-controllable via **Settings > Canvas**. `BspTreeIndex` remains available for small, static networks where fast hit-testing matters. ## Known hot path: node selection `GraphicsNode::itemChange(ItemSelectedHasChanged)` calls `setSize()` and `setColor()`, both of which call `prepareGeometryChange()` and trigger `adjust()` on every connected edge. For N selected nodes this is O(N Γ— avg degree) geometry work, synchronous on the main thread. With `NoIndex` this is fast in practice; with `BspTreeIndex` on large networks it causes visible lag. ## Canvas performance knobs All rendering flags are user-configurable via **Settings > Canvas**: | Setting | Key | Default | |---------|-----|---------| | Scene index method | `canvasIndexMethod` | `NoIndex` | | Viewport update mode | `canvasUpdateMode` | `Full` | | Antialiasing | `antialiasing` | `true` | | Cache background | `canvasCacheBackground` | `false` | | Save painter state | `canvasPainterStateSave` | `false` | | Edge highlighting | `canvasEdgeHighlighting` | `true` | Do not hard-code scene rendering flags in `GraphicsWidget`'s constructor. Apply them via the corresponding `slotOptionsCanvas*` methods, which read from `appSettings` at startup. ## `edgesHash` keying `GraphicsWidget::edgesHash` (mapping a graph edge to its `GraphicsEdge` scene item β€” UI-layer only, unrelated to `Graph`'s own data model) is keyed by `GraphicsWidget::edgeKey()`, a `quint64` packed as `relation * 10^16 + v1 * 10^8 + v2`: a 3-digit relation field and two 8-digit node-number fields, read left-to-right like the old `"relation:v1>v2"` string it replaced. This is positional encoding, not a mixing hash β€” collisions are structurally impossible as long as each field stays under its digit budget (relation < 1000, node numbers < 100,000,000 each), enforced by `Q_ASSERT_X` in `edgeKey()`. Practical consequence: a single loaded network is capped at 99,999,999 nodes β€” not a real-world limit (parsers always assign sequential `1..N` node numbers regardless of a source file's own IDs, and a `QGraphicsView` canvas with anywhere near that many interactive items is unusable long before the cap matters). --- # Regression Safety Harness SocNetV ships a headless CLI tool for deterministic regression testing: ``` socnetv-cli ``` Documentation: [`docs/SOCNETV_CLI_REGRESSION_TOOL.md`](SOCNETV_CLI_REGRESSION_TOOL.md) Scripts: ``` scripts/run_golden_compares.sh scripts/run_benchmarks.sh scripts/run_golden_io_roundtrip.sh scripts/run_io_roundtrip_shipped_datasets.sh ``` These must pass after any structural change. --- # CLI Kernel Architecture The harness is organized around **kernel modules**. Each kernel covers a specific algorithm family and emits a versioned, deterministic JSON schema. Schemas are never modified after release. Current kernels: ``` kernel_distance_v1 β€” geodesic distances + centralities kernel_reachability_v2 β€” reachability matrix kernel_walks_v3 β€” walks matrix A^K kernel_prominence_v4 β€” all node-level centrality + prestige indices kernel_io_roundtrip_v5 β€” load β†’ export β†’ reload signature comparison kernel_clustering_v6 β€” clustering coefficient, triad census, clique census kernel_connectivity_v7 β€” weakly connected components count + per-node IDs ``` Each kernel owns its execution logic, JSON schema, and comparison logic. --- # Logging All debug-level logging uses Qt's category system (`qCDebug(category)`) β€” never plain `qDebug()`. `qDebug()` unconditionally constructs a `QDebug` object and formats every argument before the result is ever discarded; `qCDebug(category)` compiles to a check that skips argument evaluation entirely when the category is disabled (~0.4 ns/call disabled vs. 618-2,193 ns/call for `qDebug()`, depending on output destination β€” a ~1,500Γ— difference). This matters most in hot loops: `DistanceEngine`'s inner BFS/Dijkstra loops and its O(NΒ²) `finalize()` pass were, at one point, the single largest measured performance cost in the codebase, purely from unconditional `qDebug()` formatting β€” see `roadmap_ws14_logging_cost.md` for the full numbers. **Categories**: one per `src/graph//` slice directory (`lcCentrality`, `lcDistances`, `lcMatrices`, etc. β€” declared in `graph.h`, defined once in `graph.cpp`); file-local for other standalone files (`lcMainWindow`, `lcEngine` for `distance_engine.cpp`, `lcMatrix` for `matrix.cpp`); or a small shared header for files with too few calls each to justify their own category (`lcParser` for all `src/parser/` files via `parser.h`; `lcForms` for `src/forms/*.cpp` via a small `forms_logging.h`; `lcGW` shared by `GraphicsWidget` and the small canvas-item files via `graphicswidget.h`). **Release builds are quiet by default** β€” a no-flag launch disables all `*.debug` categories via `QLoggingCategory::setFilterRules()`. `-d 1`/`-d 2` re-enable them at runtime β€” this is why `QT_NO_DEBUG_OUTPUT` was never used instead: it compiles logging out entirely, with no way to turn it back on for a bug report from a shipped build. **Adding logging to a new file**: reuse the existing category for that `src/graph//` directory, or declare a new file-local one (`Q_LOGGING_CATEGORY(lcYourFile, "socnetv.yourfile")`) following the existing pattern β€” never plain `qDebug()`. --- # Doc-Comment Convention Doxygen `@brief`/`@param`/algorithm-explanation comments live on the **`.cpp` definition**, not the `.h` declaration. Headers (`graph.h`, `matrix.h`, ...) stay bare signature lists β€” this is consistent across the whole `Graph`/`Matrix` API surface, not an accident of one file. Rationale: keeps headers skimmable, and keeps the explanation physically next to the logic it describes, so a change to an algorithm and its doc comment land in the same diff instead of drifting apart across two files. Where a measure's explanation is more than "what does this parameter mean" β€” every centrality/prestige function's doc comment follows a fixed shape, in order: **Meaning** (what the measure actually captures, in plain words), **When to use** (the concrete research situation it fits), **Weights** (optional β€” only for measures that take `considerWeights`/`inverseWeights`: whether this measure is shortest-path-based, where inverting a strength-type weight is correct (a strong tie should act like a short/cheap path), or walk/matrix-based, where inverting is wrong (it would make the strongest ties count least) β€” see `graph_distance_cache.cpp`'s BC/SC/EC/CC/PC block and `graph_centrality_katz.cpp` for one example of each), **Compare to** (the other measure(s) it's most easily confused with, and how it differs β€” skip this section only if there's genuinely no close neighbor), then **Math** (the formula). See `src/graph/centrality/graph_centrality.cpp` for the reference examples. --- # AddressSanitizer (ASan) Debug Builds For chasing crashes/dangling-pointer bugs that don't show up in the golden/benchmark suite (those exercise algorithm correctness, not memory safety) - a separate ASan-instrumented build catches use-after-free, double-free, and heap-buffer-overflow bugs with an exact allocation/free stack trace, rather than a bare crash address. Found and root-caused three real, previously-undetected bugs this way during WS7 MW0 (dangling `graphicsWidget`/`scene` pointers and a `printerPDF` double-free, all in `MainWindow::closeEvent` - see `roadmap_ws7_mainwindow_decomposition.md`). **Build** (separate directory - keep it alongside the normal `build/`, never mix flags into it): ```bash cmake -S . -B build-asan \ -DCMAKE_PREFIX_PATH=/path/to/Qt/6.x/macos/lib/cmake \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \ -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" cmake --build build-asan -j$(nproc) ``` **Run headlessly** (fastest iteration loop - combine with `--interactive-script`, see `roadmap_ws12_cli_scripting_mode.md`, to drive a specific scenario deterministically): ```bash ASAN_OPTIONS="detect_leaks=0:abort_on_error=1:halt_on_error=1" \ QT_QPA_PLATFORM=offscreen \ ./build-asan/SocNetV.app/Contents/MacOS/SocNetV --interactive-script my_repro.txt ``` `detect_leaks=0` matters on this codebase specifically: `MainWindow` is heap-allocated in `main.cpp` and never explicitly deleted (the process just exits after `app.exec()` returns), so a plain leak-check run reports the entire object graph as "leaked" noise on every single run - real UAF/double-free/overflow bugs are what this build is for, not that. **Run the real GUI** (needed for anything that only reproduces through actual window-manager interaction, e.g. close/quit teardown ordering): ```bash QT_QPA_PLATFORM=cocoa ASAN_OPTIONS=abort_on_error=1 ./build-asan/SocNetV.app/Contents/MacOS/SocNetV ``` **Reading a report**: `AddressSanitizer: heap-use-after-free`/`double-free` reports include both the current (bad) access site and the original `freed by thread T0 here:`/`previously allocated by thread T0 here:` stack traces with exact file:line - go straight to those two allocation sites, no guessing needed. A `SEGV`/`DEADLYSIGNAL` report (no alloc/free trace) means the bad pointer wasn't allocated through ASan's own tracked allocator at the time of the crash - for a live-register-level root cause in that case, attach `lldb` directly (disable ASan's own signal handler first so `lldb` gets the raw fault: `ASAN_OPTIONS=handle_segv=0:handle_sigbus=0:allow_user_segv_handler=1`), then `bt`/`register read`/`disassemble --frame`/`memory read` at the fault to identify the actual receiver object and compare its address against candidate objects captured via breakpoints earlier in the same run. --- # Development Workflow ## Build CMake + Qt6 (Linux / macOS / Windows). Keep changes incremental. ## Regression discipline After any structural change: ``` ./scripts/run_golden_compares.sh ./scripts/run_benchmarks.sh ``` Golden outputs and performance must remain stable. ## Current focus See [`docs/ARCHITECTURAL_REFACTORING_ROADMAP.md`](../ARCHITECTURAL_REFACTORING_ROADMAP.md) for active workstreams, priorities, and the long-term architecture direction. ## Continuous Integration (GitHub Actions) `.github/workflows/build-ci.yml` (triggered by `[ci]`/`[gha]` in a commit message on `develop`) and `build-release.yml` (triggered by pushing a tag) both drive Qt via [`jurplel/install-qt-action`](https://github.com/jurplel/install-qt-action) (aqtinstall), which is **independent of the target distro's own Qt6** β€” Launchpad PPA builds (see below) and the OBS RPM build (`socnetv.spec`, unversioned `pkgconfig(Qt6*)` requirements) both build against whatever Qt6 the distro's own package repos provide, not anything from these workflows. Changing the `qt-version` matrix here only affects: the CI compile-compatibility smoke test, and the actual macOS DMG / Windows installer / Linux AppImage artifacts that `build-release.yml` publishes for tagged releases. `build-ci.yml` tests both **qmake** and **cmake** per OS (two separate steps, each gated only on `matrix.os`) β€” this used to be piggybacked on having two `qt-version` matrix entries (older version β†’ qmake, newer β†’ cmake), which is why the `if:` conditions on those steps look qt-version gated in git history; they no longer are, since the matrix currently carries a single version. If bumping the pinned Qt version: check `https://download.qt.io/official_releases/qt//` for open-source availability first β€” Qt's newest LTS patch releases are sometimes commercial-only for a window before reaching the open-source channel that `aqtinstall` draws from. --- # Launchpad PPA builds ``` Ubuntu 22.04 LTS (Jammy) Ubuntu 24.04 LTS (Noble) ``` SocNetV-3.7/docs/SOCNETV_CLI_REGRESSION_TOOL.md000066400000000000000000001037551524402210300203670ustar00rootroot00000000000000# SocNetV CLI Regression Tool `socnetv-cli` is a headless regression tool for verifying algorithmic correctness during development and refactoring of SocNetV. It provides deterministic execution of compute kernels without loading any UI components, and is the backbone of the WS6 regression harness. See also: [`ARCHITECTURAL_REFACTORING_ROADMAP.md`](ARCHITECTURAL_REFACTORING_ROADMAP.md) --- # Purpose The CLI enables: * Headless dataset loading (no MainWindow / GraphicsWidget) * Deterministic kernel execution * Golden-output JSON generation * Strict regression comparison against committed baselines * Performance benchmarking (`distance` and `prominence` kernels) * CI integration (fail-fast on mismatch) This ensures refactors preserve: * numeric results * connectivity bookkeeping * centrality vectors * prestige vectors * directed/weighted semantics * matrix-based computations (walks, reachability) --- # Architecture The CLI is modular. * `socnetv_cli.cpp` β†’ faΓ§ade (argument parsing, dispatch) * `cli/cli_common.cpp` β†’ shared utilities * `cli/kernels/kernel_distance_v1.cpp` * `cli/kernels/kernel_reachability_v2.cpp` * `cli/kernels/kernel_walks_v3.cpp` * `cli/kernels/kernel_prominence_v4.cpp` * `cli/kernels/kernel_io_roundtrip_v5.cpp` * `cli/kernels/kernel_clustering_v6.cpp` * `cli/kernels/kernel_connectivity_v7.cpp` * `cli/kernels/kernel_matrix_v8.cpp` * `cli/kernels/kernel_vertex_connectivity_v9.cpp` Each kernel owns: * Its execution logic * Its JSON schema builder * Its comparison routine The CLI is a safety harness, not a new analytics engine. --- # Design Principles * No UI involvement * No graphics dependency * Deterministic vertex ordering * Deterministic float formatting (floats serialized as strings) * Schema isolation per algorithm family * Zero silent semantic modification of Graph Each kernel has its own schema version. Existing schemas are **never modified**. --- # Build The tool is built as: ``` socnetv-cli ``` It is compiled alongside the main application via CMake (`BUILD_CLI=ON`). --- # Kernels and JSON Schemas Kernels are selected with `--kernel`. See more in 'Basic usage' further below. --- ## Distance / Centrality Kernel * Kernel: `distance` (default) * JSON schema: `schema_version = 1` Protects: * DistanceEngine * Geodesic-based centralities * Connectivity semantics Defaults are: ``` -c 1 -w 0 -x 1 -k 0 ``` --- ## Reachability Kernel * Kernel: `reachability` * JSON schema: `schema_version = 2` Reachability semantics: * R(i,j) = 1 if geodesic distance is finite * R(i,j) = 0 otherwise * Diagonal convention: **R(i,i) = 1** Derived from the distance kernel. Constraints: * `--centralities` not applicable * Must use `-c 0` * `--bench` not supported --- ## Walks Matrix Kernel * Kernel: `walks_matrix` * JSON schema: `schema_version = 3` * Required option: `--walks-length K` Computes: ``` XM = A^K ``` Each element: ``` XM(i,j) = number of walks of exact length K from i to j ``` Output includes: * `walks.nodes` * `walks.matrix` * `walks.length` * `walks.total_walks` --- ## Prominence Kernel * Kernel: `prominence` * JSON schema: `schema_version = 4` Protects all node-level prominence indices. ### Centrality * DC / SDC * CC / SCC (classic closeness) * IRCC / SIRCC (influence-range closeness) * BC / SBC * SC / SSC * PC / SPC * IC / SIC * EVC / SEVC * eccentricity (+ eccentricity_inf) ### Prestige * DP / SDP (degree prestige) * PP / SPP (proximity prestige) * PRP / SPRP (PageRank) Characteristics: * Deterministic vertex ordering * Deterministic float serialization * Strict per-field comparison * No UI involvement This kernel combines: * DistanceEngine-based indices * Standalone centrality functions * Prestige functions --- ## IO Roundtrip Kernel * Kernel: `io_roundtrip` * JSON schema: `schema_version = 5` Purpose: * Protects IO + parser correctness during refactors by enforcing a strict **load β†’ (optional export) β†’ reload** contract. * Verifies canonical graph signatures rather than trusting parser counters. * Supports **multi-relational** graphs via a per-relation bundle comparison. Workflow: 1) Load input dataset (any supported format). 2) Attempt to export in the same format (only when export is supported for that file type). 3) Reload exported file. 4) Compare canonical signatures between original load and roundtrip reload: * Single relation: compares the single signature. * Multi-relational: compares a per-relation signature bundle (relation names + per-relation counts/signatures). Export support: * Two of the nine importable formats don't export yet β€” GML and TWOMODE (`Graph::saveToFile()` falls through to its `default:` case for both; see `m_graphFileFormatExportSupported` in `graph.cpp`). Every other format (GraphML, Pajek, Adjacency, GraphViz DOT, UCINET DL, both Edge List variants) does export and is exercised by this kernel's full roundtrip. For the two unsupported formats, the kernel reports a stable "skipped export" outcome and still emits v5 JSON. Key output fields (v5): * `KERNEL_DESC` β€” describes the kernel contract * `RELATIONS` β€” number of relations in the loaded graph * `SYMMETRIC` β€” 0/1 (important because some formats default to DIRECTED but are symmetric) * `ROUNDTRIP_EQUIV` β€” 0/1 if roundtrip was performed * `ROUNDTRIP_SKIPPED` β€” string reason when export is not supported (performed=false) Comparison: * `compareGoldenV5Io()` enforces stable behavior for: * performed vs skipped export * per-relation bundle structure and signatures * canonical counts derived from the Graph (ties_graph) and derived links_sna * relation-name normalization (no double quoting, etc.) --- ## Clustering Kernel * Kernel: `clustering` * JSON schema: `schema_version = 6` Protects: * local clustering coefficient (CLC) per node * network average clustering coefficient * triad census (16 MAN classes) * maximal clique counts by size Characteristics: * deterministic vertex ordering * deterministic float serialization * strict per-field comparison * no UI involvement This kernel combines: * `Graph::clusteringCoefficient(false)` * `Graph::graphTriadCensus()` * `Graph::graphCliques(QSet(), QSet(), QSet())` Notes: * `-c` is not applicable * `--bench` not supported * `-w/-x` are still encoded in baselines for consistency, even if the current clustering/clique routines are not weight-driven in the same way as distance-based kernels --- ## Connectivity Kernel * Kernel: `connectivity` * JSON schema: `schema_version = 7` Protects: * weakly or strongly connected component count * per-node component ID assignment (weak mode only) * connected/disconnected determination Connectivity semantics: | Graph type | Method | Connected when | |------------|--------|----------------| | Undirected | standard BFS | 1 component | | Directed, `--connectivity-type weak` (default) | BFS treating all arcs as undirected | 1 weak component | | Directed, `--connectivity-type strong` | Tarjan's SCC algorithm, respecting arc direction | 1 strong component | Weak connectivity answers "how many disconnected islands exist?", ignoring arc direction. Strong connectivity answers "can every node reach every other node via directed paths?" β€” a strictly finer partition (a directed path aβ†’bβ†’c is one weak component but three strong components, since `c` cannot reach `a`). `--connectivity-type` is ignored on undirected graphs, where the two notions coincide. Output fields: * `connectivity.component_count` β€” number of components * `connectivity.connected` β€” true if component_count == 1 * `connectivity.type` β€” `"connected"` (undirected), `"weak"`, or `"strong"` (directed) * `per_node[].component_id` β€” 1-based component assignment per vertex, **weak mode only** (strong mode reports a count, not per-vertex membership β€” `Graph::graphStronglyConnectedComponents()` doesn't track it, see the function's own doc comment in `graph_distance_facade.cpp`) Characteristics: * deterministic vertex ordering * topology-only (no weights, no centralities) * no UI involvement Notes: * `-c`, `-w`, `-x`, `-k` are not applicable * `--bench` not supported * `--connectivity-type weak|strong` (default `weak`); only meaningful on directed graphs * Component IDs are 1-based and assigned in BFS discovery order --- ## Matrix Kernel * Kernel: `matrix` * JSON schema: `schema_version = 8` Protects raw contents of every `Matrix`-producing `Graph` operation β€” direct coverage of `Matrix::item()`/`setItem()` indexing, independent of whatever downstream result (centrality score, distance value, clique count) happens to read that matrix. See WS6.7 in `roadmap_ws6_testing_ci_regression.md` for the motivating gap and how the dump-mode split below was decided. Seven categories dumped: * adjacency (`AM`) * adjacency inverse (`invAM`) β€” plus `invertible` (bool) * distances (`DM`) * similarity (`SCM`, simple-matching metric) * reachability (`XRM`) * walks, fixed length (`XM`) * total walks (`XSM`) β€” **skipped above N=50** (`kTotalWalksSkipThreshold`, `kernel_matrix_v8.cpp`); summing matrix powers up to N-1 measured ~9.2 minutes at N=500, so this category simply isn't emitted on larger fixtures rather than making every run pay that cost * clique co-membership (`CLQM`) β€” no size gate, stays cheap (single-digit ms) even at N=500 Output fields: * `matrices.adjacency`, `matrices.distances`, `matrices.reachability`, etc. β€” one object per category, `null`/omitted for `total_walks` above the size threshold * `matrices.adjacency_inverse.invertible` β€” bool; `false` for a singular matrix (see #269) * Each matrix object: `dump_mode` (`"full"` or `"summary"`), `rows`, `cols`, and either `data` (the full grid, small fixtures only) or `row_sums`/`col_sums`/`trace`/`sample_cells` (large fixtures) Characteristics: * deterministic vertex ordering * dump mode is size-dependent, not a flag β€” decided internally per fixture Notes: * `-w`, `-x`, `-k` control whether weights/isolate-dropping factor into the underlying computations before matrices are dumped. `-c` must be `0` β€” matrix categories don't involve centralities, and the kernel rejects a truthy value outright (default is `1`, so pass `-c 0` explicitly). * `--bench` not supported --- ## Vertex Connectivity Kernel * Kernel: `vertex_connectivity` * JSON schema: `schema_version = 9` Protects local (Menger's theorem / vertex-split max-flow) and global (pairwise-minimum) vertex connectivity β€” see #7 and `roadmap_ws11_algorithm_additions.md` for the algorithm design. Two modes, via `--conn-mode`: * `local` β€” kappa(s,t) between `--conn-source S` and `--conn-target T` (both required) * `global` (default) β€” kappa(G), the whole network's vertex connectivity Connectivity semantics β€” same weak/strong split as the Connectivity Kernel above, via `--connectivity-type weak|strong` (default `weak`, ignored on undirected graphs): | Mode | Directed, weak | Directed, strong | |------|-----------------|-------------------| | local | undirected adjacency (edges treated as bidirectional) | respects arc direction β€” kappa(s,t) can differ from kappa(t,s) | | global | minimum over unordered non-adjacent pairs | minimum over ordered non-adjacent pairs | Output fields: * `mode` β€” `"local"` or `"global"` * `connectivity_type` β€” `"weak"`, `"strong"`, or `"undirected"` * `local.source`, `local.target`, `local.status`, `local.value` (mode `local` only) β€” `status` is one of `"ok"` (`value` holds kappa(s,t), 0 means unreachable β€” a normal, valid answer), `"adjacent"` (s,t are directly connected β€” no finite cut exists, Menger's theorem requires non-adjacency, `value` is omitted), or `"invalid"` (nonexistent or equal source/target, `value` omitted) * `global.value` (mode `global` only) β€” kappa(G); 0 means the network is already disconnected Characteristics: * deterministic vertex ordering * topology-only (no weights, no centralities) * no UI involvement * global mode is O(nΒ²) local-connectivity computations in the worst case β€” fine for the small/toy baseline fixtures used here, not something to run against large benchmark datasets Notes: * `-c`, `-w`, `-x`, `-k` are not applicable * `--bench` not supported * `--conn-source`/`--conn-target` are required for `--conn-mode local`, ignored for `global` * The `status`/`value` split (rather than a single int with a sentinel like `-1`) is deliberate β€” see #271, a real bug this session caused by exactly that pattern (a sentinel silently misused as a bool/count) --- # Basic Usage ## Available Parameters `socnetv-cli` is intentionally small: a **single faΓ§ade** parses a shared set of options, then dispatches into a selected `--kernel` implementation. ### Global flags #### `-b` / `--verbose` Enables `qDebug()`/`qCDebug()` output. Without it, only warnings/criticals reach stderr β€” the default for clean regression-script output. ### Input selection #### `-i ` / `--input ` Path to the dataset file to load. * Required for all kernels unless a kernel explicitly supports synthetic generation (currently: **no**). * Relative paths are allowed. Examples: ```bash -i src/data/SmallWorld_N10_E12.graphml -i ./mygraph.paj ``` #### `-f ` / `--format ` Input file type **ID** (must match SocNetV's internal file-type enum). Common file types (from `global.h`): * `1` β†’ GRAPHML * `2` β†’ PAJEK (.paj / .net) * `3` β†’ ADJACENCY * `4` β†’ GRAPHVIZ (DOT) * `5` β†’ UCINET (DL) * `6` β†’ GML * `7` β†’ EDGELIST_WEIGHTED * `8` β†’ EDGELIST_SIMPLE * `9` β†’ TWOMODE (**not supported by CLI kernels; do not baseline**) Notes: * The CLI is strict: if you pass a mismatched `-f` for the actual file contents, parsing may fail or semantics may differ. * For IO regression work, treat `-f` as part of the baseline identity. #### `-d ` / `--delim ` Field delimiter, passed straight through to `Parser::load()`. Relevant for Adjacency and Edge List formats; default is a single space. #### `-m <0|1>` / `--two-mode <0|1>` Marks the input as a two-mode (affiliation) network. Default `0`. #### `-l <0|1>` / `--labels <0|1>` Tells the Adjacency parser the input file has row/column labels. Default `0`. --- ### Kernel selection #### `--kernel ` Selects which analysis kernel to run. Supported kernels: * `distance` (default) β€” schema v1 * `reachability` β€” schema v2 * `walks_matrix` β€” schema v3 * `prominence` β€” schema v4 * `io_roundtrip` β€” schema v5 * `clustering` β€” schema v6 * `connectivity` β€” schema v7 * `matrix` β€” schema v8 * `vertex_connectivity` β€” schema v9 Examples: ```bash --kernel distance --kernel reachability --kernel walks_matrix --kernel prominence --kernel io_roundtrip --kernel clustering --kernel connectivity --kernel matrix --kernel vertex_connectivity ``` If omitted: * `--kernel distance` is assumed. **Basic Example (Distance kernel)** ```bash ./socnetv-cli \ -i src/data/SmallWorld_N10_E12.graphml \ -f 1 ``` --- ### Run flags (shared semantics) These flags control *how the graph is interpreted* and/or *what extra results are computed*. #### `-c <0|1>` / `--centralities <0|1>` Controls whether the **distance kernel** computes geodesic-based centralities. * `1` = compute centralities (default for distance kernel baselines: usually **1**) * `0` = distances-only run (faster; smaller output) Notes: * Only meaningful for `--kernel distance`. * For other kernels, see per-kernel constraints below. #### `-w <0|1>` / `--weights <0|1>` Controls whether edge weights are considered (when weights exist). * `1` = consider weights * `0` = ignore weights (treat as unweighted) Notes: * If the dataset format has no weights (or all weights are default), `-w 1` may behave the same as `-w 0`, but **still keep it explicit in baselines**. #### `-x <0|1>` / `--inverse-weights <0|1>` Controls how weights are interpreted when `-w 1` is enabled. * `1` = treat weight as "strength" and use inverse weight as distance cost (common in SNA) * `0` = treat weight directly as distance cost Notes: * If `-w 0`, this flag should not change results, but we still keep it explicit for stable baseline naming. #### `-k <0|1>` / `--drop-isolates <0|1>` Controls whether isolate vertices are removed before computation. * `1` = drop isolates (removes isolated nodes) * `0` = keep isolates Notes: * This affects N, connectivity bookkeeping, averages, and many per-node vectors. Always encode it in baseline names. --- ### Walks kernel specific #### `--walks-length ` Required only for `--kernel walks_matrix`. Meaning: * Computes the walks matrix `A^K`, where: * `XM(i,j)` = number of walks of **exact** length `K` from i to j. Constraints: * Must be a positive integer. * Required when `--kernel walks_matrix`. * Ignored / invalid for other kernels. Example: ```bash --kernel walks_matrix --walks-length 6 ``` --- ### Output modes `socnetv-cli` runs in **one** of the following "modes": 1. normal run (prints metrics to stdout) 2. dump deterministic JSON (`--dump-json` / `-j`) 3. strict compare against a golden JSON baseline (`--compare-json` / `-p`) 4. benchmarking (`--bench`, `distance` and `prominence` kernels only) #### `--dump-json ` / `-j ` Writes the kernel's deterministic JSON output to ``. * Intended to generate new golden baselines. * Output is schema-versioned and stable. Constraints: * Not allowed together with `--compare-json` * Not allowed together with `--bench` Example: ```bash --dump-json src/tools/baselines/ErdosRenyi_N10_E10__C1_W0_IW1_DI0.json ``` #### `--compare-json ` / `-p ` Runs the selected kernel and strictly compares output to an existing JSON baseline. Behavior: * Prints per-field diffs on mismatch * Exits non-zero on mismatch (CI-safe) Constraints: * Not allowed together with `--dump-json` * Not allowed together with `--bench` Example: ```bash --compare-json src/tools/baselines/prominence/Krackhardt_Kite_N10__PROM__V4__FT2__W0_IW1_DI0.json ``` #### `--strict` `io_roundtrip`-kernel-specific modifier for `--compare-json`: promotes a timing regression (actual roundtrip time vs. the baseline's recorded time) from an advisory warning to a hard failure. Has no effect on any other kernel. --- ### Benchmarking (`distance` and `prominence` kernels only) #### `--bench ` Runs the compute step multiple times and prints timing stats: * `COMPUTE_RUNS` * `COMPUTE_MS_MIN` * `COMPUTE_MS_MEDIAN` * `COMPUTE_MS_MEAN` * `COMPUTE_MS_MAX` Constraints: * Only valid for `--kernel distance` or `--kernel prominence` * Cannot combine with `--dump-json` * Cannot combine with `--compare-json` Example: ```bash --kernel distance -c 1 -w 1 -x 1 -k 0 --bench 20 --kernel prominence --bench 20 ``` --- ## Per-kernel constraints summary To avoid accidentally producing meaningless baselines: ### `--kernel distance` (schema v1) Allowed: * `-c`, `-w`, `-x`, `-k` * `--dump-json`, `--compare-json`, `--bench` Notes: * `--bench` is also supported here and on `--kernel prominence` β€” no other kernel. ### `--kernel reachability` (schema v2) Allowed: * `-w`, `-x`, `-k` (affects the underlying distance semantics) * `--dump-json`, `--compare-json` Not applicable / required: * `-c` must be `0` (centralities not used here) * `--bench` not supported ### `--kernel walks_matrix` (schema v3) Required: * `--walks-length K` Allowed: * `-w`, `-x`, `-k` (all three are read and baked into the schema's `run` object) * `--dump-json`, `--compare-json` Not supported: * `--bench` ### `--kernel prominence` (schema v4) Allowed: * `-w`, `-x`, `-k` * `--dump-json`, `--compare-json`, `--bench` Notes: * Prominence kernel covers *many* indices; `-w/-x` materially changes several results. * One of only two kernels (with `distance`) that support `--bench`. ### `--kernel io_roundtrip` (schema v5) Allowed: * `-w`, `-x`, `-k` (depending on loader semantics) * `--dump-json`, `--compare-json` Notes: * Some formats will "skip export" deterministically; that is expected and baseline-stable. ### `--kernel clustering` (schema v6) Allowed: * `-w`, `-x`, `-k` * `--dump-json`, `--compare-json` Not applicable / required: * `-c` not used * `--bench` not supported Notes: * v6 verifies clustering coefficient outputs, triad census, and maximal clique counts by size. ### `--kernel connectivity` (schema v7) Allowed: * `--connectivity-type weak|strong` * `--dump-json`, `--compare-json` Not applicable: * `-c`, `-w`, `-x`, `-k` β€” connectivity is topology-only; these flags have no effect and should be omitted * `--bench` not supported Notes: * v7 identifies weakly (BFS, arcs treated as undirected) or strongly (Tarjan's SCC, respects arc direction) connected components, per `--connectivity-type` (default `weak`). * `--connectivity-type` is ignored on undirected graphs, where the two notions coincide. * Component IDs are 1-based and assigned in BFS discovery order β€” **weak mode only**; strong mode reports a count, not per-vertex membership. ### `--kernel matrix` (schema v8) Allowed: * `-w`, `-x`, `-k` * `--dump-json`, `--compare-json` Not applicable / required: * `-c` must be `0` β€” `kernel_matrix_v8.cpp` rejects a truthy `-c` outright (`"--centralities is not applicable to --kernel matrix"`). Since `-c`'s CLI-wide default is `1`, **always pass `-c 0` explicitly** or the run fails immediately. * `--bench` not supported Notes: * v8 dumps raw contents of seven `Matrix`-producing operations (adjacency, inverse, distances, similarity, reachability, walks, total walks) plus clique co-membership. * Dump mode (full grid vs. row/col-sum summary) is chosen internally based on fixture size, not a flag. * `total_walks` is omitted above N=50 (`kTotalWalksSkipThreshold`) β€” see the kernel section above. ### `--kernel vertex_connectivity` (schema v9) Allowed: * `--conn-mode local|global` * `--conn-source S --conn-target T` (required for `--conn-mode local`) * `--connectivity-type weak|strong` * `--dump-json`, `--compare-json` Not applicable: * `-c`, `-w`, `-x`, `-k` β€” topology-only; these flags have no effect and should be omitted * `--bench` not supported Notes: * v9 computes local kappa(s,t) (`local` mode) or global kappa(G) (`global` mode, the default) via Menger's theorem / vertex-split max-flow. * `--connectivity-type` is ignored on undirected graphs, where the two notions coincide. * Local mode's `status` field distinguishes `"ok"` (a real value, including 0 for unreachable), `"adjacent"` (no finite cut exists β€” not a numeric answer), and `"invalid"` (bad source/target). --- ## Baseline naming convention (recommended) When you dump JSON, bake the run flags into the filename (as already used in this repo): * Distance v1: `__C{0|1}_W{0|1}_IW{0|1}_DI{0|1}` * Prominence v4: `__W{0|1}_IW{0|1}_DI{0|1}` * Reachability v2 / Walks v3 / IO v5: include kernel + schema label and any required parameters (e.g. `__WALKS_K6__V3`, `__FT2__...`, etc.) * Clustering v6: `__CLUST__V6__FT{n}__W{0|1}_IW{0|1}_DI{0|1}` * Connectivity v7: `__CONN__V7__FT{n}` (no flag suffixes β€” topology-only; add `__STRONG` for `--connectivity-type strong`) * Matrix v8: `__MATRIX__V8__FT{n}__W{0|1}_IW{0|1}_DI{0|1}` * Vertex Connectivity v9: `__VCONN__V9__FT{n}` (no flag suffixes β€” topology-only; suffix with mode/pair, e.g. `__global` or `__local_1_3`) This keeps baselines self-describing and prevents "wrong flags, right file" mistakes. --- # Golden Output Dump ## Distance (schema v1) ```bash ./socnetv-cli \ -i src/data/SmallWorld_N10_E12.graphml \ -f 1 \ --dump-json src/tools/baselines/SmallWorld_N10_E12__C1_W0_IW1_DI0.json ``` Flag encoding: ``` C1 = computeCentralities=1 W0 = considerWeights=0 IW1 = inverseWeights=1 DI0 = dropIsolates=0 ``` --- ## Reachability (schema v2) ```bash ./socnetv-cli \ --kernel reachability \ -i src/data/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl \ -f 5 -c 0 -w 1 -x 1 -k 0 \ --dump-json src/tools/baselines/reachability/StokmanZiegler_Netherlands__REACH__V2.json ``` --- ## Walks Matrix (schema v3) ```bash ./socnetv-cli \ --kernel walks_matrix \ --walks-length 6 \ -i src/data/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj \ -f 2 -c 0 -w 1 -x 1 -k 0 \ --dump-json src/tools/baselines/walks/DunbarGelada_H22a__WALKS_K6__V3.json ``` --- ## Prominence (schema v4) ```bash ./socnetv-cli \ --kernel prominence \ -i src/data/Krackhardt_Kite_N10.paj \ -f 2 -w 0 -x 1 -k 0 \ --dump-json src/tools/baselines/prominence/Krackhardt_Kite_N10__PROM__V4__FT2__W0_IW1_DI0.json ``` Flag encoding: ``` W0 = considerWeights=0 FT2 = file type =2 IW1 = inverseWeights=1 DI0 = dropIsolates=0 ``` --- ## IO Roundtrip (schema v5) ```bash ./socnetv-cli \ --kernel io_roundtrip \ -i src/data/TinyGraphML_Weighted_Dir_N3.graphml \ -f 1 \ --dump-json src/tools/baselines/io_roundtrip/TinyGraphML_Weighted_Dir_N3__FT1.json ``` Notes: * Formats without export support still produce a stable v5 JSON with: * performed=false * a `ROUNDTRIP_SKIPPED` reason string * canonical load signatures Baseline directory: * `src/tools/baselines/io_roundtrip/` --- ## Clustering (schema v6) ```bash ./socnetv-cli \ --kernel clustering \ -i src/data/Krackhardt_Kite_N10.paj \ -f 2 -w 0 -x 1 -k 0 \ --dump-json src/tools/baselines/clustering/Krackhardt_Kite_N10__CLUST__V6__FT2__W0_IW1_DI0.json ``` Flag encoding: ``` W0 = considerWeights=0 FT2 = file type =2 IW1 = inverseWeights=1 DI0 = dropIsolates=0 ``` Baseline directory: ``` src/tools/baselines/clustering/ ``` --- ## Connectivity (schema v7) ```bash ./socnetv-cli \ --kernel connectivity \ -i src/data/TinyDisconnected_Undir_N6_E4.paj \ -f 2 \ --dump-json src/tools/baselines/connectivity/TinyDisconnected_Undir_N6_E4__CONN__V7__FT2.json ``` Strong mode, on a directed graph: ```bash ./socnetv-cli \ --kernel connectivity \ -i src/data/TinyArc_Dir_N2_E1.paj \ -f 2 --connectivity-type strong \ --dump-json src/tools/baselines/connectivity/TinyArc_Dir_N2_E1__CONN__V7__FT2__STRONG.json ``` Flag encoding: ``` CONN = connectivity kernel V7 = schema version 7 FT2 = file type = 2 (Pajek) STRONG = --connectivity-type strong (suffix omitted for weak, the default) ``` No weight or centrality flags β€” connectivity is topology-only. Baseline directory: ``` src/tools/baselines/connectivity/ ``` --- ## Matrix (schema v8) ```bash ./socnetv-cli \ --kernel matrix \ -i src/data/TinyPath_N3_E2.paj \ -f 2 -c 0 \ --dump-json src/tools/baselines/matrix/TinyPath_N3_E2__MATRIX__V8__FT2__W0_IW1_DI0.json ``` Flag encoding: ``` MATRIX = matrix kernel V8 = schema version 8 FT2 = file type = 2 (Pajek) W0 = considerWeights=0 IW1 = inverseWeights=1 DI0 = dropIsolates=0 ``` Baseline directory: ``` src/tools/baselines/matrix/ ``` --- ## Vertex Connectivity (schema v9) ```bash ./socnetv-cli \ --kernel vertex_connectivity \ -i src/data/TinyPath_N3_E2.paj \ -f 2 --conn-mode local --conn-source 1 --conn-target 3 \ --dump-json src/tools/baselines/vertex_connectivity/TinyPath_N3_E2__VCONN__V9__FT2__local_1_3.json ``` Global mode: ```bash ./socnetv-cli \ --kernel vertex_connectivity \ -i src/data/TinyPath_N3_E2.paj \ -f 2 --conn-mode global \ --dump-json src/tools/baselines/vertex_connectivity/TinyPath_N3_E2__VCONN__V9__FT2__global.json ``` Flag encoding: ``` VCONN = vertex_connectivity kernel V9 = schema version 9 FT2 = file type = 2 (Pajek) ``` No weight or centrality flags β€” topology-only. Global-mode baselines are deliberately Tiny*/toy datasets only (see the kernel section above for why). Baseline directory: ``` src/tools/baselines/vertex_connectivity/ ``` --- # Golden Output Compare ```bash ./socnetv-cli \ -i src/data/data_file.graphml \ -f 1 \ --kernel \ --compare-json ``` On mismatch: * Exact field differences printed * Non-zero exit code returned Schemas are strictly compared per version. --- # What Is Verified ## Distance Kernel (v1) Graph-level: * nodes * links_sna * ties_graph * directed / weighted * avg_distance * diameter * disconnected_pairs * connected Per-node: * CC / SCC * BC / SBC * SC / SSC * EC / SEC * PC / SPC * distance_sum * eccentricity --- ## Reachability Kernel (v2) * nodes * matrix (0/1) * reachable_pairs * reachable_density --- ## Walks Kernel (v3) * nodes * matrix (integer counts) * walks.length * walks.total_walks --- ## Prominence Kernel (v4) Graph-level: * nodes * links_sna * ties_graph * directed / weighted Per-node: Centrality: * DC / SDC * CC / SCC * IRCC / SIRCC * BC / SBC * SC / SSC * PC / SPC * IC / SIC * EVC / SEVC * eccentricity (+ eccentricity_inf) Prestige: * DP / SDP * PP / SPP * PRP / SPRP Floating-point values are serialized as strings. --- ## IO Roundtrip Kernel (v5) Graph-level: * nodes * relations * directed / symmetric / weighted * ties_graph (canonical, from Graph adjacency) * links_sna (derived) Roundtrip-level: * performed vs skipped export behavior (must remain stable) * `ROUNDTRIP_EQUIV` and mismatch hints when performed * per-relation signature bundle comparison for multi-relational datasets (expected relations + signatures from original load vs what reload produced) --- ## Clustering Kernel (v6) Graph-level: * nodes * links_sna * ties_graph * directed / weighted Metrics: * averageCLC * nodesWithCLC Per-node: * CLC * hasCLC Triad census: * 003 * 012 * 102 * 021D * 021U * 021C * 111D * 111U * 030T * 030C * 201 * 120D * 120U * 120C * 210 * 300 Cliques: * maximal clique counts by size * max_clique_size * total_cliques --- ## Connectivity Kernel (v7) Graph-level: * component_count β€” number of weakly or strongly connected components, per `--connectivity-type` * connected β€” true if component_count == 1 * type β€” `"connected"` (undirected), `"weak"`, or `"strong"` (directed) Per-node: * component_id β€” 1-based integer, BFS discovery order. **Weak mode only** β€” strong mode doesn't track per-vertex SCC membership, only the count. Semantics: * For undirected graphs: standard BFS component labeling; `--connectivity-type` has no effect. * Weak mode (default): BFS traverses both out-edges and in-edges simultaneously (treats arcs as undirected). Answers "how many disconnected islands exist?" independent of arc direction. * Strong mode (`--connectivity-type strong`): Tarjan's SCC algorithm, respecting arc direction. Answers "can every node reach every other node via directed paths?" β€” a strictly finer partition than weak connectivity, computed by `Graph::graphStronglyConnectedComponents()`. --- ## Matrix Kernel (v8) Graph-level: * `run.considerWeights`, `run.inverseWeights`, `run.dropIsolates` β€” the flags the underlying computations ran with Per-category (`matrices.*`), each with `dump_mode` (`"full"` or `"summary"`), `rows`, `cols`: * `adjacency` β€” raw `AM` * `adjacency_inverse` β€” raw `invAM`, plus `invertible` (bool; false for a singular matrix, #269) * `distances` β€” raw `DM` * `similarity` β€” raw `SCM` (simple-matching metric) * `reachability` β€” raw `XRM` * `walks` β€” raw `XM` (fixed length) * `total_walks` β€” raw `XSM`; omitted above N=50 (`kTotalWalksSkipThreshold`) * `clique_comembership` β€” raw `CLQM` `dump_mode` semantics: * `"full"` β€” small fixtures dump the complete NΓ—N grid (`data`) * `"summary"` β€” large/sparse fixtures dump `row_sums`, `col_sums`, `trace`, and `sample_cells` (corners + center) instead of the full grid --- ## Vertex Connectivity Kernel (v9) Graph-level: * `mode` β€” `"local"` or `"global"` * `connectivity_type` β€” `"weak"`, `"strong"`, or `"undirected"` Local mode (`local.*`): * `source`, `target` β€” the requested pair * `status` β€” `"ok"` (`value` holds kappa(s,t)), `"adjacent"` (no finite cut exists, no `value`), or `"invalid"` (bad source/target, no `value`) * `value` β€” present only when `status == "ok"`; 0 is a valid answer (target unreachable from source) Global mode (`global.*`): * `value` β€” kappa(G); 0 means the network is already disconnected Semantics: * Local: Menger's theorem via vertex-split max-flow (Edmonds-Karp). Weak mode treats edges as bidirectional; strong mode respects arc direction, so kappa(s,t) can differ from kappa(t,s). * Global: minimum local connectivity over all non-adjacent pairs (unordered for weak/undirected, ordered for strong), pruned by a minimum-degree bound (Whitney's inequality). * Complete graphs need no special case: with no non-adjacent pair to test, the degree bound itself is the answer (kappa(K_n) = n-1). --- # Micro-Benchmarking Mode (Distance and Prominence Kernels Only) The CLI provides benchmarking for `DistanceEngine`-based work, via `--kernel distance` or `--kernel prominence`. ```bash ./socnetv-cli \ -i dataset \ -f type \ -c 1 -w 1 -x 1 -k 0 \ --bench 20 ``` Outputs: ``` COMPUTE_RUNS COMPUTE_MS_MIN COMPUTE_MS_MEDIAN COMPUTE_MS_MEAN COMPUTE_MS_MAX ``` Constraints: * Cannot combine with `--dump-json` * Cannot combine with `--compare-json` * Only supported with `--kernel distance` or `--kernel prominence` --- # Automated Regression Scripts ## Golden Comparisons ``` scripts/run_golden_compares.sh ``` Validates: * Distance (v1) * Reachability (v2) * Walks (v3) * Prominence (v4) * IO Roundtrip (v5) * Clustering (v6) * Connectivity (v7) * Matrix (v8) * Vertex Connectivity (v9) Fails on any mismatch. --- ## Performance Benchmarks ``` scripts/run_benchmarks.sh ``` Validates median compute times for the `distance` and `prominence` kernels. Machine-aware baseline sets supported. --- # Baselines Distance baselines: ``` src/tools/baselines/distance/ ``` Reachability baselines: ``` src/tools/baselines/reachability/ ``` Walks baselines: ``` src/tools/baselines/walks/ ``` Prominence baselines: ``` src/tools/baselines/prominence/ ``` IO baselines: ``` src/tools/baselines/io_roundtrip/ ``` Clustering baselines: ``` src/tools/baselines/clustering/ ``` Connectivity baselines: ``` src/tools/baselines/connectivity/ ``` Matrix baselines: ``` src/tools/baselines/matrix/ ``` Vertex Connectivity baselines: ``` src/tools/baselines/vertex_connectivity/ ``` See: [`src/tools/baselines/BASELINES__README.md`](../src/tools/baselines/BASELINES__README.md) --- # Regression Discipline Rules: * Never modify existing schema structures * New kernel β†’ new schema version * Deterministic ordering always * Explicit failure on mismatch * Baselines are updated only for deliberate semantic fixes The CLI is the architectural safety harness of SocNetV. SocNetV-3.7/docs/roadmaps/000077500000000000000000000000001524402210300154335ustar00rootroot00000000000000SocNetV-3.7/docs/roadmaps/roadmap_ws10_graphicswidget_overhaul.md000066400000000000000000000237031524402210300252500ustar00rootroot00000000000000# GraphicsWidget β€” Canvas Rendering & Features (WS10) ## Goal Keep `GraphicsWidget` (`src/graphicswidget.h`/`.cpp`, the `QGraphicsView` subclass that owns the canvas β€” node/edge scene items, selection, zoom/rotation, guides) fast at large network sizes and capable of new canvas-drawing features, as an ongoing concern separate from `Graph`/`GraphVertex` domain-model work. ## Status Phase 1 (#250, a correctness/hot-path/structural cleanup pass), the #260 canvas-clear performance fix, and the rendering-performance regression kernel (WS6.6) are shipped β€” see What WS10 Delivered below. The rest of the Performance Checklist and the whole Feature Checklist remain open. ## Background This was originally tracked as a subsection of the [Architecture & Performance Roadmap (WS3)](roadmap_ws3_architecture_performance.md); split out into its own workstream (WS10) once #250 completed, since canvas/`GraphicsWidget` work is a structurally separate, ongoing concern from WS3's domain-model work (`Graph`/`GraphVertex`) β€” not a one-off cleanup with a defined end. **Scope widened beyond pure performance** to cover `GraphicsWidget` generally β€” new canvas-drawing features (e.g. #22) belong here too, not just rendering-cost reduction, since they touch the same class and the same `QGraphicsScene`/`QGraphicsView` machinery. Correctness bugs, hot-path allocation/scan reductions, and structural changes to `GraphicsWidget` were Phase 1's territory (done, see below). **What Phase 1 did not do:** none of it changed the actual paint/geometry cost of rendering a large network β€” it removed waste (redundant allocations, scans, transform applications) without touching the underlying per-item rendering model. If the canvas still feels slow on large networks, that's expected β€” the real rendering-cost work is the Performance Checklist below. New canvas-drawing capabilities (not performance-motivated) are tracked separately in the Feature Checklist. ## What WS10 Delivered ### Phase 1 β€” GraphicsWidget Performance and Code Quality Overhaul (#250) βœ… Complete Correctness fixes, hot-path allocation/scan reductions, and structural changes across `graphicswidget.cpp`, closed out with a full Doxygen pass and a dead-code sweep. Issue [#250](https://github.com/socnetv/app/issues/250) is closed. Worth remembering from this pass: - **Integer edge keys.** `createEdgeName()` built a `"relation:v1>v2"` string with several heap allocations on every edge operation. Replaced with `GraphicsWidget::edgeKey()`, a decimal-digit-packed `quint64` (`relation * 10^16 + v1 * 10^8 + v2` β€” a 3-digit relation field and two 8-digit node-number fields), chosen over a bit-packed or mixing-hash key specifically for debuggability (digits read left to right in the same order as the old string) and because positional encoding makes collisions between distinct `(relation, v1, v2)` triples structurally impossible, not just statistically unlikely β€” backed by `Q_ASSERT_X` bounds checks in `edgeKey()`. - **Rotate-button regression, found and fixed during this pass.** Guarding the rotation-slider signal chain with `QSignalBlocker` (to stop `zoomToFit()`/`reset()` double-applying the transform) broke the rotate buttons: unlike `zoomIn`/`zoomOut`, `rotateLeft`/`rotateRight` never called `changeMatrixRotation()` directly β€” the transform was only ever applied as a side effect of the now-blocked slider's `valueChanged`. Fixed by making `rotateLeft`/`rotateRight` call `changeMatrixRotation()` directly, same pattern as the zoom methods. - **`edgesHash.reserve(500000)` at startup, removed rather than resized.** A "reserve based on real counts once known" fix was considered and dropped: `Graph::signalGraphLoaded` only fires *after* all `createNode`/`createEdge` calls have already populated the hashes, so it can't size the load that's already happening. Given `QHash`'s amortized-O(1) growth, a fixed reserve only saved a handful of cheap rehashes on very large loads while costing every small/medium network (the common case) a bucket array sized for 500,000 entries at construction β€” removed outright. - **Dead code found via a systematic zero-caller sweep** (`grep -rn` per method across all of `src/`, not just the originally-flagged items): `hasNode()` and `setNodeSizeAll()` had no callers anywhere in the tree β€” both deleted outright rather than refactored. - Also: single-probe hash lookups (`contains()`+`value()` β†’ `value(key, nullptr)`) across 15+ sites, `qDebug()` β†’ `qCDebug(lcGW)` in hot paths (`wheelEvent`, `mousePressEvent`, zoom/scale handlers), guide items tracked in a maintained `QList` instead of full `scene()->items()` scans, `selectAll()` fixed to use scene coordinates instead of viewport pixels, and a full Doxygen `@brief` pass across all 78 methods plus all 13 signals (which have no `.cpp` body to document against). ### Post-Phase-1 fix β€” canvas-clear performance (#260) βœ… Complete Clearing a large network (File β†’ New, or loading a new file over one already displayed) was slow β€” confirmed 30s to over a minute on a 2000-node/40,000-edge network. Two distinct causes, both fixed: - **`scene()->clear()` destructor cascade (WS10 territory).** `GraphicsNode::~GraphicsNode()` manually `delete`d every one of its edges before self-removing, and each `GraphicsEdge::~GraphicsEdge()` then did its own individual `removeRefs()` (O(n) `std::list` scan to unlink from the *other* endpoint's edge list). Fixed by adding `GraphicsWidget::isClearing()`, set for the duration of `GraphicsWidget::clear()`; when set, both the node destructor's manual edge-deletion loop and `removeRefs()` are skipped entirely, since `QGraphicsScene::clear()` already tears down every remaining item itself. Real, but not the dominant cost. - **MainWindow signal re-entrancy (not WS10 territory, noted here only for the full picture).** Live profiling (`sample` against the actual process, driven reproducibly via the `--interactive-script` CLI flag from #261) showed the actual dominant cost was a `QComboBox::setCurrentIndex()` call in `MainWindow::slotNetworkChanged()` missing a `blockSignals()` guard β€” it re-entered `slotEditEdgeMode()`, which forced a synchronous full-canvas repaint via `statusMessage()` while the old (still fully rendered) network was still on screen. Fixed in `mainwindow.cpp`, not `graphicswidget.cpp` β€” see #260 for the full write-up. ### Rendering-performance regression coverage (WS6.6) βœ… Done (2026-07-31) The golden harness covered computation kernels (distance, prominence, clustering) but had zero coverage of the graphics layer. Fixed via WS6.6 in the testing roadmap (`roadmap_ws6_testing_ci_regression.md`, "Canvas rendering performance kernel (#240)"): five new `--interactive-script` commands (`render`, `bulk-node-size`, `bulk-edge-color`, `move`, `quit`) driving the real GUI binary under `QT_QPA_PLATFORM=offscreen`, with `scripts/run_render_perf_bench.sh` comparing timing against upper-bound thresholds. This was a deliberate deviation from the originally-sketched headless-CLI-kernel approach β€” see WS6.6 for why. This was the prerequisite for the Performance Checklist items below; they remain unstarted. ## What Remains Open ### Performance Checklist These are known, evidence-based gaps β€” not speculative wishlist items. Each is grounded in either existing documentation or code already read. They haven't been scoped to Phase-1-style depth (concrete approach + completion criteria) yet. - **Node-selection hot path is still synchronous and unbatched.** Documented in `README_DEVELOPER_NOTES.md` ("Known hot path: node selection"): `GraphicsNode::itemChange(ItemSelectedHasChanged)` calls `setSize()` and `setColor()`, both of which call `prepareGeometryChange()` and trigger `adjust()` on every connected edge. For N selected nodes this is O(N Γ— avg degree) geometry work, synchronous on the main thread, with no batching across the N nodes of a single rubber-band selection. Candidate direction: defer/coalesce geometry-change notifications across a whole selection event instead of firing per-node. - **Bulk style operations still fire one `prepareGeometryChange()` per item.** `setEdgeArrowSize()` and similar bulk setters loop over every edge calling individual per-item geometry-change/update calls rather than batching. - **Large-network interactive performance beyond `NoIndex`.** The scene-index-method fix (defaulting to `NoIndex`, per `README_DEVELOPER_NOTES.md`) addressed BSP-tree rebuild cost, but there's been no investigation of item culling / level-of-detail rendering for networks large enough that most nodes are off-screen or sub-pixel at the current zoom level β€” `QGraphicsView` still walks and paints every item in `NoIndex` mode regardless of visibility. - **`GraphicsEdge` bezier/arrow path caching cost at scale** has not been profiled independently of the fixes above β€” worth revisiting once the render-perf kernel exists to measure it properly instead of guessing. **Before picking any of these up:** if the canvas still feels slow, the most useful next step is probably naming *which* interaction feels slow (initial load/paint of a large network? panning? dragging a selection? zooming?) β€” that determines which item above is actually worth doing first, rather than guessing. ### Feature Checklist New `GraphicsWidget` capabilities, not performance-motivated. Not yet scoped to implementation-ready depth. - **#22 β€” Add text anywhere on the canvas.** A free-floating text annotation feature, independent of node/edge labels β€” e.g. for titling a network view or annotating a region. Needs a new `QGraphicsItem` type (or reuse of `GraphicsNodeLabel`'s text-rendering machinery without the node attachment), placement/editing UX, and a decision on whether it's persisted in saved network files (and if so, in which formats) or session-only, like guides. ## Work Rules **Before committing any change described in this file:** run `./scripts/run_golden_compares.sh`. All golden JSON baselines must still pass. SocNetV-3.7/docs/roadmaps/roadmap_ws11_algorithm_additions.md000066400000000000000000000255351524402210300243710ustar00rootroot00000000000000# Algorithm Additions (WS11) ## Goal Implement algorithm-family feature requests that don't fit any existing workstream: WS5 (Matrices Modernization) is explicitly scoped to matrix *storage/performance*, not new algorithms; WS9 (Graph Exploration) is a completed workstream. WS11 is pure numerical/graph-theory implementation on top of already-stable infrastructure, not architecture. ## Status **Started.** Four items shipped so far β€” see What WS11 Delivered. Everything else in this doc (cohesive subgroups, more clustering algorithms, structural equivalence) is still untouched. ## Background New analysis algorithms requested against the existing `src/graph/` algorithm-slice architecture β€” algorithm slices stay QtCore only, no UI construction in the slice itself; rendering/reporting lives in `src/graph/ui/` and `src/graph/reporting/`. Grouped below by which existing slice directory each would live in. ## What WS11 Delivered - **#7 β€” Network connectivity metric.** Local vertex connectivity ΞΊ(s,t) between an actor pair β€” the minimum number of other nodes whose removal disconnects them β€” via Menger's theorem: ΞΊ(s,t) equals the max number of internally vertex-disjoint sβ†’t paths, computed as a max-flow problem on a vertex-split network (split each vertex into in/out nodes joined by a capacity-1 edge, so a unit of flow through a vertex "costs" one removal). Also covers global vertex connectivity ΞΊ(G) = min over all non-adjacent pairs of ΞΊ(s,t), with a cheap upper bound from the minimum vertex degree (Whitney's inequality) to prune most pairs before running max-flow on them. For directed networks, asks the user weak vs. strong (see #272) and applies that choice to both the local and global computation. Distinct from the existing edge-based cohesion measures. - **#272 β€” Connectedness only checks weak connectivity for directed networks.** Not a new algorithm β€” a bug-fix/polish pass on the existing "Connectedness" feature, tracked here because it produces `Graph::graphStronglyConnectedComponents()` (Tarjan's SCC), which #7's "strong" mode depends on. Also fixes a dead `connectivityMenu` menu member, a result message pointing at a menu path that doesn't exist, and a missing help-text entry for "Connectedness" in the Cohesion toolbox dropdown. Three follow-on visualizations surfaced by this work are noted below under What Remains Open (Cohesion), not yet filed or scoped. - **#10 β€” Katz Centrality.** New **Analyze β†’ Centrality β†’ Katz Centrality (KC)**, with full parity to the app's other 12 prominence indices: HTML/CSV report, all 4 Layout β†’ By Prominence Index variants, Filter Nodes by Centrality, Find Node by index score, prominence distribution chart, and a `--katz-alpha` CLI flag under the existing `prominence` kernel with golden baselines in `src/tools/baselines/prominence/` (undirected/directed happy paths, an Ξ±-boundary rejection case, and Krackhardt Kite for non-trivial size). New `src/graph/centrality/graph_centrality_katz.cpp` (`Graph::centralityKatz(alpha, considerWeights, inverseWeights, dropIsolates)`), `IndexType::KATZ`, `GraphVertex::KC()/SKC()`, and `src/forms/dialogcentralitykatz.{h,cpp,ui}` (asks for `Ξ±`; no live `1/Ξ»_max` bound shown, since that would require a synchronous `Graph` call from the GUI thread β€” relies on `centralityKatz()`'s post-hoc rejection instead). Since the shared layout-by-prominence dispatch has no parameter slot, the 4 layout variants reuse the `Ξ±` cached from the last Analyze run (`Graph::m_lastKatzAlpha`), with a `QMessageBox` telling the user to run Analyze first if Katz hasn't been computed yet this session. Landed `Matrix::powerIteration()`'s `Ξ»_max` output param (reused by `centralityEigenvector()` too) as a shared prerequisite. Found and fixed two independent pre-existing arg-shift bugs (`centralityInformation`/`centralityEigenvector` missing `inverseWeights`) opportunistically while wiring this in, in the CLI kernel and in `layoutByProminenceIndex()` respectively β€” both noted in `CHANGELOG.md`. - **#39 β€” Bonacich Power Centrality.** New **Analyze β†’ Centrality β†’ Bonacich Power Centrality (BPC)**, reusing Katz's now-proven pattern with the same full parity. Same `(I - xM)⁻¹` shape as Katz (`b = Ξ±(I - Ξ²R)⁻¹ Β· R Β· 1`, `R = Aα΅€`, same directional convention as Katz), minus the `- I` term and with a second parameter `Ξ²` that β€” unlike Katz's `Ξ±` β€” is allowed to be **negative**, flipping whether being connected to well-connected others helps or hurts (per-node scores can come out negative, unlike every other measure in the app); only `Ξ²` must satisfy `|Ξ²| < 1/Ξ»_max(A)`, `Ξ±` is a free outer scale factor with no bound. New `src/graph/centrality/graph_centrality_bonacich.cpp` (`Graph::centralityBonacich(alpha, beta, considerWeights, inverseWeights, dropIsolates)`, reusing `Matrix::productByVector()` for the extra `RΒ·1` step Katz's own formula didn't need), `IndexType::BPC`, `GraphVertex::BPC()/SBPC()`, `src/forms/dialogcentralitybonacich.{h,cpp,ui}` (two spin boxes, `Ξ²` not clamped to positive), `m_lastBonacichAlpha`/`m_lastBonacichBeta` cache for the layout-by-prominence path (mirrors `m_lastKatzAlpha` exactly). 5 new golden baselines under `src/tools/baselines/prominence/`, including one deliberately exercising the negative-`Ξ²` sign-flip on `TinyPath_N3_E2` (node 2 gains sharply while both endpoints go negative) β€” every value hand-verified via independent Gauss-Jordan elimination, same discipline as Katz. Guarded the `getProminenceIndexByName()` naming collision flagged during scoping: the new `"Bonacich"` check had to go **before** the existing `"Power Centr"` check (confirmed live in code β€” that branch would otherwise shadow it, matching the existing Gil-Schmidt `PC`). Also renumbered `IndexType` (`KATZ = 10, BPC = 11`, then `DP/PRP/PP/CLC = 12..15`, previously `KATZ/BPC = 14/15` at the very end): the Analyze menu already grouped Katz with the other centrality measures, but the Prominence dropdown and Layout-by-Index combo are built from `prominenceIndexList`, whose order is positionally tied to this enum, so Katz was visually landing after the Prestige entries there β€” inconsistent with the menu. No enum value is persisted anywhere, so this was safe to renumber mid-series. ## What Remains Open ### Centrality β€” `src/graph/centrality/` - **#134 β€” Alternate Centrality Measures Meta-List.** Not a single algorithm β€” a running collection of centrality-measure ideas (cross-references #10, #39, #108, and external references like `centiserve`/`netrankr`/`CINNA`). Treat as a backlog-within-a-backlog: triage individual measures out of it into their own issues as they're actually prioritized, rather than implementing "the meta-list" as one deliverable. ### Cohesion β€” `src/graph/cohesion/` - **#3 β€” Cohesive subgroups identification.** n-cliques, n-clans/n-clubs, k-plexes, matrix permutation approaches. Four distinct methods bundled in one issue β€” likely worth splitting into separate issues once one is actually scoped, since they're independent algorithms with different complexity profiles (clique-family enumeration is combinatorially expensive at scale). - **(not yet filed) Color nodes by strongly connected component.** Extends the existing weak-only "Layout β†’ Node Color by Connected Component" action to offer strong too. Tarjan's algorithm (`graphStronglyConnectedComponents()`) already computes per-vertex SCC membership as a side effect of finding the components β€” it's just discarded today (only the count is kept; see the function's own doc comment in `graph_distance_facade.cpp`). Needs an `m_vertexStrongComponentId` cache mirroring the existing weak one, plus UI wiring. - **(not yet filed) Articulation points (cut vertices).** Nodes whose individual removal disconnects the graph β€” the classic, most intuitive answer to "which single nodes are critical for connectivity." Standard DFS/low-link algorithm, a close cousin of Tarjan's SCC (same underlying technique as bridge-finding) but a distinct computation β€” doesn't need vertex connectivity or max-flow at all, runs once over the whole graph. Not built yet. - **(not yet filed) Minimum vertex cut for the weakest pair.** `graphConnectivity()` already runs max-flow internally to compute ΞΊ(G) β€” the actual separating node set is recoverable via standard max-flow/min-cut extraction (a BFS over the residual graph once the flow saturates), not a guess. Needs `graphConnectivity()` to remember which pair achieved the minimum (currently only the value is tracked), then re-run local flow for that pair with cut extraction. **Caveat, worth surfacing to the user in the UI**: minimum vertex cuts aren't always unique β€” a symmetric graph (e.g. the Petersen graph) can have several different node sets of the same minimum size separating the same pair, and the algorithm will deterministically return one of them, not necessarily "the" canonical one a human would pick. ### Clustering β€” `src/graph/clustering/` - **#5 β€” More clustering/community-detection algorithms**: Girvan-Newman, Clauset-Newman-Moore, Wakita-Tsurumi. SocNetV currently only has HCA (Hierarchical Clustering Analysis). **Cross-reference:** #258 ("[Future] Color nodes by detected community β€” Louvain/modularity", tracked as WS9 debt) is the same underlying capability (community detection) from the layout/coloring-integration angle rather than the analysis-output angle β€” worth scoping together rather than landing two independent community-detection implementations. ### Similarity / Structural Equivalence β€” `src/graph/similarity/` - **#181 β€” Structural equivalence analysis**: Multidimensional Scaling (MDS), blockmodelling, CONCOR (convergent correlations). Standard SNA structural-equivalence toolkit; SocNetV currently has pairwise similarity measures (matching, Pearson) but not these three. ## Work Rules - Same discipline as every other workstream: `./scripts/run_golden_compares.sh` clean before any commit, new golden baselines added for each new computation. - Each algorithm gets its own issue once actually scoped (several of the issues above bundle multiple distinct methods) β€” don't implement "the issue," implement one algorithm at a time. - New algorithm slices stay QtCore-only per the `src/graph/` boundary rule; UI/reporting hooks live in `src/graph/ui/` and `src/graph/reporting/`, matching the existing pattern for every other analysis in the app. - Every centrality/prestige doc comment pairs its technical `@brief` with a fixed shape β€” **Meaning**, **When to use**, **Compare to** (nearby/confusable measures), then **Math** β€” per `docs/README_DEVELOPER_NOTES.md`'s Doc-Comment Convention section. Already retrofitted onto every existing measure (DC, CC, IRCC, BC, SC, EC, PC, IC, EVC, DP, PRP, PP, CLC); Katz/Bonacich's own doc comments follow the same shape from the start. SocNetV-3.7/docs/roadmaps/roadmap_ws12_cli_scripting_mode.md000066400000000000000000000314221524402210300241730ustar00rootroot00000000000000# CLI Interactive/Scripting Mode (WS12) ## Goal Drive SocNetV from the command line without manual clicking β€” profiling with `sample`/`perf`, exercising GUI-triggered flows the headless `socnetv-cli` tool can't reach (it doesn't build the GUI at all), automated demos/regression scenarios, and eventually external programs driving SocNetV as a live component. ## Status 🚧 In progress. Thirty-four commands shipped across #261/#262/WS14/WS6.6/WS16 β€” see What WS12 Delivered below; every command now logs a uniform `BENCH` line on completion. Eventually most of SocNetV's functions should be reachable through interactive mode β€” see Background for where this is headed. ## Background ### Long-term direction The end state: most of SocNetV's functionality reachable through interactive mode, and SocNetV running as a long-lived process continuously fed commands by a third-party program β€” e.g. `tail -f events.txt | socnetv --interactive-script -`, with SocNetV acting as a live visual monitor for an external process (event spreading, simulations, etc.), not just a one-shot script runner. **This is achievable, and the current architecture is most of the way there** β€” every command already dispatches through the real Qt event loop rather than a blocking loop, which is the part that would be hardest to retrofit later. What's actually missing: - **Streaming input.** `runInteractiveScript()` currently does `readAll().split('\n')` once at startup and stops when the list is exhausted. A monitor mode needs the opposite: read what's available now, then keep watching indefinitely instead of terminating. For a live-appended file, `QFileSystemWatcher` + seeking to the last-read position covers it; for a pipe (the `tail -f | socnetv` case), `QSocketNotifier` on stdin's file descriptor, triggering a read whenever data arrives. `processNextInteractiveCommand()`'s "no more commands, stop" path becomes "no more commands *right now*, go idle until woken" instead. - **A `-` path convention** for `--interactive-script` to mean "read from stdin," matching the usual Unix convention, for the piped use case specifically. - **Bidirectional communication**, for the monitor use case specifically (not needed for scripted testing). Every command shipped so far is one-way: SocNetV consumes and acts, nothing reports state back. A real external monitor (e.g. "how many nodes are currently infected") needs SocNetV to answer queries, not just receive commands β€” that's a materially different, larger design question (a query/response command syntax, or a different transport entirely) than continuous command consumption, and not scoped yet. ## What WS12 Delivered ### CLI flags - `--encoding ` β€” loads the startup file with a given text codec, bypassing the "Preview file & Choose Encoding" dialog. - `--interactive-script ` β€” runs a plain-text script after startup, one command per line. Implementation in `MainWindow::runInteractiveScript()`/`processNextInteractiveCommand()` (`mainwindow.cpp`). Three dispatch shapes recur, all documented in the Doxygen comment on `processNextInteractiveCommand()` itself. Whichever shape a command uses, the rule is always the same: only advance to the next command once this command's own work has genuinely finished, never merely queued or triggered β€” getting this wrong is a real, reproducible bug, not a style preference (see below). - **No dispatch** (`new`, `render`, `bulk-node-size`, `bulk-edge-color`): a direct, blocking call on the GUI thread, no cross-thread queuing β€” genuinely done by the time the call returns, so advancing immediately afterward is correct as-is. - **Single-step** (`relation`, `erdos`, `erdos-m`, `save`, `add-node`, `add-edge`, `add-relation`, `click-node`, `move`): `QMetaObject::invokeMethod(activeGraph, lambda, Qt::QueuedConnection)` queues a lambda onto `activeGraph`'s own thread and returns immediately, without waiting for it to finish β€” so `BENCH` logging *and* the call advancing to the next script command (via a nested `QMetaObject::invokeMethod(this, ..., Qt::QueuedConnection)` back to the GUI thread) both happen *inside* that lambda, at actual completion, never around the `invokeMethod` call itself. Advancing outside the lambda let the next script command (e.g. `quit`, or another queued command) race ahead while the previous one's queued work was still running, confirmed via an out-of-bounds crash when `quit` ran immediately after `erdos` with no `delay` between them. Found and fixed across all 9 affected commands during the WS15 investigation that also produced Finding 8's fix β€” see `roadmap_ws15_cancellation_progress_unification.md`. - **Two-step** (`filter_ego`, `filter_isolates`, `symmetrize_strongties`, `symmetrize_cocitation`, `unilateral`, `distances`, `distances_bench` β€” anything long enough to want a progress dialog): `runGraphOperationAsync(operation, waitMessage, onComplete)` β€” one lambda does the (possibly slow) work, a second runs only once that's genuinely finished, to log `BENCH` and advance the script. Both lambdas share timer/result state via `std::shared_ptr`, since a plain local variable wouldn't survive between two separate lambdas. ### Output format Every command logs exactly one line on completion: ``` BENCH [command-specific fields] N= E= elapsed_ms= ``` via `qInfo()` β€” deliberately not `qDebug()`/`qCDebug()`, so these lines keep printing regardless of logging-category filter state (quiet-by-default, `-d` flags, etc.). This is uniform across every command below, not just the ones originally added for benchmarking. ### Commands - `delay X` β€” wait X seconds before the next command. `elapsed_ms` in its `BENCH` line should read ~= the requested delay β€” a cheap sanity check that scripted delays aren't drifting under load. - `new` β€” File β†’ New. - `relation N` β€” switch to relation N. - `unilateral` β€” toggle unilateral edges. Calls `Graph::edgeFilterUnilateral()` directly via `runGraphOperationAsync`, matching `slotEditFilterEdgesUnilateral()`'s own dispatch (two-step pattern, see above) rather than triggering the real `QAction`. - `erdos N p directed|undirected` β€” generates an ErdΕ‘s–RΓ©nyi `G(n,p)` network. - `save path` β€” saves the current network as GraphML. - `add-node` β€” adds a node at a random position. - `add-edge source target [weight]` β€” adds a directed edge (default weight 1). - `add-relation name` β€” adds a new relation and switches to it. - `distances [weights] [inverse] [dropisolates] [csv]` β€” mirrors the real Cohesion β†’ Distances Matrix menu action (`slotAnalyzeMatrixDistances()`) exactly: same computation (`writeMatrix()` β†’ `graphMatrixDistanceGeodesicCreate()`), same `runGraphOperationAsync()` dispatch, same output file β€” just without opening a `TextEditor` afterward, and without `askAboutEdgeWeights()`'s modal prompt (the tokens answer what it would ask). Trailing tokens are order-independent; presence of a token means true, absence means false. `csv` selects `ReportFormat::Csv` explicitly (WS16, #113) rather than reading the persisted Settings preference β€” a script has no Settings dialog to reflect. Previously called `graphDistancesGeodesic()` directly and crashed on some networks (`DistanceEngine::initRun` β†’ `Graph::isSymmetric` β†’ `edgeExists` β†’ `GraphVertex::hasEdgeTo`, invalid `QMultiHash` access) β€” the real menu action, computing via `writeMatrix()`, did not crash on the same network, so this command now goes through that path instead. - `distances_bench [weights] [inverse] [dropisolates] [centralities]` β€” benchmarking-only sibling of `distances`: same dispatch and computation, no disk write. `centralities` has no real-menu equivalent (the GUI computes each centrality index via ~9 separate menu actions, not one combined action), so it lives here rather than on `distances`. - `report-centrality-degree [weights] [dropisolates] [csv]`, `report-centrality-closeness [weights] [inverse] [dropisolates] [csv]`, `report-centrality-closeness-ir [weights] [inverse] [dropisolates] [csv]`, `report-centrality-betweenness [weights] [inverse] [dropisolates] [csv]`, `report-centrality-stress [weights] [inverse] [dropisolates] [csv]`, `report-centrality-eccentricity [weights] [inverse] [dropisolates] [csv]`, `report-centrality-power [weights] [inverse] [dropisolates] [csv]`, `report-centrality-information [weights] [inverse] [csv]`, `report-centrality-eigenvector [weights] [inverse] [csv]`, `report-prestige-degree [weights] [dropisolates] [csv]`, `report-prestige-proximity [dropisolates] [csv]`, `report-prestige-pagerank [dropisolates] [csv]` β€” each mirrors its real `Analyze` menu action exactly (`slotAnalyzeCentralityDegree()`, `slotAnalyzeCentralityCloseness()`, etc.), same `distances`-style pattern and `csv` token. `report-centrality-degree` was added for WS16 (#113, CSV report export) Step 0 as the first centrality/prestige report ever exercised headlessly; the other 11 followed in Step 2, once every `writeCentrality*`/`writePrestige*` function gained CSV support. `report-centrality-information` and `report-centrality-eigenvector` have no `dropisolates` token (the underlying functions don't take one, or - Eigenvector - never blank isolate rows regardless); `report-prestige-proximity` and `report-prestige-pagerank` have no `weights`/`inverse` tokens (fixed in the real menu action too). - `report-reciprocity [weights] [csv]`, `report-eccentricity [weights] [inverse] [dropisolates] [csv]`, `report-clustering-coefficient [csv]`, `report-triad-census [csv]` β€” added for WS16 Step 3 (the long-tail reports), same mirroring pattern as the commands above. `report-clustering-coefficient` and `report-triad-census` have no `weights` token (both fixed `considerWeights = true` in their real menu action). - `render` β€” forces a synchronous `graphicsWidget->viewport()->repaint()` (unlike `update()`, which only schedules one). Added for WS6.6's canvas rendering-perf kernel (`roadmap_ws6_testing_ci_regression.md`). - `bulk-node-size ` β€” calls `slotEditNodeSizeAll(N)` directly (nonzero `N` skips its modal `QInputDialog`). - `bulk-edge-color ` β€” calls `slotEditEdgeColorAll(QColor(name))` directly (a valid color skips its modal `QColorDialog`). - `move ` β€” sets an absolute canvas position via `Graph::vertexPosSet()`. Graduated from the backlog for WS6.6. - `quit` β€” ends the script and the app (`close()`, with the save-changes prompt bypassed since no one is present to answer it), so a scripted run doesn't need to be killed externally. ## What Remains Open Candidate commands, not yet scoped: - `run ` β€” trigger an analysis/layout by name - `open ` - `wait-idle` β€” block until the GUI event loop is idle, for more precise timing than a fixed `delay` - Basic control flow (`repeat N { ... }`) for stress-testing/averaging timing across runs - Script-level variables/parameters passed from the command line, instead of everything hardcoded in the script text - `cancel` β€” invoke `Graph::slotCancelComputation()` directly (same effect as clicking a `QProgressDialog`'s Cancel button, without needing one). Needs two prerequisites first, not just the command itself: - **A non-blocking dispatch variant.** Every command shipped so far β€” both dispatch patterns above β€” only advances the script *after* its own operation genuinely completes. So a script can never run `cancel` while a prior command is still in flight; the next line isn't dispatched until the previous one is already done. Both `report-centrality-information` and `report-centrality-eigenvector` (added in WS16 Step 2) now reach `Matrix::inverse()`/`powerIteration()` respectively, so that prerequisite is met; the non-blocking dispatch variant is still needed. Motivating use case: verifying WS5 A5's cancellation-aware algebra kernels actually interrupt a real in-progress computation, not just accept the parameter without exercising it. ### Known issues - **`scripts/run_report_export_bench.sh`'s large fixture hangs on its second invocation** within the same script run (found during WS16 Step 3) β€” never returns, no error, killed after 3+ minutes when it happened. A single standalone `--interactive-script` run of the same fixture always completes cleanly; the issue only shows up when the GUI binary is launched repeatedly in a tight loop. Not diagnosed further β€” plausibly a resource or state issue with repeated offscreen launches rather than anything in a specific command's own logic. ## Work Rules Same as everywhere else: `./scripts/run_golden_compares.sh` clean before any commit. New commands must stay dispatched through the real Qt event loop, not called directly, so scripted runs behave the same as actual user interaction. SocNetV-3.7/docs/roadmaps/roadmap_ws13_undo_redo.md000066400000000000000000000112051524402210300223120ustar00rootroot00000000000000# Undo/Redo (WS13) ## Goal Give users a real undo/redo mechanism for graph-mutating operations (#31), extending the existing filter/visibility snapshot pattern (shipped via WS9, see Background) to structural mutations and attribute edits. ## Status Just created (2026-07-30), not started. Scoped at a high level only β€” detailed technical design (per-mutation-type state capture, stack architecture) has not been done yet. See What Remains Open below for what's still unresolved. ## Background ### Origin #31 is a long-standing user request: *"My main frustration, as a beginner, is that I can't find an undo button; when I make large-scale mistakes, I find myself having to start all over."* No technical prerequisite is stated in the issue itself. This was previously tracked as a cross-cutting dependency of [`roadmap_ws3_architecture_performance.md`](roadmap_ws3_architecture_performance.md), gated on "at least M2" of that roadmap under the assumption that structural undo needs a stable domain model first. That assumption was investigated and found false β€” WS3's own domain-model plan itself turned out unnecessary (see its Goal section) and was dropped, so undo never structurally depended on anything WS3 was building. Spun out to its own workstream rather than folded back into WS3 (now narrowed to evidence-driven fixes, not a home for "things that need `Graph` to be cleaner first") or into WS9 (marked complete/shipped; reopening it to add a substantial new feature was the wrong container β€” same reasoning that led to spinning up WS11/WS12 as their own workstreams earlier). ### Existing precedent: filter/visibility undo (already shipped, via WS9) `m_visibilityHistory` (`QStack`, declared in `graph.h`, implemented in `src/graph/filters/graph_node_filters.cpp`) is a real, working, shipped undo mechanism: - Push a `GraphVisibilitySnapshot` onto the stack before every non-destructive filter operation. - Pop and restore on "Restore All" or arbitrary-index chip removal (`Graph::vertexFilterRemoveAt()` drains the stack, restores the pre-filter base, and replays all remaining snapshots' `FilterSpec`). - Plain value-type snapshots β€” zero `QUndoStack`, zero Qt command-pattern machinery. Covers today: centrality filter, ego network filter, selection filter, edge weight filter, attribute filter, and the query builder (#221). This is real evidence that undo is achievable against `Graph`'s existing (unrestructured) mutation API β€” the starting point for WS13, not a pattern to invent fresh. ## What WS13 Delivered Nothing yet. See "Existing precedent" above β€” that's WS9-shipped prior art WS13 will extend, not WS13's own delivered work. ## What Remains Open ### What's not covered today β€” the actual gap #31 is about - **Structural mutations**: `vertexCreate`/`vertexRemove`, `edgeCreate`/`edgeRemove`, relation add/remove. Unlike a visibility toggle, reversing these requires capturing real state before the mutation (a removed vertex's full edge list, attributes, position β€” not just an on/off flag). - **Attribute/property edits**: label, size, color, shape, weight, custom attributes. - **Bulk edits** (WS9 Feature 3 Phase 5) explicitly bypass any undo mechanism today β€” noted as a known gap in `roadmap_ws9_graph_exploration.md` at the time it shipped. ### Open questions β€” not yet scoped - Does the snapshot-stack pattern extend cleanly to structural mutations, or does per-mutation state-capture cost (copying full vertex/edge state) make it impractical for bulk operations (large random-network generation, subgraph deletion, multi-row bulk edit)? - One shared stack for structural + attribute + visibility undo, or separate stacks (mirroring the existing node/edge visibility split)? - Interaction with WS3 M2's batched edge-visibility signal path (`EdgeVisibilityChange` batching for relation switches/unilateral toggles) β€” does a batched operation need one snapshot per batch, or one per underlying edge change? - Redo: the existing filter stack is undo-only. Is that acceptable for structural mutations, or does #31 require real redo? - Scope boundary with `Graph::edgeFilterUnilateral()`/`relationSet()` style non-destructive operations that already have their own history mechanism β€” should structural undo reuse `m_visibilityHistory` or be a genuinely separate stack? ## Work Rules Same as elsewhere in this codebase: `./scripts/run_golden_compares.sh` clean before any commit; prefer adapters/extensions of the existing snapshot pattern over introducing new machinery (`QUndoStack`) unless a concrete limitation of the snapshot approach is found; every phase individually regression-tested before the next begins. SocNetV-3.7/docs/roadmaps/roadmap_ws14_logging_cost.md000066400000000000000000000077161524402210300230270ustar00rootroot00000000000000# Logging Cost & Release-Build Hygiene Roadmap (WS14) Tracking issue: [#268](https://github.com/socnetv/app/issues/268). ## Goal Stop paying `qDebug()` string-formatting cost in hot loops β€” the single largest performance problem found in the codebase to date. Origin: a "secondary finding, still open" filed against #254 in [`roadmap_ws3_architecture_performance.md`](roadmap_ws3_architecture_performance.md). ## Status βœ… Done. Zero `qDebug()` calls remain anywhere in `src/`, live or commented-out β€” converted to `qCDebug(category)` throughout (28 categories, ~1,760 call sites). The logging-category convention (why it's fast, how to add a new category) now lives in [`README_DEVELOPER_NOTES.md`](../README_DEVELOPER_NOTES.md)'s "Logging" section, not here. ## What WS14 Delivered - **L1 β€” release builds quiet by default.** A no-flag launch previously inherited whatever debug setting was last persisted to `settings.conf` β€” fixed to always force quiet, matching `-d 0`. - **L2 β€” `DistanceEngine` β†’ `lcEngine`.** The actual hot path: unconditional `qDebug()` in the BFS/Dijkstra inner loops and `finalize()`'s O(NΒ²) pair loop. - **L3 β€” parsers β†’ shared `lcParser`.** - **L4 β€” `matrix.cpp` β†’ `lcMatrix`.** Also settled a standing question: `inverseByGaussJordanElimination()` has no reachable caller (`createMatrixAdjacencyInverse()`'s sole caller always passes `"lu"`) β€” decision was to keep and convert it rather than delete it (coordinated with [WS5](roadmap_ws5_matrices_modernization.md), which owns any future decision to actually remove dead matrix code). - **Full tree sweep** β€” every remaining call site beyond the four hot-path milestones, at the user's request: even cold-path calls pay full formatting cost whenever debug output is ever turned on. **Measured results** (MacBook Pro M5, 24GB RAM, Release `-O2`, golden-clean throughout): | What | Before | After | Speedup | |---|---|---|---| | `DistanceEngine`, CLI, 1000N/10000A | 4,674 ms | 108 ms | **43Γ—** | | `DistanceEngine`, GUI, distances+centralities | 17,643 ms | 29 ms | **~608Γ—** (compounds L1's parallel-stderr-contention fix with L2's category gate β€” see Notes) | | Parser load, 2000-node GraphML | 323 ms | 109 ms | **~3Γ—** | | Information Centrality, N=300 | 4.27 s | 0.30 s | **~14Γ—** | | Binary size | 2.39 MB | 1.61 MB | βˆ’33% | `qCDebug(disabledCategory)` measured at 0.40 ns/call vs. plain `qDebug()`'s 618-2,193 ns/call depending on output destination β€” a ~1,500Γ— difference in the per-call cost this conversion removes. Full mechanism and reproduction command in README. ## Notes for the Future - **The GUI win (~608Γ—) is larger than the CLI win (~43Γ—) for a real reason, not measurement noise:** `DistanceEngine`'s parallel worker threads all writing to stderr concurrently hit real contention on Qt's internal logging lock; suppressing output (L1) removes that contention entirely, on top of L2's formatting-cost fix. Leading hypothesis, not independently profiled/confirmed in isolation. - **Why not just compile out logging with `QT_NO_DEBUG_OUTPUT`?** It would silently break the `-d 1`/`-d 2` command-line feature (compiled-out `qDebug` can never be re-enabled at runtime). `qCDebug` + categories gets the same near-zero cost while staying runtime-enablable β€” why conversion, not deletion, was the right fix. - Two mechanical-conversion gotchas worth remembering if this pattern is ever repeated elsewhere: printf-style `qDebug("fmt", args)` and the `qDebug ()` (space-before-parens) variant both evade a naive literal-text grep/sed pass β€” a real conversion needs to account for both. - Not independently verified: behavior when launched from Finder rather than a terminal (stderr routes to `os_log` instead of the destinations measured here β€” expected to be more expensive, not measured). ## What Remains Open Nothing scoped under WS14. Any *new* code should use `qCDebug(category)` from the start β€” see README's "Logging" section for which category to use. SocNetV-3.7/docs/roadmaps/roadmap_ws15_cancellation_progress_unification.md000066400000000000000000000156071524402210300273200ustar00rootroot00000000000000# App Responsiveness Contract (WS15) Dispatch, Cancellation, Busy-Guard & Parallelization ## Goal Make "the app is responsive" a checkable property instead of a recurring, never-quite-finished fix: define responsiveness as four independent, per-operation properties, and audit every long-running `Graph::` operation against all four, rather than fixing whatever's currently visible and calling it done. **The contract β€” four properties, numbered to match the milestone (P1-P4) that owns each one:** 1. **Working cancellation (P1)** β€” a `cancelCheck` wired at genuinely fine-grained points, using the atomic-flag + `Qt::DirectConnection` mechanism. 2. **Busy-guard coverage (P2)** β€” menu/toolbar/canvas/shortcuts disabled for the duration, via `setAppBusy()`. Automatic once (3) is true. 3. **Non-blocking dispatch (P3)** β€” wrapped in `runGraphOperationAsync` (or equivalent). 4. **Internally parallelized where the algorithm structure allows it (P4)** β€” `QtConcurrent`, following `DistanceEngine`'s APSP precedent. Not every algorithm qualifies (e.g. LU decomposition is sequential); the audit records which do and how. An operation can satisfy any subset of these independently β€” that's a legitimate state to report, not something to paper over. ## Status 🚧 In progress. P1-P3 βœ… done β€” linear progress-dialog system retired, exactly one progress dialog now exists app-wide. P4's audit done; implementation not started. See What WS15 Delivered below. ## What WS15 Delivered ### P1 β€” Atomic flag + `Qt::DirectConnection` βœ… Done `Graph::m_progressCanceled` is now `std::atomic`; the Cancel button's `canceled()` signal uses `Qt::DirectConnection` instead of the default queued cross-thread connection, so it lands synchronously instead of waiting for a busy `graphThread` event loop that never frees up. `Matrix::ludcmp()` (the O(nΒ³) core of `inverse()`) got its own `cancelCheck`, since `inverse()`'s own check is only reached after `ludcmp()` returns. Known residual gaps: `Matrix::solve()`'s own `ludcmp()` call isn't wired, and `DistanceEngine`'s parallel BFS deliberately skips cancellation in worker threads (performance-motivated). ### P2 β€” Global "graph busy" guard βœ… Done `MainWindow::setAppBusy()` disables `menuBar()`/`toolBar()`/`graphicsWidget`/`leftPanel` (the toolbox panel) and every reachable `QAction`, for the duration of every `runGraphOperationAsync` call β€” snapshotting and restoring only what it itself disabled, so state legitimately disabled elsewhere isn't clobbered. Covers container-level `setEnabled(false)`, individual `QAction`s (whose own `isEnabled()`, e.g. keyboard shortcuts, a container disable alone doesn't reach), and the toolbox's `QComboBox`es specifically (not `QAction`s, so outside that sweep otherwise β€” see `roadmap_ws5_matrices_modernization.md`'s A6 section). ### P3 β€” Retire the linear progress-dialog system βœ… Done Every `Graph::` operation reachable from the GUI now dispatches through `runGraphOperationAsync()`'s single indeterminate busy dialog. The legacy linear system (`Graph::progressCreate()`/ `progressUpdate()`/`progressFinish()` β†’ a separate numeric `QProgressDialog`) is fully retired, including `DistanceEngine`'s own nested dialog β€” `DistanceEngine::compute()` already self-closes its dialog internally, so removing dialog creation there left `resetCancellation()` (renamed from `progressCreate()`) as its only remaining job. `randomNetErdosCreate()`'s `--interactive-script erdos`/`erdos-m` benchmark path, the one caller that bypasses `runGraphOperationAsync`, calls `resetProgressCanceled()` directly instead, since it has no other reset point. Exactly one progress dialog exists in the app now, always β€” and it stays visible through Cancel: Qt's `QProgressDialog::cancel()`, wired to the built-in Cancel button, unconditionally hides the dialog before emitting `canceled()` (`setAutoClose`/`setAutoReset` don't gate that path), so a second `canceled()` connection re-shows it, relabels it "Canceling...", and disables it until the operation's own completion continuation tears it down for real. ### P4 β€” Parallelization audit βœ… Audit done, implementation not started Audited every long-running operation in `src/graph/`'s algorithm slices against all four contract properties, judging property 4 by real algorithm structure (independent per-source/per-node work vs. an inherently sequential dependency chain), not a grep pass. **Best parallelization candidates** (clear win, APSP-shaped, per-vertex/per-row independent, read-mostly): `centralityDegree`, `graphTriadCensus`, `clusteringCoefficient`, the O(NΒ²) matrix-fill loops following `graphDistancesGeodesic()` (`graphMatrixShortestPathsCreate`, `graphMatrixDistanceGeodesicCreate`, `createMatrixReachability`, `createMatrixAdjacency`), `centralityClosenessIR`/`prestigeDegree`/`prestigeProximity`. `createMatrixSimilarityMatching`/ `Matrix::distancesMatrix()`/`pearsonCorrelationCoefficients()` are good fits too but need a `cancelCheck`-style `Matrix` API change first. **Poor candidates** (inherently sequential): `Matrix::inverse()`/LU decomposition, `graphClusteringHierarchical` (agglomerative merging), the outer particle-selection loop in `layoutForceDirectedKamadaKawai`, preferential-attachment growth in `randomNetScaleFreeCreate`. The random generators generally split into a parallelizable "decide" phase and a serial "apply" phase (`edgeCreate()` mutates shared state) β€” not a drop-in `blockingMap`. **Worst remaining cancellation gaps** (long-running, coarse-only or zero mid-loop checks): `prestigePageRank`'s convergence loop, `graphTriadCensus`'s O(NΒ³) inner loops, `createMatrixSimilarityMatching` (one opaque uncancellable step), `randomNetRegularCreate`'s unbounded edge-randomization retry loop, `graphCliques`' Bron-Kerbosch recursion (checked only at recursion depth 1, deliberately, to avoid flooding the event loop β€” an accepted trade-off). Already parallel: `graphDistancesGeodesic` (β†’ `DistanceEngine`, `QtConcurrent::blockingMap`); Betweenness/Brandes centrality rides along in the same pass. Not yet decided whether/when to act on any of this β€” it's a map, not a commitment. Any parallelization work still needs its own golden/benchmark evidence (same discipline as WS5 A2.0/A3) before being called a real improvement. ## What Remains Open - **P4 implementation**: decide which parallelization candidates to act on, if any. While investigating P3's Cancel-button fix, tracing a distance-based analysis end to end also surfaced a reproducible crash in the `--interactive-script` command dispatcher (a script-ordering race, independent of anything above) β€” found, fixed, and documented in `roadmap_ws12_cli_scripting_mode.md`, not here. ## Work Rules - No GitHub issue for any of this (unreleased 3.7-cycle behavior) β€” fix directly. - Once P4 lands (or is explicitly parked): add a changelog entry, update WS5's A5 section and WS7's status line to point here instead of duplicating content. SocNetV-3.7/docs/roadmaps/roadmap_ws16_report_csv_export.md000066400000000000000000000573631524402210300241450ustar00rootroot00000000000000# Report CSV Export (WS16) ## Goal Let analysis reports be exported as CSV, not just HTML β€” issue #113: currently every `Analyze β†’ ...` report is an HTML file opened in the browser or the app's internal `TextEditor` (some with charts/prose embedded), with no plain-tabular option for opening results in a spreadsheet. ## Status βœ… Complete. All 4 steps done: baseline benchmarking, matrix-family CSV export, centrality/prestige CSV + dedup, and the Step 3 long tail (Reciprocity, standalone Eccentricity, Clustering Coefficient, Triad Census). Clique Census and Hierarchical Clustering are HTML-only permanently, by design (see Step 3 below) β€” not a gap. ## Background A three-agent codebase investigation established the actual shape of the problem before any design work: - All ~27 report writers are `Graph::write*()` methods in `src/graph/reporting/graph_reports.cpp`, each opening a `QFile` and writing a full HTML document (`htmlHead` + content + `htmlEnd`, built once in the `Graph` constructor, `graph.cpp:159-275`). `MainWindow::slotAnalyze*` slots call these via the standard `runGraphOperationAsync` dispatch, then open the result via `QDesktopServices::openUrl()` or an internal `TextEditor`, gated by `appSettings["viewReportsInSystemBrowser"]`. - **Matrix-family reports** (~15 report types: Adjacency, Distances, Geodesics, Reachability, Laplacian, Degree, Cocitation, Transpose, Adjacency Inverse, Walks, Similarity/Pearson/Matching, Dissimilarities) already funnel through one shared renderer, `Graph::writeMatrixHTMLTable()` (`:5959`), called from `writeMatrix()` (`:5548`, cleanly split into a compute phase and a render phase joined only by a `Matrix&` reference) and four sibling writers. This is the cheap slice β€” one new renderer function covers all of it. - **Centrality/Prestige reports** (12 functions β€” `writeCentralityDegree`, `...Closeness`, `...ClosenessInfluenceRange`, `...Betweenness`, `...Stress`, `...Eccentricity`, `...Power`, `...Information`, `...Eigenvector`, `writePrestigeDegree`, `...Proximity`, `...PageRank`) have **no shared table renderer** β€” each hand-rolls an identical 5-column (Node/Label/Raw/Normalized/%Normalized) `` scaffold, complete with copy-pasted `tableSort()` JS wiring and isolate-drop placeholder logic. Harder slice β€” needs a genuinely new shared renderer, which also deduplicates 12x copy-pasted HTML as a side effect. - **No headless path reaches any `write*()` function except `--interactive-script`.** `socnetv-cli`'s kernels only ever emit JSON. The `distances` interactive command (`mainwindow.cpp:6175-6208`) was the only existing example of a report writer benchmarked end-to-end (compute + HTML file I/O) before this workstream. - CSV escaping already exists and should be reused: `TableExport::toCSV(QAbstractItemModel*, path)` (`src/graph/io/table_export.cpp:27-76`) has a private `csvQuote()` helper (quote-only- if-needed) used today for the Data Table dock's node/edge export. Matrix reports never need it (row/column headers are always plain vertex numbers); centrality/prestige reports do, since node labels are free text. - Settings: `DialogSettings`' existing `reportsGroupBox` (`src/forms/dialogsettings.ui`, inside `generalTab`) already has 3 report-output controls (label length, real-number precision, chart type), each wired `DialogSettings` signal β†’ `MainWindow` connect β†’ a `Graph::setReportsX()` setter storing a `Graph`-private member. A 4th control (output format) fits this exact groupbox and wiring pattern. **Design decisions**: one global "Report output format" setting (HTML default / CSV), not per-report toggles; CSV output is lean (table only, no prose/summary-stats/chart β€” a report with a chart just skips that step for CSV); CSV escaping reused via a new `TableExport` overload, never reimplemented; `MainWindow` decides the file extension and always opens CSV via `QDesktopServices::openUrl()` (the internal `TextEditor`'s plain-text mode isn't a useful CSV view); `writeMatrixAdjacencyPlot()` (glyph-based visual plot) is excluded β€” not tabular data; purely narrative/single-value reports (Connectedness, Node/Graph Connectivity's ΞΊ) stay HTML-only. ## What WS16 Delivered ### Step 0 β€” Baseline benchmarking infrastructure Since `--interactive-script` is the only headless path that can reach a `Graph::write*()` function, and nothing benchmarked the centrality/prestige family before this: - New interactive-script command `report-centrality-degree [weights] [dropisolates]` (`mainwindow.cpp`), mirroring `slotAnalyzeCentralityDegree()` exactly, same shape as the existing `distances` command β€” the first centrality/prestige report ever exercised headlessly. - New script `scripts/run_report_export_bench.sh`, modeled on `scripts/run_render_perf_bench.sh` (the WS6.6 precedent for GUI-driven, `--interactive-script`, `QT_QPA_PLATFORM=offscreen` benchmarks `run_benchmarks.sh`'s CLI-kernel-only design can't reach). Deliberately a rough capture-and-print tool, not a CI-threshold-gated regression kernel like the render-perf one β€” formal threshold-gating can follow later if this becomes a standing regression concern. - Two fixtures: `scripts/fixtures/report_export_bench_small.txt` (N=500, E=2500) and `..._large.txt` (N=2000, E=40000, matching the existing render-perf fixture's scale). **Baseline numbers** (median of 5 runs, macOS arm64, Debug build, offscreen): | Fixture | `distances` (writeMatrix, HTML) | `report-centrality-degree` (writeCentralityDegree, HTML) | |---|---|---| | small (N=500, E=2500) | 1166 ms | 106 ms | | large (N=2000, E=40000) | 27092 ms | 776 ms | Note: `distances`' timing is dominated by APSP computation (DistanceEngine), not the HTML-writing step itself β€” the two are not separated in this measurement. Later steps should expect these end-to-end numbers to stay essentially flat (compute unchanged) rather than looking for a large drop; the CSV path's own timing (once it exists) is the more informative comparison. ### Step 1 β€” Matrix-family CSV export - New `Graph::writeMatrixCSVTable()` (`graph_reports.cpp`), sibling to `writeMatrixHTMLTable()` β€” same value-formatting rules (RAND_MAX sentinel, #266 large-magnitude scientific notation, precision), comma-delimited, vertex-number header row, no escaping needed. - New `ReportFormat { Html, Csv }` enum (`global.h`), threaded as a parameter through `writeMatrix()`, `writeMatrixWalks()`, `writeMatrixDissimilarities()`, `writeMatrixSimilarityMatching()`, `writeMatrixSimilarityPearson()`, and `writeMatrixAdjacency()` β€” compute phase untouched in every case, CSV branch is self-contained and returns early. `writeMatrixAdjacency()` doesn't reuse `writeMatrixCSVTable()` (it computes cells live via `edgeExists()`, not from a pre-built `Matrix`, to preserve node numbers after deletions) β€” has its own small comma-delimited loop instead. - New Settings control: `reportsGroupBox` in `DialogSettings` gained a 4th row ("Output format", HTML/CSV combo), wired via the same `DialogSettings` signal β†’ `MainWindow` connect β†’ `Graph::setReportsOutputFormat()` pattern as the 3 existing Reports settings. `appSettings["initReportsOutputFormat"]` persists it (default `"0"` = HTML). - **Bug found and fixed along the way**: `writeMatrixAdjacency()` and `writeMatrixWalks()` both returned `void`, unlike every other writer in this family. Their 3 `MainWindow` call sites (`slotNetworkViewSociomatrix`, `slotAnalyzeWalksLength`, `slotAnalyzeWalksTotal`) gated the "open the report" step on `activeGraph->progressCanceled()` alone β€” which only reflects the user clicking Cancel, not a file-open failure. A silently failed write (bad path, permissions, disk full) was reported to the user as "saved" even though nothing was written. Both functions now return `bool`; all 3 call sites check the real success flag. - All 14 `MainWindow` call sites updated: pick `.html`/`.csv` extension from the setting, pass `format` through, CSV always opens via `QDesktopServices::openUrl()`. The `distances` interactive-script command gained a `csv` token (explicit, not settings-read, since a script has no Settings dialog) so it can't silently diverge from the real menu action. **Verification**: `./scripts/run_golden_compares.sh` clean (Phase A/compute untouched everywhere). Manual content check: `distances weights csv` on an 8-node network produced a correct comma-delimited, symmetric distance matrix with a 0 diagonal, matching the HTML sibling's values exactly. Live GUI check of the new Settings control caught and fixed a real bug (see below) before it shipped. Benchmark re-run (`run_report_export_bench.sh`) confirmed the HTML path is unchanged within normal run-to-run noise (small: 1166β†’1243ms, 776β†’767ms; large: 27092β†’27481ms, 776β†’767ms β€” all within the ~5-10% variance already present run-to-run). A single-run CSV-vs-HTML `distances` comparison on the large fixture (28991ms CSV vs. 24495ms HTML) isn't a real signal either way β€” both fall inside that same variance band, and this benchmark measures APSP compute time (identical in both branches), not report-writing cost specifically; isolating the write-only cost would need a different measurement, not attempted here. **Settings UI bug found and fixed during manual verification**: the `.ui` file defined the new combo box's two items (`HTML`, `CSV`) *and* the constructor called `addItems()` with the same two strings β€” the combo showed four duplicate entries at runtime. Fixed by removing the static `.ui` items, matching the existing `reportsChartTypeSelect` pattern (items added purely in code). ### Step 2 β€” Centrality/Prestige CSV export + dedup Cataloguing all 12 functions' HTML table blocks before designing anything (rather than trusting Step 1's "5-column" description at face value) surfaced that the table shape isn't uniform: - **3-data-column family** (raw / standardized / %standardized): Degree, Closeness, Betweenness, Stress, Power, Degree Prestige, PageRank Prestige, Information Centrality β€” 8 functions. - **2-data-column family** (raw only, since raw == standardized for these; %raw): Closeness Influence Range, Eccentricity Centrality, Proximity Prestige β€” 3 functions. - **4-data-column family**: Eigenvector Centrality alone (raw / scaled / standardized / %standardized). Isolate-row-blanking (the HTML `--` placeholder / CSV empty-cell behaviour) also wasn't uniform: 10 of the 12 use `dropIsolates && isIsolated()`, Information Centrality uses a bare `isIsolated()` (it has no `dropIsolates` parameter), and Eigenvector Centrality never blanks isolate rows at all despite taking a `dropIsolates` parameter. Each report's existing behaviour was preserved exactly via a per-call predicate rather than silently unified. **New shared renderer pair** (`Graph::writeScoreTableHTML()` / `writeScoreTableCSV()`, `graph_reports.cpp`, ahead of `writeCentralityInformation()`): "Node"/"Label" are fixed leading columns; a `QStringList` of data-column headers and a `std::function(GraphVertex*)>` row-value callback supply the rest, so all three column-shapes are handled by the same function without a fixed-arity assumption. An optional `std::function` isolate predicate (default `nullptr`, matching Eigenvector's "never blank" behaviour) lets each of the 12 call sites reproduce its own existing rule. The HTML renderer generates its `tableSort()` `onclick` JS from the real column count β€” a side effect of this is that it also fixes a small pre-existing dead-code bug: the three 4-column tables (Influence Range Closeness, Eccentricity, Proximity Prestige) had copy-pasted `onclick` handlers that reset a nonexistent `asc5` JS variable; the generated version only emits `asc1..ascN` for the columns that actually exist. **Deviation from the original Step 2 plan**: rather than adding a `TableExport::toCSV(headers, rows, path)` overload that opens its own file, `writeScoreTableCSV()` follows Step 1's actual `writeMatrixCSVTable()` precedent instead β€” it writes directly into the same already-open `QTextStream`/`QFile` the HTML path uses (self-contained CSV branch, returns early, one file-open per report). `TableExport`'s private `csvQuote()` helper was promoted to a public `TableExport::csvQuote()` function and is called on the Label column only β€” the one free-text field in these reports; node numbers and scores are always numeric and never need escaping. All 12 `Graph::write*()` signatures gained a trailing `const int &format = ReportFormat::Html` parameter (matching Step 1's pattern); all 12 `MainWindow::slotAnalyze*` call sites were retrofitted identically to Step 1's 14 matrix-family sites (extension picked from the Settings preference, CSV always opens via `QDesktopServices::openUrl()`). The interactive-script `report-centrality-degree` command gained a `csv` token (matching `distances`'), and 11 new sibling commands were added - `report-centrality-closeness[-ir]`, `report-centrality-betweenness`, `report-centrality-stress`, `report-centrality-eccentricity`, `report-centrality-power`, `report-centrality-information`, `report-centrality-eigenvector`, `report-prestige-degree`, `report-prestige-proximity`, `report-prestige-pagerank` - each mirroring its real menu action exactly, giving every centrality/prestige writer a first-ever headless benchmark path. `scripts/run_report_export_bench.sh` and both fixtures were generalized from the hardcoded `distances`/`report-centrality-degree` pair to loop over all 13 commands (an array of command names replaces the old 1:1 hand-written variable pairs), so one run now benchmarks every report writer this workstream touches. **Baseline numbers** (median of 3 runs, macOS arm64, Debug build, offscreen; all HTML, since this run's purpose is confirming the existing HTML path is unaffected): | Report | small (N=500, E=2500) | large (N=2000, E=40000) | |---|---|---| | `distances` (writeMatrix) | 1190 ms | 28768 ms | | Degree Centrality | 110 ms | 803 ms | | Closeness Centrality | 336 ms | 9281 ms | | Closeness (Influence Range) | 103 ms | 762 ms | | Betweenness Centrality | 81 ms | 302 ms | | Stress Centrality | 83 ms | 299 ms | | Eccentricity Centrality | 81 ms | 301 ms | | Power Centrality | 81 ms | 291 ms | | Information Centrality | 1029 ms | 42545 ms | | Eigenvector Centrality | 535 ms | 10599 ms | | Degree Prestige | 82 ms | 298 ms | | Proximity Prestige | 101 ms | 695 ms | | PageRank Prestige | 93 ms | 295 ms | `distances` and Degree Centrality are within ~2-6% of the Step 0 baseline (1166β†’1190 ms, 27092β†’28768 ms; 106β†’110 ms, 776β†’803 ms) - inside the previously-established run-to-run noise band, confirming the shared-renderer extraction added no measurable overhead. The other 11 numbers are first-ever measurements (no prior baseline existed), now recorded here as the reference point for any future change to these writers. **Verification**: `./scripts/run_golden_compares.sh` clean. Content spot-checks via `--interactive-script`: Degree Centrality CSV matched its HTML sibling's values exactly (including the `DC'` = `DC / sumDC` valued-network standardization, not `DC / (N-1)`); Eigenvector CSV showed all 4 data columns; Influence Range Closeness CSV showed exactly 2; a sparse network with isolates confirmed `dropisolates csv` renders **empty cells** for blanked rows (not `--` - CSV consumers read a blank field as null/absent, `--` would corrupt a numeric column type on import). Fresh HTML regeneration confirmed the `tableSort` dead-code fix (4-column tables now emit only `asc1..asc4`). Live GUI smoke test (user-performed, since headless testing can't reach the Settings dialog): Settings β†’ Output format β†’ CSV, ran Degree Centrality from the real `Analyze` menu β†’ opened correctly in the system's default spreadsheet app with a clean table; switched back to HTML, ran Closeness Centrality β†’ opened correctly in the built-in report viewer. ### Step 3 β€” Long tail Investigation first, same as Step 2: `writeReciprocity()`, `writeEccentricity()` (the standalone report β€” distinct from Eccentricity *Centrality*, done in Step 2), `writeClusteringCoefficient()`, and `writeTriadCensus()` were read in full before deciding anything, plus a check of Connectedness and Node/Graph Connectivity's actual call sites. - **Connectedness and Node/Graph Connectivity turned out not to be file reports at all** β€” `slotAnalyzeConnectedness()`/`slotAnalyzeNodeConnectivity()` show their result via `QMessageBox`/`QInputDialog`, never calling any `Graph::write*()` function. Nothing to do here; the original plan's "likely stay HTML-only" guess was half right but the real answer is "N/A, not a report." - **Reciprocity** fits the Step 2 shared renderer exactly (6 data columns, no isolate-blanking) β€” reused `writeScoreTableHTML()`/`writeScoreTableCSV()` as-is, no renderer changes needed. - **Clustering Coefficient** also fits directly (3 data columns). Its HTML loop had a `progressCanceled()` check on every row, unlike every Step 2 report (which check once before the loop) β€” dropped to match the established convention, trading fine-grained cancellation on very large N for consistency; the pre-loop check is retained. - **Eccentricity** (standalone) needed two genuine renderer extensions, since it doesn't fit the "plain qreal, always-present row" shape the renderer assumed: - Its value can be the infinity glyph (∞), not a number, for disconnected nodes. Rather than a one-off, `writeScoreTableHTML()`/`writeScoreTableCSV()` gained a general RAND_MAX-as-infinity convention matching `writeMatrixHTMLTable()`/`writeMatrixCSVTable()`'s existing sentinel, so any future report needing this doesn't need its own workaround. - It skips disabled vertices entirely (`continue`, row never appears) rather than blanking them β€” different from every Step 2 report's isolate-*blanking*. Added a second optional predicate, `isSkipped`, distinct from `isBlanked`, so this exact behavior carries over instead of being silently changed to blanking (which would make disabled nodes visible with placeholder cells where before they didn't appear at all). - Minor, deliberate side effect: Reciprocity and Eccentricity's "Actor" column header is now "Node" (the shared renderer's fixed label), matching every other score table. Purely cosmetic β€” same data, same meaning β€” not called out separately at the time but worth recording here since it's a visible (if trivial) output change. - **Triad Census** is a small *fixed-shape* table (16 known triad types Γ— census count), not per-node β€” doesn't go through the shared renderer at all. A direct 2-column CSV write (`Type`, `Census`) suffices; no escaping needed since triad-type labels are fixed known strings. - **Clique Census** and **Hierarchical Clustering** confirmed as HTML-only, permanently, exactly as scoped originally: Clique Census combines 4 heterogeneous sub-tables (clique list, actorΓ—clique matrix, actorΓ—actor co-membership matrix, and a full HCA dendrogram sub-report) that don't reduce to one flat CSV; Hierarchical Clustering produces an equivalence matrix plus a dendrogram, same reasoning. Documented as comments at both declarations in `graph.h` so the reasoning survives independent of this doc. **Bug found and fixed along the way**: `writeReciprocity()` had two separate problems, both fixed in the same pass since fixing the second required touching the exact code the first lives in: - Like `writeMatrixAdjacency()`/`writeMatrixWalks()` in Step 1, it returned `void` and silently swallowed file-open failures; its `MainWindow` call site checked only `activeGraph->progressCanceled()`, which doesn't reflect a failed write. Converted to `bool`, call site now checks the real success flag. - The per-actor "Symmetric" ratio divided by an actor's total tie count with **no zero-guard**, unlike every other ratio in the same function. An actor with zero inbound and outbound edges hit `0/0`, producing the literal text `nan` in both HTML and CSV output β€” confirmed present in the original HTML report too (not something the CSV refactor introduced), by generating a fresh HTML report from the pre-fix code against the same test network. Guarded the same way as the other four ratios: no ties means 0, not NaN. All 4 `MainWindow` call sites (Reciprocity, Eccentricity, Clustering Coefficient, Triad Census) retrofitted with the Step 1/2 extension-picking and viewer-branching pattern. 4 new interactive-script commands added (`report-reciprocity`, `report-eccentricity`, `report-clustering-coefficient`, `report-triad-census`), each mirroring its real menu action. **Baseline numbers** (macOS arm64, Debug build, offscreen, HTML): | Report | small (N=500, E=2500), median of 3 | large (N=2000, E=40000), single run | |---|---|---| | Reciprocity | 23 ms | 241 ms | | Eccentricity (standalone) | 37 ms | 227 ms | | Clustering Coefficient | 19 ms | 543 ms | | Triad Census | 9164 ms | not run (see below) | The large fixture's numbers are a **single run, not a median** β€” see the benchmark-harness finding below. `distances` and all 12 Step 2 reports were re-measured in the same pass and stayed within normal run-to-run variance of their Step 2 baseline (e.g. `distances` 28768β†’27057 ms single-run, Degree Centrality 803β†’877 ms), consistent with "no computational change" for those paths. **Triad Census is O(nΒ³)** (enumerates every triple of vertices) and was excluded from the large fixture (N=2000) after a single run took over 12 minutes and was still running when interrupted β€” genuinely expensive at that scale, not a bug. It stays in the small fixture (N=500, ~9s) where it's still a meaningful measurement. `scripts/run_report_export_bench.sh`'s shared `COMMANDS` list now tolerates a command being absent from one fixture's output (skips it rather than failing), since the two fixtures no longer run an identical command set. **Benchmark-harness finding (not fixed here β€” out of WS16's scope)**: running `scripts/run_report_export_bench.sh` end-to-end, the large-fixture `--interactive-script` process **hangs** (not crashes β€” no error, never returns) on its second invocation in the same script run; a single standalone run always completes cleanly. Root cause not diagnosed β€” plausibly a resource or state issue with launching the offscreen GUI repeatedly in a tight loop rather than anything specific to Step 3's changes, since the fixtures/harness are WS12 territory, not WS16's. Worth a closer look if this benchmark script becomes something run regularly. **Verification**: `./scripts/run_golden_compares.sh` clean throughout. Content spot-checks via `--interactive-script` on a fresh directed test network: Reciprocity CSV/HTML matched exactly (post-fix, no `nan` anywhere); Eccentricity CSV/HTML both showed `∞` for all 10 nodes, confirmed correct (not a bug) by checking the network genuinely isn't strongly connected β€” matrix-identical between formats; Clustering Coefficient and Triad Census CSVs matched their HTML output. ## Known Gaps - **Clique Census and Hierarchical Clustering are HTML-only, permanently** β€” both combine several heterogeneous sub-tables (co-membership matrices, a dendrogram) that don't reduce to one flat CSV table. Documented as comments at their `graph.h` declarations. - **Connectedness and Node/Graph Connectivity have no CSV form** because they were never file reports β€” both show their result via a dialog, not an `Analyze` report. - **`scripts/run_report_export_bench.sh`'s large fixture hangs on its second invocation** within the same script run β€” tracked in [`roadmap_ws12_cli_scripting_mode.md`](roadmap_ws12_cli_scripting_mode.md)'s Known Issues, since it's the shared `--interactive-script` harness (WS12), not anything specific to this workstream. - **Triad Census's benchmark only runs at the small fixture's scale (N=500)** β€” it's O(nΒ³); a single run at the large fixture's N=2000 was still running after 12+ minutes when interrupted. ## What Remains Open Nothing scoped under WS16. SocNetV-3.7/docs/roadmaps/roadmap_ws1_distances_geodesic_engine.md000066400000000000000000000046661524402210300254320ustar00rootroot00000000000000# Distance & Geodesic Engine Refactor Roadmap (WS1) ## Goal Extract `Graph::graphDistancesGeodesic()` β€” a monolithic BFS/Dijkstra + Brandes + progress + result-storage method β€” into a standalone, testable `DistanceEngine` while preserving exact numeric results and UI behaviour. ## Status βœ… Complete. Current `DistanceEngine` architecture (call flow, scratch-state layering, APSP storage) lives in [`README_DEVELOPER_NOTES.md`](../README_DEVELOPER_NOTES.md)'s "Distance Engine" section, not here. Active DistanceEngine work is tracked in [`roadmap_ws3_architecture_performance.md`](roadmap_ws3_architecture_performance.md) (WS3). ## What WS1 Delivered - **Extraction foundation** β€” `DistanceEngine` introduced as the owner of geodesic-distance computation, accessing `Graph` internals via `friend class DistanceEngine;` (an explicit transitional choice, narrowed further by WS3 M2+). - **Verified behavioral parity** β€” checked against Zachary's Karate Club: identical distances, centralities, and prestige indices vs. SocNetV 3.2. - **Internal structure** β€” `compute()` split into `initRun()`/`runAllSources()`/`finalize()`, with explicit scratch structs replacing giant parameter lists. - **Progress/UI decoupling** β€” `IDistanceProgressSink` interface (`GraphDistanceProgressSink` for Qt/UI, `NullDistanceProgressSink` for headless), so `DistanceEngine` has no Qt-signal dependency. - **Golden regression harness** (`socnetv-cli --kernel distance --compare-json`) β€” the deterministic JSON-schema foundation WS6 later grew to 7 kernel families. - **Physical extraction** β€” `src/engine/distance_engine.{h,cpp}` created; `graph.cpp` reduced to constructor + `clear()`. - **Micro-benchmarking** β€” `scripts/run_benchmarks.sh` introduced, with a performance guardrail vs. v3.2. - A transitional step (wrapping per-source SSSP scratch behind intent-revealing `Graph`/ `GraphVertex` accessors) was later fully superseded by WS3 Phase 1/2's `PerSourceScratch` extraction and parallel source loop β€” the transitional accessors no longer exist. ## What Remains Open - Narrowing/removing the `friend class DistanceEngine` grant on `Graph` β€” checked directly: `DistanceEngine` touches ~85 `graph.X` members through it, all public accessors or cache/aggregate fields, squarely WS3 M4 (explicit cache objects) territory, not a domain-model boundary. Parked, not scheduled. - Type tightening (`int` β†’ `qsizetype`) β€” low priority, deferred. SocNetV-3.7/docs/roadmaps/roadmap_ws2_ui_graph_facade.md000066400000000000000000000047131524402210300233410ustar00rootroot00000000000000# Graph as FaΓ§ade / Coordinator β€” WS2 ## Goal Turn `Graph` from a monolithic algorithm host into a thin faΓ§ade/coordinator: keeps state + invariants, exposes a stable faΓ§ade API for UI and CLI, delegates algorithms to slices/engines, centralizes UI orchestration (signals, thread affinity) β€” does not host algorithm logic directly. ## Status βœ… Complete. Current `Graph`-as-faΓ§ade architecture (directory layout, algorithm/UI boundary rules) lives in [`README_DEVELOPER_NOTES.md`](../README_DEVELOPER_NOTES.md)'s "Graph as FaΓ§ade" and "Structural Boundary Inside `src/graph/`" sections, not here. ## What WS2 Delivered - **F0 β€” FaΓ§ade contract defined**: what UI/CLI are allowed to call, marked in `graph.h`; internal helpers no longer considered public API. - **F1 β€” Engine boundary tightened**: `DistanceEngine` extracted with no direct access to `Graph` internals (later fully superseded by WS3's `PerSourceScratch` extraction). - **F2 β€” Mechanical extraction of `graph.cpp`**: no logic/signature changes, pure translation-unit slicing, verified after every slice. The structural core of WS2 β€” `graph.cpp` now contains only the constructor and `clear()`. - **F3 β€” UI boundary tightened**: audited `MainWindow`/`GraphicsWidget`/dialogs/graphics items; found thread-affinity handling (`thread()`/`moveToThread`) as the one non-faΓ§ade pattern, fixed with faΓ§ade wrappers. - **F4 β€” Algorithm/UI separation enforced**: formal boundary between algorithm slices (compute-only, no QtWidgets/QtCharts, no UI signals) and the UI faΓ§ade layer (`src/graph/ui/`), applied first to the prominence-distribution subsystem. ## Notes for the Future - **F4 had a real gap, found and closed during WS3 (#254):** F4 separated *who* constructs QtCharts objects but not *which thread* runs that code β€” it was safe by accident (every `MainWindow` β†’ `Graph` call was, incorrectly, a direct synchronous call, so UI faΓ§ade code always happened to run on the GUI thread already). Once #254 made `Graph` methods actually run on their intended `graphThread`, this started constructing QtCharts objects off the GUI thread. Fixed with `Graph::runOnGuiThread()` β€” now documented as the standard pattern in `README_DEVELOPER_NOTES.md`. Any new UI faΓ§ade code must use it. ## What Remains Open Nothing scoped under WS2 itself. Further engine extraction, IO boundary work, and the `DistanceEngine`/`Graph` `friend` relationship are tracked under WS3/WS4 respectively. SocNetV-3.7/docs/roadmaps/roadmap_ws3_architecture_performance.md000066400000000000000000000120311524402210300253140ustar00rootroot00000000000000# Architecture & Performance Roadmap (WS3) ## Goal Fix specific, measured performance and correctness problems in `Graph`/`GraphVertex` β€” not build a separate domain-model layer for its own sake. (Originally scoped around introducing a domain model; that turned out unnecessary β€” every real win here came from fixing a specific, measured problem directly, not from restructuring on the assumption that separating concerns is inherently worth it.) ## Status βœ… Done β€” see [`ARCHITECTURAL_REFACTORING_ROADMAP.md`](../ARCHITECTURAL_REFACTORING_ROADMAP.md). Current `DistanceEngine`/threading architecture lives in [`README_DEVELOPER_NOTES.md`](../README_DEVELOPER_NOTES.md), not here. ## What WS3 Delivered - **M1 β€” `DistanceEngine` parallelization** βœ… β€” source loop parallelized via `QtConcurrent::blockingMap`; measured **2.7×–8.3Γ—** speedup (Debug build, 24-core Linux), all golden baselines passing. Architecture (`PerSourceScratch`/`ThreadLocalState`, call flow) now documented in README's "Distance Engine" section. - **M1 continuation (APSP storage)** βœ… β€” delegated to and completed by [WS5's A2](roadmap_ws5_matrices_modernization.md): relation-keyed flat matrices (`Graph::m_apspDist`/`m_apspSigma`) replacing per-vertex `QHash` storage on `GraphVertex`. - **#254/#263 β€” GUI freeze during long computations** βœ… β€” root cause: `MainWindow` called `Graph` methods directly/synchronously instead of dispatching onto `graphThread`. Fixed with `MainWindow::runGraphOperationAsync()` (now the standard pattern β€” see README), rolled out across 31 entry points. Two real bugs found along the way: a `QProgressDialog::close()` call that silently marked successful completions as cancelled (fixed via `reset()` instead), and a UI-faΓ§ade chart-building crash once code genuinely started running on `graphThread` (fixed with `Graph::runOnGuiThread()`, see WS2). - **#266 β€” Walks Total numeric display fix** βœ… β€” walk counts at high matrix powers exceed `double`'s precision; `writeMatrixHTMLTable()` now uses scientific notation above magnitude 1,000 instead of printing false-precision digits. - **#249/#253 β€” Viewport auto-fit and resize debouncing** βœ… β€” `resizeEvent` debounced to one `canvasSizeSet` per stable stop; zoom/reset decoupled from the slider chain; `zoomToFit()` snaps to 100% when within a small margin of it. Canvas/`GraphicsWidget` work now tracked under [WS10](roadmap_ws10_graphicswidget_overhaul.md). - **M2 β€” `GraphVertex` `QObject` removal + edge-visibility signal batching** βœ… β€” dropped `GraphVertex`'s `QObject` inheritance (its one signal now relayed via plain method calls to `Graph`); measured ~16 bytes/vertex saved (~117 KB on `geom.net`'s 7,343 nodes) plus eliminating `QObjectPrivate`'s separate per-vertex heap allocation. Batched the two bulk edge-visibility call sites (`relationSet()`, `edgeFilterUnilateral()`) from one signal per edge to one per batch. - **GraphicsWidget Performance and Code Quality Overhaul (#250)** βœ… β€” tracked in full under [WS10](roadmap_ws10_graphicswidget_overhaul.md). ## Investigated and Deferred - **M3 β€” move pure data containers out of UI/Qt dependencies.** Two candidate fields were checked and found already correctly placed, not misplaced as originally assumed: `canvasWidth`/ `canvasHeight` are genuine headless layout parameters (read by layout generators and the headless CLI path, not UI leftovers), and `m_clickedEdge`/`m_vertexClicked` already live in the correct layer (`src/graph/ui/`'s click-resolution computation, not passive UI bookkeeping). The one real candidate β€” giving a structural adapter real ownership of `VList m_graph`/`vpos` instead of just reading through `Graph` β€” was checked and rejected: `vpos` is touched at ~140 call sites across 18 files, ownership transfer would buy zero performance (same lookup, same complexity, wherever the hash lives), and there's no currently-blocked test behind it. Deferred indefinitely; revisit only if a concrete need appears. - **M4 β€” relocate `Graph`'s cache flags into explicit cache objects.** The stated motivation (~20 `calculated*` flags with "no shared invalidation mechanism") was checked directly against `Graph::setModStatus()` and found false: a shared mechanism already exists, called from 66 sites, and correctly invalidates at every real structural mutator checked. No live staleness bug found. What's left is coarse-grained invalidation (safe, but recomputes more than strictly necessary) and a real-but-not-currently-felt maintenance tax (new analyses hand-edit the same shared reset block) β€” not enough on its own to justify the refactor. Deferred indefinitely, same as M3. - A `GraphModel` structural adapter was built as a supposed M2 prerequisite, shipped, found to be unused by the commit it was meant to enable, and removed the next day (`b9508c17`) β€” see git history for the full trace, not repeated here. ## What Remains Open Nothing actively scoped. M3/M4 are parked (see above) β€” pick back up only if a concrete need appears, not preemptively. SocNetV-3.7/docs/roadmaps/roadmap_ws4_io_parser_refactor.md000066400000000000000000000034051524402210300241270ustar00rootroot00000000000000# IO / Parser Refactor Roadmap (WS4) ## Goal Reduce tight coupling between `Parser` (Qt signals/threads) and core `Graph` state, while preserving identical parsing semantics and deterministic outputs (golden parity required). ## Status βœ… Done (P1–P6). Current parser/IO architecture (mutation pipeline, file layout, completion signals) lives in [`README_DEVELOPER_NOTES.md`](../README_DEVELOPER_NOTES.md)'s "Parsing and I/O" section, not here. P7 is optional and not started. ## What WS4 Delivered - **P1–P3 β€” Sink-based mutation plane** β€” `IGraphParseSink` introduced as the sole `Parser`β†’`Graph` mutation contract, replacing the previous ad-hoc Qt-signal mutation fan-out entirely (signals removed, not just superseded). GUI and headless CLI now share identical parsing behavior. - **P4 β€” Golden IO coverage** β€” `io_roundtrip` kernel baselines for GraphML, Pajek, Adjacency, DOT, DL, GML, EdgeList; formats without an exporter still get an "export skipped" baseline-locked outcome. - **P5 β€” `ParseConfig` boundary** β€” an immutable `ParseConfig` struct constructed once at the start of `Parser::load()`, replacing scattered individual-parameter defaults across internal parse handlers. No logic/ordering/mutation-stream changes. - **P6 β€” Split `parser.cpp` by format** β€” one translation unit per format under `src/parser/`, done incrementally (one format per commit, golden + benchmark verified each time). `parser.cpp` itself: ~5,500 LOC β†’ ~1,200 LOC. ## What Remains Open - **P7 (optional)** β€” an explicit `ParseTransaction` layer wrapping the mutation stream, for a clearer lifecycle/easier instrumentation. Not started; proceed only if it simplifies the design without altering behavior β€” not required for anything currently blocked. SocNetV-3.7/docs/roadmaps/roadmap_ws5_matrices_modernization.md000066400000000000000000000141421524402210300250310ustar00rootroot00000000000000# Matrices Modernization Roadmap (WS5) ## Goal - Make the matrix subsystem genuinely faster β€” contiguous storage, flat-array APSP results. - Isolate matrix creation and computation into coherent, testable types. - Cancellation-aware algebra kernels. ## Status βœ… Complete. All seven milestones (A1-A7) shipped. `Matrix`'s internal storage and the `QHash`-vs-`Matrix` APSP storage decision are documented in [`README_DEVELOPER_NOTES.md`](../README_DEVELOPER_NOTES.md)'s "Matrix Storage" section, not here. The cancellation flag-delivery mechanism these kernels depend on, and a busy-guard gap found while verifying A6, are tracked in [`roadmap_ws15_cancellation_progress_unification.md`](roadmap_ws15_cancellation_progress_unification.md). ## What WS5 Delivered - **A1 β€” Inventory**: catalogued `Graph`'s named `Matrix` fields and every construction site scattered across `src/graph/`'s slice directories, prerequisite for A4. - **A2 β€” APSP storage migration** (v3.7): `Graph::m_apspDist`/`m_apspSigma` (`QHash`) replaced per-vertex `GraphVertex::m_distance`/`m_shortestPaths` QHash storage, migrated in three golden-verified steps. 15-17% faster at N=1,000, 9% faster at N=7,343 (`geom.net`), 1-3% faster end-to-end in the GUI. - **A2.0 β€” Empirical validation** (v3.7): `matrix_storage_bench.cpp` compared `QHash` vs `Matrix` lookup storage at N=1,000/7,343 across three topologies β€” `Matrix` won lookup speed 22Γ—-32Γ— in every configuration and memory too for any topology with one dominant connected component. - **A3 β€” Contiguous storage** (P1): replaced `MatrixRow*` (one heap allocation per row) with a flat `qreal*` buffer plus a precomputed row-pointer index. Allocation count 7,344β†’1 at `geom.net` scale; measured 38-64% faster construction and 14-19% faster cell-access lookup across all three A2.0 topologies (isolated benchmark, `--structure matrix` mode). End-to-end: no measurable change, expected since `DistanceEngine`'s BFS traversal dominates total work over matrix writes on that workload. Fixed three real bugs found during the accompanying doxygen pass (`operator*`'s wrong result shape, `swapRows()`/`multiplyRow()` using `rows()` instead of `cols()`, `productSym()` dead code). Also added `prominence --bench` support (`kernel_prominence_v4.cpp`, `PROM_BA500_M3` case) since A3's own end-to-end evidence needed a kernel exercising `WM`/`invM`, which `distance`-kernel benchmarks never touch. - **A4 β€” Isolate construction into `matrices/`**: moved the four genuinely self-contained construction functions (`graphMatrixShortestPathsCreate`/`graphMatrixDistanceGeodesicCreate` β†’ new `matrices/graph_matrix_distances.cpp`; `createMatrixReachability`/`graphWalksMatrixCreate` β†’ new `matrices/graph_matrix_reachability.cpp`). Left `WM`/`invM` (`centralityInformation()`) and `CLQM` (`graphCliques()`/`graphCliqueAdd()`) in their algorithm slices β€” both inseparable from their surrounding computation, not standalone construction. Deleted the dead `sumM` field (declared and cleared on graph reset, never populated anywhere). - **A5 β€” Cancellation-aware algebra kernels** (I1, I2): `Matrix::inverse()`/`powerIteration()` gained an optional `cancelCheck` parameter, checked once per outer-loop iteration, wired at `createMatrixAdjacencyInverse()`/`centralityInformation()`/`centralityEigenvector()`. Live testing found the flag-delivery mechanism itself broken (a Graph/MainWindow wiring issue, not `Matrix`) β€” fixed in WS15 P1. - **A6 β€” Cancellation in `distancesMatrix()`/`similarityMatrix()`/`pearsonCorrelationCoefficients()`** (I4 β€” corrected from `writeMatrix()`'s dead `MATRIX_DISTANCES_*` switch-cases to the real live call path once traced): same `cancelCheck` convention as A5, wired at their three `Graph::` callers in `similarity/graph_similarity_matrices.cpp` and post-call checks in their three `write*` callers. Found and fixed a related crash during live-testing: the toolbox dock panel's comboboxes (plain `QComboBox`es, not `QAction`s) were never covered by `MainWindow::setAppBusy()`'s disable sweep, letting a second report race a still-running one on shared `Graph::` matrix members β€” reproduced as a `SIGSEGV` in `Matrix::item()`. Fixed by disabling `leftPanel` (the toolbox's container) in `setAppBusy()` alongside `menuBar()`/`toolBar`/`graphicsWidget`. - **A7 β€” Golden coverage for matrix operations**: delivered via [`roadmap_ws6_testing_ci_regression.md`](roadmap_ws6_testing_ci_regression.md)'s WS6.7 (`kernel_matrix_v8`) β€” full coverage audit, design decisions, and empirical XSM/CLQM timing findings live in that doc. ## What Remains Open - **C1 β€” `createMatrixAdjacencyInverse()` reports `invertible=true` for a mathematically singular matrix** ([#269](https://github.com/socnetv/app/issues/269), found while building WS6.7's `kernel_matrix_v8`). Its singularity check inspects the LU-decomposition *result* for nonzero off-diagonal entries rather than pivot magnitude *during* decomposition β€” a singular input produces numerical garbage (confirmed on `TinyPath_N3_E2`: `1e+20`/`-1e+20` entries) that still passes the check. Information Centrality is the real downstream consumer at risk. Proposed fix (check pivot magnitude in `ludcmp()` against a relative, not absolute, tolerance) not implemented; natural fit alongside `Matrix::inverse()`'s internals whenever picked up. - The dead `MATRIX_DISTANCES_EUCLIDEAN`/`HAMMING`/`JACCARD`/`MANHATTAN`/`CHEBYSHEV` switch-cases in `writeMatrix()` (`graph/reporting/graph_reports.cpp:5663-5919`) β€” unreachable, no caller passes those enum values. Found while scoping A6; not removed. - **Perf baseline recording for A3/A6-touched kernels** β€” deferred to the v3.7 tag per the project's clean-tagged-release rule (baselines are never recorded against `develop` HEAD). An interim, non-committed reading exists for `PROM_BA500_M3` (this machine, current `develop`: median 40ms) for reference until the real recording happens at the tag. - `README__RELEASE_PROCEDURE.md` has no step for (re-)recording perf baselines at release time β€” found while checking the above; worth adding. SocNetV-3.7/docs/roadmaps/roadmap_ws6_testing_ci_regression.md000066400000000000000000001037271524402210300246610ustar00rootroot00000000000000# Testing / CI / Regression Roadmap (WS6) ## Goal Prevent silent regressions during modernization. ## Status 🚧 Ongoing, supporting workstream (no fixed end state β€” continuously active underneath every other workstream). CI integration (WS6.5) is explicitly last and not started. ## Background Both GUI and CLI load graphs through the same IO mutation pipeline introduced in WS4 (`Parser` β†’ `IGraphParseSink` β†’ `Graph`), via `tools/headless_graph_loader.h`, which blocks on `Graph::signalGraphLoaded` (falling back to `Parser::finished`). This is what makes CLI kernel output valid regression evidence for GUI-triggered behavior, rather than a separate code path being tested in isolation. `socnetv-cli` itself is a thin faΓ§ade (argument parsing + dispatch only) over kernel translation units under `src/tools/cli/kernels/`. The full kernel list, CLI flags, and JSON schemas live in [`docs/SOCNETV_CLI_REGRESSION_TOOL.md`](../SOCNETV_CLI_REGRESSION_TOOL.md) β€” not duplicated here. > **Before committing any change described in this file:** run > `./scripts/run_golden_compares.sh`. All golden JSON baselines must still pass. --- ## What WS6 Delivered ### Regression scripts The test harness is primarily exercised via scripts: ``` scripts/run_golden_compares.sh scripts/run_benchmarks.sh scripts/run_golden_io_roundtrip.sh scripts/run_io_roundtrip_shipped_datasets.sh ``` Key properties: - Golden comparisons enforce deterministic algorithm outputs and IO stability. - Benchmarks enforce performance guardrails against per-platform baselines. - IO roundtrip baselines are committed in-repo under `src/tools/baselines/io_roundtrip/`. Perf baselines (`scripts/perf_baselines//perf_expected.env`) are re-recorded from a clean `v3.6` tag checkout, not arbitrary `develop` HEAD β€” a stale pre-M1-speedup baseline once made every benchmark misleadingly report "30-70% faster than baseline" regardless of what was actually being tested. All three platform sets (macOS arm64/M5, Linux x86_64) are current as of this writing. ### WS6.6 β€” Canvas rendering performance kernel (#240) βœ… Done (v3.7) Goal: The existing golden harness covers computation kernels (distance, prominence, clustering, etc.) but has no coverage of the graphics layer. A rendering-performance kernel would let us catch regressions in `GraphicsWidget` / `GraphicsEdge` / `GraphicsNode` paint paths automatically. **Approach taken β€” deviates from the original plan below, for a concrete reason.** The original idea (kept for context, see "Original approach" below) was a headless `socnetv-cli` kernel using `QOffscreenSurface`/`QImage`. That doesn't fit this codebase: `socnetv-cli` (`src/tools/socnetv_cli.cpp:28`) constructs a `QCoreApplication`, not a `QApplication`, and `GraphicsWidget`/`QGraphicsScene` are QtWidgets classes that need a real `QApplication` to behave correctly. Retrofitting `QApplication` into the CLI tool for one kernel is exactly the kind of `GraphicsWidget`/canvas change that should be verified live rather than assumed to work. Instead, this extends the already-proven WS12 `--interactive-script` mechanism (`roadmap_ws12_cli_scripting_mode.md`), running the real `SocNetV` GUI binary with `QT_QPA_PLATFORM=offscreen` (Qt's standard headless-widget-testing mechanism β€” no visible window, same paint/geometry code path as a real session): - Five new interactive-script commands (`mainwindow.cpp`): `render` (forces a synchronous `viewport()->repaint()`, timed), `bulk-node-size ` (`slotEditNodeSizeAll()`), `bulk-edge-color ` (`slotEditEdgeColorAll()`), `move ` (`Graph::vertexPosSet()`), and `quit` (ends the script cleanly β€” needed because nothing previously stopped a scripted GUI run; every earlier manual test had to be killed externally). Each of the first four logs one `qInfo() << "BENCH ..."` line, same pattern as WS14's `distances`/`distances centralities` commands. WS12's later uniform-BENCH-logging pass extended this to every interactive-script command, not just these four β€” relevant below. - Fixed reference script: `scripts/fixtures/render_perf_script.txt` β€” generates a reference network via `erdos-m 2000 40000 undirected` (a new command, `Graph::randomNetErdosCreate()`'s existing `G(n,M)` mode exposed to the interactive-script grammar) rather than depending on an external dataset file, then runs the fixed sequence: render, bulk-node-size, bulk-edge-color, move, render, quit. **Originally used `erdos 2000 0.02 undirected` (G(n,p)) instead** β€” its own code comment already says plainly it's "deterministic-shape (though not deterministic-content, since it's still randomized)": node count is exact, but edge count only concentrates around its expected value rather than landing on it exactly. `erdos-m`'s `G(n,M)` mode places exactly M edges, so N and E are now both exactly reproducible run to run (which specific edges land where is still randomized, which doesn't matter for a timing kernel). - Driver: `scripts/run_render_perf_bench.sh`, structured to match `run_benchmarks.sh`'s existing conventions (`scripts/lib/find_socnetv_gui.sh` mirrors `find_socnetv_cli.sh`; same auto-detected/overridable baseline-set resolution; same `--record` bootstrapping flow). Runs `QT_QPA_PLATFORM=offscreen --interactive-script ` `RENDER_BENCH_RUNS` times (default 7), parses the five `BENCH` lines per run, and compares each metric's **median-of-N** `elapsed_ms` against an upper-bound threshold (2Γ— the recorded median-of-N reference run) β€” "must be faster than X ms", not exact equality. - Baseline: `scripts/perf_baselines/macos-arm64/render_perf_expected.env` and `scripts/perf_baselines/macos-m5/render_perf_expected.env`, both recorded from this machine (mirrors the compute-benchmark convention of keeping a generic per-arch baseline alongside a chip-exact one). `linux-x86_64` doesn't have a recording yet β€” the script degrades gracefully (prints `SKIP` per threshold) when no baseline file exists for the current machine, rather than failing; someone running `--record` there once is the way to add coverage for that platform. **Not yet verified on Linux** β€” `QT_QPA_PLATFORM=offscreen` availability/behavior there hasn't been checked live. **Three real bugs found and fixed while re-verifying this kernel, not just a documentation pass:** 1. **The "degrades gracefully" claim above was false until now.** The script's missing-baseline check did `exit 2` with a hard `ERROR` when the baseline *file* didn't exist at all β€” the SKIP path only ever covered a file that exists but is missing one specific variable. Concretely: this exact recording machine (a MacBook Pro M5) auto-resolves to the generic `macos-arm64` baseline set (`uname -m` can't distinguish Apple Silicon generations), which wasn't recorded at the time β€” only `macos-m5` was. So running the script with no explicit override failed outright on the very machine it was recorded on. Fixed: the missing-file case now prints an `INFO` notice and continues with every metric legitimately `SKIP`ped, matching what this doc always claimed (and `macos-arm64` is now recorded too, so the failure mode no longer triggers here anyway). 2. **BENCH-line parsing was silently misaligned.** The comparator greped every line containing `"BENCH "` and read the first five as `render, bulk-node-size, bulk-edge-color, move, render` by fixed position. That held when this kernel first shipped, but WS12's later uniform-logging pass added `BENCH` lines to every command, including the fixture's own setup/teardown (`erdos`/`erdos-m`, `delay`, `quit`) β€” nobody re-tested this script after that landed. The comparator was silently reading `erdos-m`'s and `delay`'s timings as `RENDER_INITIAL` and `BULK_NODE_SIZE`, while the real `move` and second `render` measurements were dropped entirely (only the first five lines were ever read). It still printed "OK" throughout, since the mismatched values all happened to be small millisecond timings that stayed under threshold by coincidence β€” this was a real, live correctness gap, not just stale docs. Fixed: the grep now allowlists the four metric command names (`^BENCH (render|bulk-node-size|bulk-edge-color|move) `) instead of matching any `BENCH` line, which is robust against the fixture gaining more setup/teardown commands later without needing this filter touched again. 3. **Single-shot measurement made both the recording and the comparison noisy.** `--record` originally ran the fixture exactly once and doubled its raw readings; a repeat-run check found `render`'s own timing swinging ~104-131ms (~25%) run to run on identical hardware/build. Two baselines recorded minutes apart from the literal same machine (`macos-arm64` vs `macos-m5`, before this fix) differed by up to 24% on `BULK_NODE_SIZE` purely from that noise, not from any real difference between the two "targets." Fixed: the script now runs the fixture `RENDER_BENCH_RUNS` times (default 7, override via env var) and uses the per-metric **median** across runs for both recording and comparison β€” matching `run_benchmarks.sh`'s own median-of-N convention for the headless compute kernels, just implemented as an external loop here since each run launches a fresh GUI process rather than looping inside one CLI invocation. Re-recorded both `macos-arm64` and `macos-m5` from scratch against the corrected parser, the new deterministic fixture, and median-of-7: the two are now within a few ms of each other on every metric, confirming the previous divergence really was recording noise. **On-screen vs. offscreen timing β€” measured, not assumed.** Ran the same fixture script both ways on the recording machine, 3 times each, to check whether `QT_QPA_PLATFORM=offscreen` changes what's being measured: | Command | On-screen | Offscreen | |---|---|---| | `render` (initial) | ~297-303ms | ~131-158ms | | `bulk-node-size` | ~44-49ms | ~46-52ms | | `bulk-edge-color` | ~45-78ms | ~44-45ms | | `render` (after mutations) | ~294-303ms | ~120-138ms | `render`'s actual paint cost is consistently ~2Γ—-2.4Γ— faster offscreen β€” no compositor/backing-store round trip on macOS β€” but the non-paint bulk operations measure the same either way. Confirmed the `render` command's own timing is genuinely synchronous (not partially deferred) by running two `render` calls back-to-back with a 2-second gap between them: both reported the identical elapsed time, which is what you'd expect from `QWidget::repaint()`'s documented synchronous contract (unlike `update()`, which only schedules a repaint) and wouldn't hold if any part of the cost were being silently deferred past the call returning. Conclusion: offscreen thresholds are only meaningful against other offscreen runs, not as a claim about real on-screen user-perceived performance β€” which is fine, since this kernel's job is regression detection, not UX measurement. **Recorded reference run (MacBook Pro M5, 24GB RAM, offscreen, N=2000/~39,800E):** `render`=154ms, `bulk-node-size`=46ms, `bulk-edge-color`=44ms, `move`=0ms, `render`-after-mutations=120ms. Thresholds recorded at 2Γ— each (with a 15ms floor for near-instant operations like `move`, since doubling a 0-1ms reading would produce a threshold too tight to survive normal jitter). Why this matters: `GraphicsWidget` bulk operations (`setEdgeArrowSize`, etc.) currently fire thousands of individual `prepareGeometryChange()` calls without batching. Phase 1–5 of #240 fix the worst offenders, but without a regression kernel there is no automated guard to prevent the problems returning. **Not done in this pass, and CI integration stays out too** per WS6.5's "CI is last" policy. The bigger open gap: the golden harness still has no *correctness* coverage of the canvas, only performance coverage. Verifying that `GraphicsWidget::setEdgesVisibilityBatch()` actually left the right set of edges visible/hidden after a relation switch or a unilateral-edge toggle has no automated check today β€” the only verification is live manual testing via WS12's `--interactive-script` mechanism (`roadmap_ws12_cli_scripting_mode.md`), built partly for this reason. A future canvas kernel should assert on actual `GraphicsWidget` *state* after a fixed operation sequence (which edges/nodes end up visible, positioned where expected, etc.), not just timing β€” same offscreen-rendering constraint as this kernel, but a state-comparison kernel rather than a timing-threshold one. Could plausibly share this kernel's harness scaffolding, or be a sibling kernel (`kernel_render_state_v9`-shaped) β€” not scoped in detail yet. **This gap is tracked as open work below, under WS6.1.** ### WS6.7 β€” Matrix operation golden coverage (`kernel_matrix_v8`) βœ… Done (v3.7) `Matrix`-producing operations had no direct golden coverage before this β€” every existing kernel only ever checked downstream results (centrality scores, distance values, clique counts), so a subtle get/set indexing bug in a new storage layout (WS5's A3 target) could still produce correct downstream numbers by coincidence. `kernel_matrix_v8` dumps raw matrix contents instead, for all seven categories audited as having no direct coverage: adjacency (`AM`), inverse (`invAM`), distances (`DM`), similarity (a local `SCM`, matching metric), reachability (`XRM`), walks (`XM`, fixed length) and total walks (`XSM`), and clique co-membership (`CLQM`). **New `Graph` accessors** (`matrixAdjacency()`, `matrixAdjacencyInverse()`, `matrixDistances()`, `matrixReachability()`, `matrixWalks()`, `matrixTotalWalks()`, `matrixCliqueCoMembership()`, `graph.h`) expose these previously-private fields read-only-by-convention β€” non-const because `Matrix::item()`/`rows()`/`cols()` are themselves non-const everywhere in the codebase. **Dump format is size-dependent**, decided empirically: - Small fixtures (`TinyPath_N3_E2`, `TinyDisconnected_Undir_N6_E4` β€” reused, no new fixtures added) dump the full NΓ—N grid. - The big/sparse tier uses `Benchmark_BA_Directed_N500_m3` (N=500, already shipped, already used by `run_benchmarks.sh`) rather than an out-of-repo `~/socnetv/library` path, so the baseline reproduces on any machine or in CI. It dumps a compact summary instead of the full grid (250K cells): per-row/column sums, the trace, and five sampled cells (corners + center). - **Total walks (`XSM`) is skipped above N=50** (`kTotalWalksSkipThreshold`, `kernel_matrix_v8.cpp`) β€” measured directly, not assumed: summing matrix powers up to N-1 took **~9.2 minutes (553,940 ms)** at N=500 on the big fixture (MacBook Pro M5, unoptimized build). Clique co-membership needed no such gate β€” **6 ms** on the same run, since Bron-Kerbosch stays cheap on sparse graphs β€” so it's computed unconditionally at every size. **Verified the actual point of this kernel**: manually introduced a one-line off-by-one into `Matrix::item()` (`(c + 1) % m_cols` instead of `c`), reran `run_golden_compares.sh` β€” all three matrix baselines went red, plus several pre-existing kernels that also read `Matrix::item()` indirectly (distance, prominence, reachability, walks). Reverted; suite is clean again. WS5's A3 now has the safety net it was waiting on. ### `kernel_connectivity_v7` β€” weak/strong connected components (#85, #272) βœ… Done - `Graph::graphWeaklyConnectedComponents()` β€” BFS treating all edges as undirected (weak connectivity); caches count in `m_graphWeaklyConnectedComponents` and per-node IDs in `m_vertexComponentId`. Cache invalidated with `resetDistanceCentralityCacheFlags()`. - `Graph::graphStronglyConnectedComponents()` (#272) β€” Tarjan's SCC algorithm (single DFS pass, no graph transpose needed), respecting edge direction; caches count in `m_graphStronglyConnectedComponents`, same invalidation sites as the weak variant. Reports a count only, not per-vertex SCC membership (`m_vertexComponentId` stays weak-only β€” see the function's own doc comment in `graph_distance_facade.cpp`). - CLI: `--kernel connectivity --connectivity-type weak|strong` (default `weak`; ignored on undirected graphs, where the two notions coincide). - Six baselines committed: `TinyDisconnected_Undir_N6_E4` (3 components), `TinyDisconnected_Dir_N5_E3` (2 weak / 5 strong components), `TinyPath_N3_E2` (1 component / connected), plus `__STRONG` variants for `TinyDisconnected_Dir_N5_E3`, `TinyArc_Dir_N2_E1` (1 weak / 2 strong), and `TinyWeaklyConn_Dir_N3_E2` (1 weak / 3 strong). - **Connectivity semantics table** (what the kernel computes and what the UI reports): | Graph type | Topology | Mode | Components | `connected` | UI message | |---|---|---|---|---|---| | Undirected | All nodes reachable | n/a | 1 | true | "connected (1 component)" | | Undirected | N isolated islands | n/a | >1 | false | "disconnected (N components)" | | Directed | Every pair has a directed path | weak | 1 | true | "weakly connected (1 component)" | | Directed | Every pair has a directed path | strong | 1 | true | "strongly connected (1 component)" | | Directed | Aβ†’B only (not Bβ†’A) | weak | 1 | true | "weakly connected (1 component)" β€” one island | | Directed | Aβ†’B only (not Bβ†’A) | strong | 2 | false | "not strongly connected (2 components)" | | Directed | Two separate islands | weak | >1 | false | "disconnected (N weakly connected components)" | **Design rationale:** the GUI's Connectedness action (`slotAnalyzeConnectedness()`) asks the user weak vs. strong for any directed graph, rather than silently picking one β€” see #272. The old approach of treating `isConnected()`/SSSP as the de facto strong-connectivity source of truth is gone; `graphStronglyConnectedComponents()` is now the single, explicit, dedicated method for that question. ### `kernel_vertex_connectivity_v9` β€” local/global vertex connectivity (#7, WS11) βœ… Done - `Graph::graphNodeConnectivity(source, target, respectDirection)` β€” local kappa(s,t), via vertex-split max-flow (Edmonds-Karp). Returns a `NodeConnectivityResult{status, value}`, not a plain int: `Adjacent` (s,t directly connected β€” no finite cut exists) and `Invalid` (nonexistent/equal vertices) are distinct outcomes from `Ok` on purpose, precisely to avoid repeating #271's class of bug (a sentinel int silently misused as a bool/count). - `Graph::graphConnectivity(respectDirection)` β€” global kappa(G), the naive pairwise-minimum over all non-adjacent pairs, pruned by the minimum-degree bound (Whitney's inequality) and an early exit at 0. Deliberately not the smarter O(n) algorithm (Even 1975) - see the function's own doc comment for why. - CLI: `--kernel vertex_connectivity --conn-mode local|global [--conn-source S --conn-target T] --connectivity-type weak|strong`. - Seven baselines committed, deliberately Tiny*/toy datasets only (global mode is O(n^2) local- connectivity computations in the worst case - not something to run against the 500-node `Benchmark_*` datasets used elsewhere in this file): `TinyPath_N3_E2` global (kappa=1) and local, both a non-adjacent pair (1,3 β†’ ok/1) and an adjacent one (1,2 β†’ `Adjacent`, no value); `TinyDisconnected_Undir_N6_E4` global (kappa=0); `TinyWeaklyConn_Dir_N3_E2` global in both weak (kappa=1) and strong (kappa=0, not strongly connected) mode; `TinyComplete_Undir_N4_E6` (new dataset, K4) global (kappa=3=n-1, confirming the complete-graph case needs no special code path - see the function's doc comment). - Also verified against two independent, non-baseline checks: the Petersen graph (`src/data/Petersen_Graph.paj`) returns kappa(G)=3, the textbook value for that well-known 3-regular, 3-connected graph; and live in the GUI (Analyze > Cohesion > Node/Graph Connectivity), which reproduces the same CLI-verified numbers end-to-end. --- ## What Remains Open ### WS6.1 β€” Expand CLI kernel coverage (UI-adjacent functionality, headless) Goal: Expose more β€œUI-visible” functionality through deterministic CLI kernels, so it becomes testable headlessly without the GUI. Examples of high-value additions: - random network generators (deterministic via fixed seeds) - layout / visualization computations runnable headlessly (compute-only; no QtWidgets/QtCharts) - additional analysis workflows that users typically trigger from UI - a canvas *correctness* (not just performance) kernel β€” see WS6.6's "Not done in this pass" note above: no automated check exists today that `GraphicsWidget` state (visible/hidden edges, node positions) after a fixed operation sequence matches expectations, only manual `--interactive-script` testing. Could share WS6.6's harness scaffolding or be a sibling kernel. - **kernel_attribute_import_v7** (not yet built) β€” CSV/JSON attribute import + export roundtrip (#227, #232): - Use `src/data/TinyDir_N2_E1_Attributes.graphml` as the seed graph (2 nodes, 1 edge; heterogeneous custom attrs `Age`/`Party` using `d1000+` keys β€” also covers #208 regression) - Export nodes and edges to CSV and JSON via `TableExport` - Mutate specific attribute values in the exported files - Re-import via `TableImport` + `Graph::vertexAttributesImport` / `edgeAttributesImport` - Assert: mutated attributes are present with correct values; native columns (Label, Size, Color, Weight) were routed to their proper setters; read-only columns were not stored as custom attributes; no duplicate column keys - Assert roundtrip fidelity: export without modification β†’ re-import β†’ golden compare (no change) - Save as GraphML and reload; assert attribute persistence survives the full pipeline Rules: - no behavior changes in existing functionality - outputs must be deterministic (seeded where randomness exists) - each new kernel must have a clear, versioned name (`kernel__vN`) - add `--dump-json` and `--compare-json` support for each new kernel Outcome: More of the application's "user-facing" features become regression-testable without the UI. ### WS6.4 β€” Tighten determinism, measurement stability, and reporting Goal: Reduce false positives and reduce noise sensitivity, especially in IO benchmarks. Possible improvements: - clarify how BUILD_TYPE / configuration is detected and printed by scripts - use median-of-N consistently where useful (especially IO load tests) - separate IO load-time thresholds from compute-time thresholds where needed - document baseline update rules (rare; only for real semantic fixes) #### Known: parallel reduction is FP non-deterministic, with ~2.9Γ— tolerance headroom Found while measuring something else. **Not a live failure β€” a thin margin worth knowing before baselines grow.** The same binary produces a *different* result on every run. Measured: 40 consecutive runs of the `StokmanZiegler_Netherlands` weighted case (`-c 1 -w 1 -x 1`) produced **40 distinct outputs**. Cause is floating-point summation order in WS3 M1's parallel BC/SC reduction β€” thread scheduling decides the accumulation order, and FP addition isn't associative. Affects `BC`, `SBC`, `PC`, `SPC` and `metrics.avg_distance`. This is currently absorbed by the harness: per-node fields compare with a **relative** tolerance of `1e-15` (`kernel_distance_v1.cpp:186` β†’ `almostEqual()`, `cli_common.cpp:129`), and graph-level `avg_distance` via `cmpNumStrTol(..., 1e-15)`. All 40 golden compares passed. The concern is the margin. Worst observed relative spread across 25 runs: **3.4e-16** against a `1e-15` tolerance β€” **2.9Γ— headroom**, no more. That margin shrinks as thread count and accumulation chain length grow, and WS6.2 explicitly plans to add *larger* datasets. A `geom.net`-scale baseline (7343 nodes) could plausibly exceed `1e-15` and turn the suite intermittently red, which is the worst possible failure mode for a regression harness β€” flaky, unreproducible, and easy to misattribute to whatever change happened to be in flight. Also worth correcting: `cli_common.cpp:46` documents `d2s()` as *"Deterministic string for golden compare (avoid float parse/format differences)"* β€” the formatting is deterministic, but the value being formatted is not. The comment currently implies a guarantee the engine stopped providing when the source loop was parallelised. #### This is a harness-precision question, not a results-precision one Worth stating plainly, because it decides the fix: **no user ever sees the digits that vary.** SocNetV's reports already render real numbers at **6 significant digits** β€” `m_reportsRealPrecision` defaults to 6 (`graph.cpp:112`, mirrored by `appSettings["initReportsRealNumberPrecision"]`, `mainwindow.cpp:514`) and is user-configurable via Settings; every report writer applies it through `outText.setRealNumberPrecision(m_reportsRealPrecision)`. A drift at the 16th significant digit is invisible in every report SocNetV produces, and no researcher is interpreting a centrality score past 2–3 digits anyway. So the 17-digit `d2s()` serialization and the `1e-15` compare tolerance exist **only inside the golden harness**. They are a regression detector, not a result. The harness is currently about **eleven orders of magnitude stricter than the product's own output resolution** β€” which is why it is the harness, not the engine, that should give. **Choosing a tolerance.** Two bounds, and a lot of room between them: - *Floor* β€” must sit above FP noise: `3.4e-16` today, and rising with thread count and network size. - *Ceiling* β€” must sit below the smallest difference that indicates a real defect. A genuine semantic regression (wrong formula, wrong normalisation, wrong traversal, off-by-one in a path count) moves the leading significant digits β€” relative differences of `1e-3` and up, not `1e-15`. A useful anchor: a difference invisible at the product's own 6-digit display is arguably not an observable regression at all. Something around `1e-9` sits ~7 orders above the noise floor and ~3 below display resolution, and would catch every class of real regression the harness is actually there to catch. The exact figure should be argued and recorded in the code, not just bumped until the suite goes quiet. **Two things not to change while doing this:** - **Keep `d2s()` at 17 digits.** The baseline file should losslessly record what actually happened; rounding at serialization creates boundary artifacts (`0.4999995` and `0.5000005` round apart while being equal to within any sane tolerance). Compare-time tolerance is strictly more robust than round-then-compare. - **Keep integer-valued results on exact comparison.** `diameter`, `disconnected_pairs`, component IDs, triad/clique census counts and similar go through `cmpInt`, not `almostEqual`. Those are counts, they are exact, and an off-by-one there is a real bug β€” no tolerance should ever be applied to them. The split already exists; preserve it. **The alternative β€” determinism by construction β€” is not free.** Slots are assigned by *thread identity* (`distance_engine.cpp:487-499`): a `QHash` hands each OS thread a slot on first entry, and `QtConcurrent::blockingMap` distributes sources across threads however the scheduler happens to. The final reduction over `allStates` *is* already fixed-order β€” the drift comes from the *partitioning*, i.e. which sources' BC/SC contributions get grouped into which partial accumulator, not from the merge. Making that deterministic means statically partitioning `sources` into P fixed chunks and mapping over chunk descriptors instead of over individual sources, which trades away `blockingMap`'s dynamic load balancing β€” and sources differ substantially in cost, so that could eat into M1's measured 2.7×–8.3Γ—. **Unmeasured; do not assume either way.** Given that the tolerance argument above resolves the problem without touching the engine at all, this is the fallback, not the first move. **Suggested order:** widen the tolerance with the reasoning recorded β†’ then measure the actual spread at `geom.net` scale (7343 nodes) to confirm the new margin holds where WS6.2 is heading. Reproduce: run the same `--dump-json` invocation N times and hash the output with `_ms` fields stripped. ### WS6.2 β€” Systematically expand datasets and coverage Goal: Increase confidence by testing more networks and more edge cases in a structured way. Approach: - grow the dataset suite gradually - include representative small/medium/large graphs - include tricky parser edge cases per format (GraphML/DOT/Pajek quirks) - where formats lack exporters, keep using export-skipped baseline locking - prefer shipped datasets under `src/data` where possible; add external datasets only if licensing permits Rules: - add datasets incrementally - baseline additions must be reviewed (do not bulk-regenerate) ### WS6.3 β€” Refactor the golden harness scripts for modularity Problem: `run_golden_compares.sh` currently does a lot and can be noisy. Goal: Split goldens into subscripts and keep a master runner that can execute: - all suites (default) - one suite - a selected subset Direction: - create per-suite scripts (examples): - `scripts/goldens/golden_distance.sh` - `scripts/goldens/golden_reachability.sh` - `scripts/goldens/golden_walks.sh` - `scripts/goldens/golden_prominence.sh` - `scripts/goldens/golden_io_roundtrip.sh` - add a master runner that supports: - `--list` (print available suites) - `--only ` - `--skip ` - default: run all Outcome: Faster local workflows and easier diagnosis when one suite fails. ### WS6.5 β€” CI integration (LAST) Goal: Once local harness coverage is broad and stable, add CI checks to prevent regressions from landing silently. Policy: CI must not become the primary place where developers discover breakage. CI should run a carefully chosen subset by default: - build (Release preferred) - a "fast" golden subset - optionally a "fast" benchmark subset Heavier suites can run nightly or on-demand. ### WS6.8 β€” Independently audit pre-existing golden baselines for mathematical correctness **Status: deferred until after the v3.7 release** β€” queued during the Katz/Bonacich work (2026-08) but explicitly postponed given the v3.7 release deadline; pick this up first thing once v3.7 ships. Goal: Golden baselines catch *regressions* (today's output differs from yesterday's), but say nothing about whether the *original* baseline was ever mathematically correct. Every baseline currently in `src/tools/baselines/` was accepted once, at dump time, without independent verification against a hand-computable ground truth. Motivating precedent: the Katz/Bonacich verification work this session (2026-08) caught two real bugs this way that ordinary regression testing had already been passing cleanly against its own (silently wrong) baselines: - The `Matrix::powerIteration()` divide-by-zero substitution (`norm = 1`) leaking into the reported `lambdaMax` on nilpotent (directed, cycle-free) matrices β€” every golden run agreed with itself, because the bug was baked into the baseline from the start. - A stale-cache bug where a rejected alpha/beta silently short-circuited every later recompute β€” invisible to golden compares since each baseline is dumped once, not re-run with changing inputs. Both were found only by hand-deriving expected values independently (plain Python, Gauss-Jordan elimination, no numpy) against small, deliberately-constructed test networks, then comparing against what the app actually produced β€” not by trusting the existing baseline as ground truth. Suggested approach (risk-based, not exhaustive β€” see below): - Prioritize edge cases most likely to hide latent bugs, per the pattern above: directed + nilpotent/cyclic structure, isolates, self-loops, zero-weight edges, disconnected components, weighted + inverted-weight combinations, and any kernel that recently changed (`vertex_connectivity`, `connectivity`, `matrix` are the newest families and haven't had this treatment at all yet). - For each flagged case, hand-derive the expected result independently (small enough networks that this is tractable by hand or a short verification script) and compare against the current baseline β€” not just against the app's current output, since the app could be self-consistently wrong. - Where a baseline is found to be wrong, follow the same discipline used for the `powerIteration` fix: confirm the fix is unambiguously correct, understand exactly what changes and why, get explicit sign-off before touching previously-"passing" baselines, then re-dump with a clear commit explaining what was wrong and how it was verified. - Given the scope (9 kernel families, ~78 baseline files as of 2026-08), decide the audit's actual depth (representative sample vs. exhaustive vs. risk-based-only) when this is picked back up, rather than assuming exhaustive coverage is the goal by default. ### Open findings #### `run_benchmarks.sh` reports `BUILD_TYPE=Debug` even against a Release binary Script-reporting detail, not a functional bug, but it can confuse future contributors reading benchmark output. Good tiny WS6 task whenever there's a lull. #### Continuous release page shows a stale "published" date (#255 follow-up) `build-ci.yml`'s "Update continuous release description" step (`ubuntu-latest` job) `PATCH`es the existing `continuous` release's body on every `[ci]` run, but never touches its `published_at` timestamp β€” GitHub's releases page prominently displays that timestamp, so the page can look outdated (currently stuck at 2025-02-25) even though the body text and uploaded artifacts are fresh from the latest run. Proper fix is to delete and recreate the release each run instead of updating in place, which needs care: the four OS jobs currently upload artifacts to the existing `continuous` tag independently and in parallel, so the recreate step would need to run once, before any of the parallel upload steps, to avoid a race. Separately, the same script computes `commitMessage` (the commit's subject line) but never uses it in the description text β€” worth including once this is revisited. --- ## Work Rules - Keep outputs stable (version schemas when changing format). - Baseline regeneration should be treated as exceptional. - Any "FAIL" in benchmarks must be investigated; if it is noise, prefer mitigation via more stable measurement rather than loosening thresholds by default. - WS6 work should remain incremental: small changes, deterministic evidence, and consistent scripts. SocNetV-3.7/docs/roadmaps/roadmap_ws7_mainwindow_decomposition.md000066400000000000000000000143541524402210300253770ustar00rootroot00000000000000# MainWindow Decomposition Roadmap (WS7) **Tracked as:** [#257](https://github.com/socnetv/app/issues/257) ## Goal Make `MainWindow`'s implementation navigable and fast to build incrementally by physically splitting its monolithic `.cpp` into focused per-responsibility files under `src/mainwindow/` β€” mirroring WS2/F2's mechanical split of `graph.cpp`. **Not a class-decomposition effort**: `MainWindow` remains exactly one class, same signatures, same behavior, same `mainwindow.h`. (Originally scoped as a bigger controller-extraction effort β€” MW1–MW7, see Investigated and Deferred below β€” that turned out not to be needed for the actual problem: file size and navigability, not testability or coupling. No standing plan to revisit that.) ## Status βœ… Complete. `mainwindow.cpp`: 18,540 β†’ 588 lines (constructor + destructor only, matching `graph.cpp`'s post-F2 shape). ~19,100 lines across 28 files under `src/mainwindow/`. ## Background ### Motivation After WS2, `Graph` is a clean faΓ§ade split across `src/graph/`. The UI side had not received the same treatment: `MainWindow` owned all menus, toolbars, dock widgets, status bar, and dialogs, all implemented in one 18,540-line `.cpp`. Confirmed by direct reading before starting: ~235 slots follow the `slotXxx` naming convention; `statusBar()->showMessage(...)`/`statusMessage(...)` is called 359 times directly; `appSettings[...]` is accessed 304 times across ~37 methods; 20 distinct `Dialog*` classes are instantiated directly with `new`; `initMenuBar()` alone was 448 lines. This made the file expensive to navigate and slow to incrementally rebuild β€” the concrete, felt pain that prompted this workstream. ### Non-Goals * No behavior changes, no visual/UX changes, no Qt version changes, no new features. * No new classes β€” `MainWindow` stays one class throughout. ### Related Finding: Progress-Dialog Duplication & Unwrapped GUI-Blocking Operations Found while scoping WS5 A5 (cancellation-aware algebra kernels) β€” a behavioral finding, not a pure structural extraction, and it turned out to be tangled up with a broader cancellation/threading problem bigger than either WS5 or WS7's own scope. **Moved to [`roadmap_ws15_cancellation_progress_unification.md`](roadmap_ws15_cancellation_progress_unification.md) (P3)** β€” the full call-site survey lives there now, alongside the cancellation-delivery fix and crash-bug fix it shares root cause with. ## What WS7 Delivered ### MW0 β€” Mechanical File Split (F2-style) Same shape as WS2/F2: `mainwindow.h` untouched throughout (single 870-line header); method bodies moved verbatim (no internal restructuring) into new files under `src/mainwindow//`, grouped by the existing `slotXxx` naming convention (which already mirrors the menu structure). Landed in 6 commits, one per directory/batch, each independently build-verified, golden-compared, and smoke-tested (headless `--interactive-script` + a full ASan pass) before moving to the next: | Directory | Files | What moved | |---|---|---| | `lifecycle/` | 1 | `closeEvent`, `terminateThreads`, `resizeEvent` | | `init/` | 5 | Startup: settings load/save, all `QAction` construction, menu/toolbar, dock panels, remaining `init*()` | | `network/` | 5 | Network menu: file I/O, import/export, random generation, web crawler, view | | `edit/` | 6 | Edit menu: node, edge, selection/drag-mode, relation, filters, 3 shared helpers | | `analyze/` | 8 | Analyze menu: centrality/prestige, distance, matrices, cohesion, clustering, structural equivalence, prominence, shared edge-weights prompt | | `dispatch/`, `scripting/`, `layout/`, `options/`, `help/` | 1 each | WS15 async-dispatch helpers, WS12 interactive-script dispatcher, Layout menu, Options/Settings, Help menu | Two real, pre-existing bugs were found and fixed along the way (not introduced by the move itself β€” confirmed via `lldb`/AddressSanitizer against unmodified `develop` HEAD in both cases): * **`closeEvent` dangling-pointer crashes**: `graphicsWidget` and `scene` were explicitly deleted mid-handler while Qt's own window-close teardown continues dispatching events to them immediately afterward, in the same close sequence. Neither delete served a purpose beyond what the OS already reclaims at exit (`MainWindow` itself is heap-allocated in `main.cpp` and never explicitly destructed). A related crash, `editNodePropertiesAct`/`editNodeRemoveAct` deleted while still attached to a live toolbar, was fixed the same way. * **`printerPDF` double-free**: `slotNetworkExportPDF()` deleted the member pointer internally without nulling it, so `closeEvent()`'s own delete on quit double-freed it on any session that had exported a PDF at least once. One process finding: two overloaded methods (`runGraphOperationAsync`, and the 4 `slotLayout*` `...ByProminenceIndex` methods) were initially only half-moved by the extraction script β€” its dedup logic correctly discards a same-name match that's just a self-referencing `qCDebug` log line, but incorrectly discarded a second match that was a genuine C++ overload. Caught by a post-split sweep for leftover `MainWindow::` definitions in `mainwindow.cpp`; both stranded overloads moved to sit beside their sibling. **AddressSanitizer debug builds** (used throughout to verify each slice) are now documented as a standing tool in `README_DEVELOPER_NOTES.md`, not a one-off β€” configure/build/run instructions and how to read a report. ## Investigated and Deferred **MW1–MW7 (controller/panel extraction)**: the original plan carved `MainWindow` into `StatusBarController`, `AppMenuController`, `DialogManager`, `AppSettingsController`, `CanvasPanel`, and a thin coordinator `MainWindow`. Considered again once MW0 landed β€” same conclusion the project already reached for `Graph` in WS3 (see `roadmap_ws3...md`'s M3/M4): the stated, concrete pain was file size/navigability, which MW0 fully addresses. Controller extraction would buy testability/decoupling with no current concrete blocker behind it, in a codebase that has no unit-test framework to make "testability" pay off directly β€” speculative architecture, not a measured problem. Deferred indefinitely; revisit only if a concrete need appears (e.g. a real bug traced to tangled responsibilities, or an actual testability blocker). ## What Remains Open Nothing actively scoped. SocNetV-3.7/docs/roadmaps/roadmap_ws8_io_layer_stabilization.md000066400000000000000000000167531524402210300250340ustar00rootroot00000000000000# IO Layer Stabilization Roadmap (WS8) ## Goal Consolidate per-format IO dispatch β€” parsing, export, file-dialog filters, and single-relation warnings β€” behind a single `FormatHandler` registry, replacing four hand-maintained switch statements that currently must be kept in sync by hand (see Background: the GML/TWOMODE export gap is exactly the kind of drift that produces silently). One handler per format, registered once: ```cpp struct FormatHandler { FileType type; QString displayName; // "Pajek", "GraphViz DOT", ... QString fileDialogFilter; // "Pajek (*.net *.paj *.pajek)" bool supportsExport; bool singleRelationOnly; // drives the mainwindow.cpp:12162 warning std::function parse; std::function exportTo; // nullptr if !supportsExport }; ``` A single `QList` (or `QHash`) becomes the one source of truth: `Parser::load()`'s switch becomes a lookup + uniform call; `Graph::saveToFile()`'s switch becomes the same; the two `mainwindow.cpp` switches become lookups into the same table's `displayName`/`fileDialogFilter`/`singleRelationOnly` fields. **Non-goals (matching WS4's own constraints):** no parsing behavior changes, no numeric/semantic output changes β€” this is purely consolidating *dispatch*, not touching what any format parser does internally. Golden regression parity required throughout, same as every other workstream. ## Status πŸ“‹ Design drafted, not yet started. Previously three aspirational bullet points in `ARCHITECTURAL_REFACTORING_ROADMAP.md` with no dedicated file β€” fleshed out here after confirming via direct code reading that the scope is real and distinct from WS4 (not a duplicate/leftover). ## Background ### Relationship to WS4 WS4 (`roadmap_ws4_io_parser_refactor.md`, complete) achieved: - A shared mutation contract (`IGraphParseSink`) β€” parsing mutations flow through one interface. - A shared `ParseConfig` struct instead of long parameter lists. - Translation-unit separation β€” each format's parsing logic lives in its own `parser_*.cpp`. WS4 never touched **dispatch** β€” how the app decides *which* format-specific function to call, and where each format's metadata (display name, file extensions, capabilities) lives. That's WS8. ### Current Reality β€” four places hand-maintain the same per-format knowledge Confirmed by direct reading, not assumption: 1. **`Parser::load()`** (`src/parser.cpp:207`) β€” a 10-case `switch(fileFormat)` calling `parseAsGraphML(rawData)`, `parseAsPajek(rawData)`, `parseAsAdjacency(rawData, cfg, delimiter)`, `parseAsEdgeListWeighted(rawData, delimiter)`, etc. **The per-format functions don't even share a uniform signature** β€” some take just `rawData`, others take `rawData, delimiter`, others `rawData, cfg, delimiter`. Adding a new format means adding a new case here by hand. 2. **`Graph::saveToFile()`** (`src/graph/io/graph_io.cpp:225`) β€” a parallel 7-case `switch(fileType)` calling `saveToPajekFormat(...)`, `saveToAdjacencyFormat(...)`, etc. Only 7 of the 9 importable formats have an export case (no `GML` or `TWOMODE` export) β€” this asymmetry is currently only discoverable by reading both switches side by side. 3. **`MainWindow`'s file-dialog filter switch** (`src/mainwindow.cpp:6395`) β€” a third switch mapping each `FileType` to its display name and file-extension filter string (e.g. `"Pajek (*.net *.paj *.pajek);;All (*)"`), hand-kept in sync with the two above. 4. **`MainWindow`'s single-relation-warning switch** (`src/mainwindow.cpp:12162`) β€” a fourth, partial switch mapping a subset of formats (`ADJACENCY`, `GRAPHVIZ`, `EDGELIST_WEIGHTED`, `EDGELIST_SIMPLE`) to a human-readable name, used only to warn the user that a multi-relation graph will lose relations on export to a single-relation-only format. Every one of these four locations must be updated by hand whenever a format is added, renamed, or gains/loses a capability (e.g. export support, multi-relation support). Nothing enforces they stay in sync β€” the GML/TWOMODE export gap above is exactly the kind of drift this produces silently. ## What WS8 Delivered Nothing shipped yet. ## What Remains Open ### Milestones - **W8.1 β€” Inventory and uniform signature.** Catalog the exact current signature of every `parseAs*` function; design the single uniform signature (likely `bool parse(const QByteArray&, const ParseConfig&, IGraphParseSink*)`, since `ParseConfig` already carries `delim` β€” meaning the delimiter-only-vs-full-config inconsistency in point 1 above can be resolved as part of this step, not left as-is). Adjust each `parseAs*` function to match. No dispatch changes yet β€” completion criteria: build passes, golden regression unchanged. - **W8.2 β€” Introduce `FormatHandler` struct and registry.** Add the struct above (see Goal) and a `QList` populated with all 9 import formats + 7 export formats, built from the actual current per-format metadata (extensions, display names, capability flags) gathered in W8.1. Registry exists but nothing consumes it yet. Completion criteria: build passes. - **W8.3 β€” Migrate `Parser::load()` to registry-backed dispatch.** Replace the `switch` with a lookup + uniform call through the registry. Completion criteria: `run_golden_compares.sh` and `run_golden_io_roundtrip.sh` pass unchanged. - **W8.4 β€” Migrate `Graph::saveToFile()` to registry-backed dispatch.** Same treatment for export. Completion criteria: golden IO roundtrip baselines unchanged. - **W8.5 β€” Migrate both `mainwindow.cpp` switches to registry lookups.** File-dialog filter string and the single-relation-only warning both read from the same registry instead of their own hand-maintained copies. Completion criteria: manual smoke test β€” open each supported format via the file dialog, confirm filter strings unchanged; trigger the multi-relation export warning on one of the four affected formats, confirm the message is unchanged. - **W8.6 β€” Close the GML/TWOMODE export gap, or document it as intentional.** Once the registry makes the asymmetry visible in one place (`supportsExport: false` for those two), decide explicitly whether to implement the missing exporters or document why they're intentionally import-only, rather than leaving it as an undocumented gap only discoverable by reading two switches side by side. ### Related, but distinct β€” #8 Enhance GML format support Not a dispatch-consolidation item like W8.1–W8.6 above β€” this is parsing-*depth* work on the GML format specifically. The current GML parser supports a minimal command set (`graph`, `comment`, `directed`, `node`/`id`/`label`, `edge`/`source`/`target`/`label`); the [GML technical report](http://www.fim.uni-passau.de/fileadmin/files/lehrstuhl/brandenburg/projekte/gml/gml-technical-report.pdf) defines a substantially larger command set. Grouped here because it's the same file-format-support territory as WS8, not because it's part of the registry-consolidation work β€” worth picking up alongside or after the `FormatHandler` migration, not blocked on it. ## Work Rules - No parsing/export behavior changes β€” this is dispatch consolidation only, matching WS4's own non-goals. - Migrate one switch at a time (W8.3 β†’ W8.4 β†’ W8.5), golden-regression-verified after each, not as one large combined change. - `run_golden_compares.sh` and `run_golden_io_roundtrip.sh` must pass after every milestone. SocNetV-3.7/docs/roadmaps/roadmap_ws9_graph_exploration.md000066400000000000000000000055111524402210300240110ustar00rootroot00000000000000# Roadmap: Graph Exploration (WS9) ## Goal Evolve SocNetV from a visualization-focused application into a full graph exploration and data workflow platform: non-destructive filtering, structural/attribute queries, subgraph extraction, and structured (table/CSV/JSON) data workflows. ## Status βœ… Complete. All three feature tracks shipped in v3.5–v3.6. Current filter-layer architecture lives in [`README_DEVELOPER_NOTES.md`](../README_DEVELOPER_NOTES.md)'s "Filter Layer" section, not here. ## What WS9 Delivered - **Feature 1 β€” Visualization & decluttering** (#209) β€” focus-on-selection, ego networks (k=1), edge filtering by weight, non-destructive restore, ego-centered radial layout, color-by-metric (Clustering Coefficient added as a full prominence index). - **Feature 2 β€” Filtering & subgraphs** (#215) β€” attribute-based filtering (`FilterCondition`), a persistent chip-based filter bar with arbitrary chip removal via a replay stack (`FilterSpec`), a visual query builder (`GraphQuery`, AND-only), subgraph extraction (`Graph::subgraphExtract()`/`subgraphExtractFromSelection()`) and export to all 7 supported formats. - **Feature 3 β€” Data workflows** (#223) β€” attribute editing UI, a live-search node/edge table dock (`NodeTableModel`/`EdgeTableModel`/`GraphTableWidget`), CSV/JSON export and import (`TableExport`/`TableImport`, `src/graph/io/`) with column-mapping, and bulk editing (`DialogBulkEdit`) across canvas-selection/filtered/multi-row-table target sets. ## Known Gaps - Bulk-edit operations (Feature 3) bypass the undo stack β€” tracked under [WS13](roadmap_ws13_undo_redo.md) (#31). - Arbitrary chip removal doesn't cover selection/ego/centrality filters β€” those don't store enough in `FilterSpec` to replay after removal (needs either #31 structural undo or per-type parameter storage). ## What Remains Open - **Persistent named subgraphs + tab-based multi-graph UI** (#245) β€” switch between multiple named derived views without reloading. Blocked on the tab UI, a significant infrastructure investment; not started. - **Query Builder OR logic** and a **text-based query DSL** β€” `GraphQuery` is AND-only today. - **Community-based node coloring** (#258) and **edge bundling** (#259) β€” deferred, each its own algorithmic/rendering effort independent of WS9. - **In-app derived fields / normalization / type coercion** β€” deferred; the CSV/JSON roundtrip workflow already covers this via external spreadsheet tools. - **Temporal data** (#222, general case #25) β€” `FilterCondition`'s `Lte`/`Gte` operators already support date-range queries once attributes carry typed dates; the timeline/animation layer is what's missing. - **Attribute inspector panel / persistent filter panel** as real docked widgets (currently dialog-driven) β€” deferred until WS7's MainWindow decomposition is underway. SocNetV-3.7/man/000077500000000000000000000000001524402210300134505ustar00rootroot00000000000000SocNetV-3.7/man/socnetv.1000066400000000000000000000145061524402210300152210ustar00rootroot00000000000000.TH socnetv 1 "Jun 2026" "SocNetV-3.7" "Social Network Visualiser" .SH NAME socnetv - Visualize and analyze social networks .SH SYNOPSIS .B socnetv < .I file.net > .B socnetv .B \-V | \-\-version .B socnetv .B \-h | \-\-help .SH DESCRIPTION Social Network Visualiser (SocNetV) is a cross-platform, user-friendly application for the analysis and visualization of Social Networks. With SocNetV you can: .IP \[bu] 2 Draw social networks with a few clicks on a virtual canvas, load field data from a file in a supported format (GraphML, GraphViz, EdgeList, GML, Adjacency, Edgelist, Pajek, UCINET, etc.) or crawl the internet to create a social network of connected webpages. .IP \[bu] Edit actors and ties through point-and-click, analyse graph and social network properties, produce beautiful HTML reports and embed visualization layouts to the network. .SH FEATURES These are the main features and routines of SocNetV: .IP \[bu] 2 Standard graph-theoretic and network cohesion metrics, such as density, diameter, geodesics and distances, connectedness, eccentricity, clustering coefficient, walks, etc. .IP \[bu] Matrix routines: Adjacency plot, Laplacian matrix, Degree matrix, Cocitation, etc. .IP \[bu] Advanced structural measures for social network analysis such as centrality and prestige indices (i.e. eigenvector and closeness centrality, betweenness centrality, information centrality, power centrality, proximity and pagerank prestige), .IP \[bu] Community detection algorithms such as triad census, clique census, etc. .IP \[bu] Structural equivalence analysis, using hierarchical clustering, actor similarities and tie profile dissimilarities, pearson coefficients, etc. .IP \[bu] Random network creation, i.e. Erdos-Renyi, Watts-Strogatz, scale-free, ring lattice, etc. .IP \[bu] One-click recreation of well-known social network datasets such as Padgett's Florentine families. .IP \[bu] Layout algorithms based on either prominence indices (i.e. circular, level and nodal sizes by centrality score) or force-directed models (i.e. Eades, Fruchterman-Reingold, etc) for meaningful visualizations of your social network data. .IP \[bu] Multirelational loading and editing. You can load a network consisting of multiple relations or create a network on your own and add multiple relations to it. .IP \[bu] Built-in web crawler, allowing you to automatically create networks from links found in a given initial URL. .IP \[bu] Comprehensive documentation, both online and while running the application, which explains each feature and algorithm of SocNetV in detail. .IP \[bu] Binary packages for Windows, Linux and MacOS. .SH DOCUMENTATION The documentation, licensed under the Free Documentation License (FDL), is available at .SH OPTIONS .TP .B \-\-version | \-V Displays the version of the program. .TP .B \-\-help | \-H Displays a short help message. .TP .I file.net The name of the file you want to open. .SH USAGE SocNetV has a simple Graphical User Interface (GUI). The biggest (right) part of the window is occupied by a virtual "canvas" where network nodes and edges appear. To the left of it, there is a dock with 4 buttons (add/remove node, add/remove link) and some LCDs which display statistics. On the top of the window, you will find the menu with all commands and options, and at the bottom there is a status bar, where messages appear. To create a new node, you can double-click on the canvas or click on the "add node" button. To create a new link, you can middle-click on the source node and then on the target node. Alternatively, you can click on the "add link" button from the dock. You move a node by left-clicking and dragging it. When you right-click on a node, a context menu appears. From there you can remove the node, change its color, label, size as well as its shape. SocNetV supports many kinds of node shapes, i.e rectangles, diamond, ellipse, circle, etc. A similar menu appears when you right click on a link. .SH Supported Formats The default file format for reading and saving social network is GraphML. Nevertheless, SocNetV can import various other formats (see below). You can load GraphML files by clicking on menu File > Load. To load other network formats, use the menu File ->Import. SocNetV supports the following network data formats: .IP \[bu] 2 GraphML (.graphml or .xml) .IP \[bu] GML (.gml or .xml) .IP \[bu] GraphViz (.dot) .IP \[bu] Adjacency matrix (.csv, .txt, .sm, or .adj) .IP \[bu] Pajek (.net, .paj or .pajek), .IP \[bu] UCINET's Data Language (.dl) .IP \[bu] edge list (.lst or .list) .IP \[bu] Weighted Lists (.wlst or .wlist) .SH Layout algorithms SocNetV also supports a variety of layout algorithms, such as spring-embedder (i.e. energy based) and centralities-based. The latter reposition all nodes in circles of different radiuses (i.e. more central nodes appear near the centre of the canvas) or in levels of different height (more central nodes appear near to the top). You can apply layout algorithms from the menu Layout or by clicking on the checkboxes on the dock. Not all layouts appear on the dock, though. .SH KNOWN BUGS The network file parser is not 100% trustworthy. For instance, complicated Pajek or GraphML files can crash the application. Antialiasing is on by default. This slows down the application when the network has many nodes and edges. You can disable antialiasing by pressing F8 . .SH AUTHOR Dimitris Kalamaras .SH BUG REPORTS Report bugs to https://github.com/socnetv/app/issues In order to send a meaningful bug report, run .BR socnetv from a terminal, press F9 and repeat the steps which lead to bug manifestation. SocNetV will be printing debug messages on the standard output (the terminal in Linux). .SH AVAILABILITY The latest version of this program can be found at . It is distributed in source code and binary packages for Linux distributions, image disks for Mac OS X and zipped executables for Windows. To get the latest development version use: git clone -b develop --single-branch https://github.com/socnetv/app.git socnetv .SH COPYRIGHT This program is distributed under the terms of the GNU General Public License 3 as published by the Free Software Foundation . See the built-in help for details on the License and the lack of warranty. .SH SEE ALSO .BR graphviz (1) SocNetV-3.7/scripts/000077500000000000000000000000001524402210300143645ustar00rootroot00000000000000SocNetV-3.7/scripts/README.md000066400000000000000000000056511524402210300156520ustar00rootroot00000000000000# scripts/ Index of everything in this directory. Detailed docs are linked where they exist. --- ## Regression & Benchmarking (actively used) | Script | Purpose | |---|---| | `run_golden_compares.sh` | Correctness regression: runs all 8 CLI kernels against committed JSON baselines (`src/tools/baselines/`, see [`BASELINES__README.md`](../src/tools/baselines/BASELINES__README.md)). | | `run_benchmarks.sh` | Performance regression: distance/prominence/io baseline-enforced timing, plus optional clustering timing probes. See [`README__run_benchmarks.md`](README__run_benchmarks.md) for the full reference. | | `run_golden_io_roundtrip.sh` | Runs the `io_roundtrip` kernel for every baseline under `src/tools/baselines/io_roundtrip/`. `--update` regenerates them in place instead of comparing. | | `run_io_roundtrip_shipped_datasets.sh` | Runs `io_roundtrip` across every file in `src/data/` (filetype inferred from extension) as a load smoke-test β€” no `--compare-json`, no committed baseline. | | `run_matrix_storage_bench.sh` | WS5 A2.0/A3 storage micro-benchmark driver (`QHash` vs `Matrix`, or `Matrix`-vs-`Matrix` across code versions) for the `matrix-storage-bench` tool (`BUILD_MATRIX_BENCH` CMake option). | | `run_render_perf_bench.sh` | WS10/WS6.6 canvas rendering-performance regression kernel (#240) β€” drives the real GUI binary headless via `--interactive-script` (`fixtures/render_perf_script.txt`), `QT_QPA_PLATFORM=offscreen`. | | `lib/find_socnetv_cli.sh`, `lib/find_socnetv_gui.sh` | Shared binary-discovery helpers sourced by the scripts above. | | `perf_baselines//perf_expected.env` | Committed performance baselines, one directory per platform (`macos-arm64`, `macos-m5`, `linux-x86_64`). **Only re-recorded against a clean tagged release, never `develop` HEAD** β€” see [`README__run_benchmarks.md`](README__run_benchmarks.md#baseline-philosophy). | | `fixtures/render_perf_script.txt` | `--interactive-script` fixture used by `run_render_perf_bench.sh`. | ## Packaging / Release | Script | Purpose | |---|---| | `entitlements.plist` | macOS code-signing entitlements (app sandbox, etc.) for the DMG build. | | `innosetup.iss` | Windows installer script (Inno Setup), used by `build-release.yml`. | | `flathub/` | Flatpak manifest + desktop file + icon for the Flathub build. | | `update_translations.sh` | Regenerates Qt `.ts`/`.qm` translation files. | ## Legacy (not used by current CI) `travis_before.sh`, `travis_install_deps.sh`, `travis_make_build_linux.sh`, `travis_make_build_macos.sh`, `travis_upload_packages.sh` β€” from the project's earlier Travis CI setup. Not referenced by any current `.github/workflows/*.yml`; left in place, not maintained. --- See also: [`docs/README_DEVELOPER_NOTES.md`](../docs/README_DEVELOPER_NOTES.md) for the regression harness's place in the overall architecture, and [`docs/SOCNETV_CLI_REGRESSION_TOOL.md`](../docs/SOCNETV_CLI_REGRESSION_TOOL.md) for the full `socnetv-cli` command reference. SocNetV-3.7/scripts/README__run_benchmarks.md000066400000000000000000000210521524402210300210630ustar00rootroot00000000000000# Benchmarks `run_benchmarks.sh` runs **socnetv-cli micro-benchmarks** and compares benchmark timings against stored performance baselines where applicable. Benchmarks validate: - Algorithmic performance stability - Absence of accidental slowdowns - Structural refactor safety (e.g. CLI modularization) Benchmarks **do not validate correctness** β€” correctness is enforced by golden JSON comparisons (`run_golden_compares.sh`). --- # What Is Being Benchmarked The benchmark harness supports four benchmark families. By default (`--type all`), it runs only the **baseline-enforced** families: - Distance / Centrality - Prominence - IO Roundtrip The fourth family, **Optional Clustering Timing**, is developer-oriented and runs only when explicitly selected with `--type clustering`. ## Distance / Centrality (schema v1) Benchmarks the `--kernel distance` (DistanceEngine + centrality computation). Shipped cases (always run, CI-reproducible): - `EIES48_T1_C1_W1` β€” Freeman EIES 48 actors, time 1, weighted, centralities ON - `EIES48_T2_C1_W1` β€” Freeman EIES 48 actors, time 2, weighted, centralities ON - `BA500_M3_C1_W0` β€” BarabΓ‘si–Albert N=500, m=3, centralities ON - `BA500_M3_C0_W0` β€” BarabΓ‘si–Albert N=500, m=3, distances only Large-net cases (run only if `~/socnetv/library/nets/large/` exists β€” local developer machines only, not CI): - `DIST_GRAPHML_1000N_10000A_C0_W0` β€” 1000 actors, 10000 arcs, distances only (`--bench 2`) - `DIST_GRAPHML_1000N_10000A_C1_W0` β€” 1000 actors, 10000 arcs, centralities ON (`--bench 2`) > **Note:** Distance computation cost is primarily a function of E (edges), not N (vertices). > The DistanceEngine runs BFS/Dijkstra from each source: O(N Γ— (N + E)). > For dense graphs, E dominates. This is why a 1000-node/10000-arc graph > takes ~100Γ— longer than a 500-node/1219-arc graph. ## Prominence (schema v4) Benchmarks the `--kernel prominence` (all node-level centrality + prestige indices: degree, closeness, betweenness, information, eigenvector, degree/proximity/PageRank prestige). Shipped cases (always run, CI-reproducible): - `PROM_BA500_M3` β€” BarabΓ‘si–Albert N=500, m=3 N=500, not `geom.net`-scale: Information Centrality (full matrix inversion) and Eigenvector Centrality (power iteration) are O(NΒ³)-ish, impractically slow at N=7,343 for a benchmark that runs routinely. ## IO Roundtrip (schema v5) Benchmarks `--kernel io_roundtrip` load timing for large datasets. Measures `LOAD_MS` (file load time only, not save/reload). Shipped cases (always run, CI-reproducible): - `IO_PAJ_BA500` β€” BarabΓ‘si–Albert N=500, m=3, Pajek format Large-net cases (run only if `~/socnetv/library/nets/large/` exists): - `IO_GRAPHML_2000N_40000E` β€” 2000 actors, 40000 edges, GraphML - `IO_GRAPHML_1000N_10000A` β€” 1000 actors, 10000 arcs, GraphML Other kernels (reachability, walks_matrix) are validated for correctness only, not performance. ## Optional Clustering Timing (schema v6, informational) Benchmarks `--kernel clustering` wall-clock timing for selected datasets. Important: - clustering timing does **not** use `--bench` - timings are single-run, wall-clock measurements - results are **informational only** - they are **not enforced** against performance baselines - they run **only** when explicitly selected with `--type clustering` Run them with: ```bash ./scripts/run_benchmarks.sh --type clustering ``` Current shipped clustering timing probes: * `CLUST_KITE_N10` β€” Krackhardt Kite N=10 * `CLUST_SAMPSON_N18` β€” Sampson Monks N=18 Rationale: * The clustering kernel does not currently support deterministic multi-run benchmarking (`--bench`). * Clique enumeration and triad census are still useful to time locally. * This gives trend visibility without introducing noisy CI failures. --- # Benchmark Type Selection Use `--type` to select which benchmark family to run: * `--type distance` β†’ run only distance / centrality benchmarks * `--type prominence` β†’ run only prominence benchmarks * `--type io` β†’ run only IO timing benchmarks * `--type clustering` β†’ run only optional clustering timing probes * `--type all` β†’ run the baseline-enforced benchmark families only (`distance` + `prominence` + `io`) (default) Examples: ```bash ./scripts/run_benchmarks.sh --type distance ./scripts/run_benchmarks.sh --type prominence ./scripts/run_benchmarks.sh --type io ./scripts/run_benchmarks.sh --type clustering ./scripts/run_benchmarks.sh --type all ``` --- # Baseline Selection (Machine-Aware) Priority order for choosing the expected baseline file: 1. `BENCH_BASELINE=/path/to/perf_expected.env` (explicit file path) 2. `BENCH_BASELINE_SET=` β†’ `scripts/perf_baselines//perf_expected.env` 3. Auto set: `-` β†’ `scripts/perf_baselines//perf_expected.env` (if present) The script prints an INFO line showing: ``` BENCH_BASELINE_SET EXPECTED_FILE BUILD_TYPE RECORD ``` Baseline selection is therefore explicit and reproducible. --- # Recording a New Baseline Set Record a baseline for the current machine (auto set name): ```bash ./scripts/run_benchmarks.sh --record ``` Record into an explicit set name: ```bash BENCH_BASELINE_SET=linux-ryzen ./scripts/run_benchmarks.sh --record ``` This writes: ``` scripts/perf_baselines//perf_expected.env ``` Note: * Recording applies to the baseline-enforced benchmark families. * Optional clustering timing probes are not recorded into performance baselines. --- # Strict Regression Detection To treat performance regressions as hard failures: ```bash ./scripts/run_benchmarks.sh --strict ``` A case fails if: ``` actual > expected Γ— 1.10 ``` The tolerance is +10%. For distance cases, `actual` is the **median compute time** over N runs (`--bench N`). For IO cases, `actual` is the **single load time** (`LOAD_MS`). IO baselines with `expected == 0ms` are skipped automatically (too fast to measure reliably). Strict regression enforcement applies to distance and IO benchmarks only. Optional clustering timing probes are informational and are not baseline-enforced. IO timing regression enforcement is also available at the kernel level via `socnetv-cli --strict` (applies `checkIoTimings()` inside `compareGoldenV5Io()`). --- # Large Net Cases Cases that use `~/socnetv/library/nets/large/` are **local-only**: * They are skipped silently in CI environments where the directory does not exist. * They are always guarded by `if [[ -d "$LARGE_NETS_DIR" ]]`. * They should not be expected to be fast β€” 1000-node distance benchmarks take 28–47 seconds on a Debug build. * Record baselines for these on your own machine; do not commit baselines recorded on machines with different hardware or build configurations. --- # Build Type / Binary Selection The script does **not** build the project. It executes an existing `socnetv-cli` binary. Override binary path explicitly: ```bash SOCNETV_CLI=/path/to/socnetv-cli ./scripts/run_benchmarks.sh ``` `BENCH_BUILD_TYPE=Debug|Release` is only a hint used for default binary path discovery via `scripts/lib/find_socnetv_cli.sh`. Example: ```bash BENCH_BUILD_TYPE=Release ./scripts/run_benchmarks.sh ``` --- # Debug vs Release Benchmarks are sensitive to: * Build type (Debug vs Release) * Compiler flags * LTO / inlining * Logging configuration Important: * Debug builds must suppress `qDebug` / `qInfo` output during benchmarking. * Logging overhead can distort compute times significantly. Baseline sets should be recorded consistently under the same build type and environment. --- # Baseline Philosophy Baselines are treated as **stable performance contracts**. **Only re-record against a clean tagged release (e.g. `git checkout v3.6`), never against arbitrary `develop` HEAD.** `develop` accumulates unrelated speedups/regressions between releases; recording against it embeds all of that into what's supposed to be a release snapshot, and every subsequent benchmark run then reports misleading "faster than baseline" deltas for work that has nothing to do with whatever's actually being tested. Re-record once per release cycle, after tagging. They should only be re-recorded when: * A new release has just been tagged * A known performance bug is fixed and the fix is being tagged as a release Baselines are not intended to be routinely overwritten. --- # Rollback To revert to a previous baseline: * Restore the relevant `scripts/perf_baselines//perf_expected.env` from git history. * Or delete the set directory and re-record with `--record`. --- # Future Work Clustering benchmarks may evolve to support deterministic multi-run timing (bench mode) once `kernel_v6` exposes stable iteration semantics.SocNetV-3.7/scripts/entitlements.plist000066400000000000000000000003701524402210300201540ustar00rootroot00000000000000 com.apple.security.app-sandbox SocNetV-3.7/scripts/fixtures/000077500000000000000000000000001524402210300162355ustar00rootroot00000000000000SocNetV-3.7/scripts/fixtures/render_perf_script.txt000066400000000000000000000001561524402210300226570ustar00rootroot00000000000000erdos-m 2000 40000 undirected delay 1 render bulk-node-size 30 bulk-edge-color red move 1 500 500 render quit SocNetV-3.7/scripts/fixtures/report_export_bench_large.txt000066400000000000000000000010521524402210300242210ustar00rootroot00000000000000erdos-m 2000 40000 undirected delay 1 distances weights report-centrality-degree weights report-centrality-closeness weights report-centrality-closeness-ir weights report-centrality-betweenness weights report-centrality-stress weights report-centrality-eccentricity weights report-centrality-power weights report-centrality-information weights report-centrality-eigenvector weights report-prestige-degree weights report-prestige-proximity report-prestige-pagerank report-reciprocity weights report-eccentricity weights report-clustering-coefficient quit SocNetV-3.7/scripts/fixtures/report_export_bench_small.txt000066400000000000000000000010741524402210300242430ustar00rootroot00000000000000erdos-m 500 2500 undirected delay 1 distances weights report-centrality-degree weights report-centrality-closeness weights report-centrality-closeness-ir weights report-centrality-betweenness weights report-centrality-stress weights report-centrality-eccentricity weights report-centrality-power weights report-centrality-information weights report-centrality-eigenvector weights report-prestige-degree weights report-prestige-proximity report-prestige-pagerank report-reciprocity weights report-eccentricity weights report-clustering-coefficient report-triad-census quit SocNetV-3.7/scripts/flathub/000077500000000000000000000000001524402210300160115ustar00rootroot00000000000000SocNetV-3.7/scripts/flathub/org.socnetv.SocNetV.desktop000066400000000000000000000003771524402210300232020ustar00rootroot00000000000000[Desktop Entry] Name=Social Network Visualizer GenericName=Social Network Visualizer Comment=Visualize and analyze social networks Exec=socnetv Icon=org.socnetv.SocNetV Terminal=false Type=Application Categories=Education;Science;Math; StartupNotify=true SocNetV-3.7/scripts/flathub/org.socnetv.SocNetV.svg000066400000000000000000000154461524402210300223330ustar00rootroot00000000000000 SocNetV-3.7/scripts/flathub/org.socnetv.SocNetV.yaml000066400000000000000000000006261524402210300224700ustar00rootroot00000000000000app-id: org.socnetv.SocNetV runtime: org.kde.Platform runtime-version: '6.5' sdk: org.kde.Sdk command: socnetv finish-args: - --share=network - --share=ipc - --socket=x11 - --socket=wayland - --device=dri - --filesystem=home modules: - name: socnetv buildsystem: cmake sources: - type: git url: https://github.com/socnetv/app.git tag: v3.2 commit: auto SocNetV-3.7/scripts/innosetup.iss000066400000000000000000000071621524402210300171360ustar00rootroot00000000000000#define APPTITLE "Social Network Visualizer" #define APPSHORT "SocNetV" #define RELEASEFOLDER "release\" #define EXECUTABLE APPSHORT + ".exe" #define NUMERICVERSION GetVersionNumbersString(RELEASEFOLDER+EXECUTABLE) #define VERSION "3.7" #define URL "https://socnetv.org" #define COPYRIGHT "2005-2026 " + URL [Setup] AllowNoIcons=yes AppName={#APPTITLE} AppId={#APPSHORT} AppPublisher={#APPTITLE} AppPublisherURL={#URL} AppSupportURL={#URL} AppUpdatesURL={#URL} AppContact=info@socnetv.org AppVerName={#APPTITLE} {#VERSION} Compression=lzma/ultra DefaultDirName={pf}\{#APPSHORT} DefaultGroupName={#APPTITLE} DisableProgramGroupPage=true LicenseFile={#RELEASEFOLDER}LICENSE.txt InternalCompressLevel=ultra OutputBaseFilename={#APPSHORT}-{#VERSION}-installer OutputDir=. OutputManifestFile=Setup-Manifest.txt ShowLanguageDialog=no SolidCompression=yes VersionInfoProductName={#APPTITLE} VersionInfoCompany={#URL} VersionInfoCopyright=Copyright (C) {#COPYRIGHT} VersionInfoDescription={#APPTITLE} Setup VersionInfoTextVersion={#VERSION} VersionInfoVersion={#NUMERICVERSION} ; WizardImageFile=compiler:WizModernImage-IS.bmp ; WizardSmallImageFile=compiler:WizModernSmallImage-IS.bmp WizardStyle=modern [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" Name: "catalan"; MessagesFile: "compiler:Languages\Catalan.isl" Name: "corsican"; MessagesFile: "compiler:Languages\Corsican.isl" Name: "czech"; MessagesFile: "compiler:Languages\Czech.isl" Name: "danish"; MessagesFile: "compiler:Languages\Danish.isl" Name: "dutch"; MessagesFile: "compiler:Languages\Dutch.isl" Name: "finnish"; MessagesFile: "compiler:Languages\Finnish.isl" Name: "french"; MessagesFile: "compiler:Languages\French.isl" Name: "german"; MessagesFile: "compiler:Languages\German.isl" //Name: "greek"; MessagesFile: "compiler:Languages\Greek.isl" Name: "hebrew"; MessagesFile: "compiler:Languages\Hebrew.isl" //Name: "hungarian"; MessagesFile: "compiler:Languages\Hungarian.isl" Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl" Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" Name: "norwegian"; MessagesFile: "compiler:Languages\Norwegian.isl" Name: "polish"; MessagesFile: "compiler:Languages\Polish.isl" Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl" Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" //Name: "serbian"; MessagesFile: "compiler:Languages\SerbianCyrillic.isl" Name: "slovak"; MessagesFile: "compiler:Languages\Slovak.isl" Name: "slovenian"; MessagesFile: "compiler:Languages\Slovenian.isl" Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl" Name: "ukrainian"; MessagesFile: "compiler:Languages\Ukrainian.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked [Files] Source: {#RELEASEFOLDER}*; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs [Icons] Name: "{group}\{#APPTITLE}"; Filename: "{app}\{#EXECUTABLE}" Name: "{group}\{cm:UninstallProgram,{#APPTITLE}}"; Filename: "{uninstallexe}" Name: "{userdesktop}\{#APPTITLE}"; Filename: "{app}\{#EXECUTABLE}"; Tasks: desktopicon Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#APPTITLE}"; Filename: "{app}\{#EXECUTABLE}"; Tasks: quicklaunchicon [Run] Filename: "{app}\{#EXECUTABLE}"; Description: "{cm:LaunchProgram,{#APPTITLE}}"; Flags: nowait postinstall skipifsilent SocNetV-3.7/scripts/lib/000077500000000000000000000000001524402210300151325ustar00rootroot00000000000000SocNetV-3.7/scripts/lib/find_socnetv_cli.sh000077500000000000000000000016361524402210300210070ustar00rootroot00000000000000#!/bin/sh # POSIX helper for locating socnetv-cli (macOS/Linux). # Usage: # . "$ROOT_DIR/scripts/lib/find_socnetv_cli.sh" # CLI="$(find_socnetv_cli "$ROOT_DIR" "$BUILD_TYPE")" || exit 1 find_socnetv_cli() { root=$1 build_type=$2 # Fast-path known layouts for p in \ "$root/build/socnetv-cli" \ "$root/build/$build_type/socnetv-cli" \ "$root/builds/__unspec__/$build_type/socnetv-cli" \ "$root/builds/__unspec__/Debug/socnetv-cli" do if [ -x "$p" ]; then echo "$p" return 0 fi done # QtCreator kits / custom build dirs: # builds///socnetv-cli if [ -d "$root/builds" ]; then for t in "$build_type" Debug Release; do p=$(find "$root/builds" -maxdepth 3 -type f -name socnetv-cli -path "*/$t/*" 2>/dev/null | sed -n '1p') if [ -n "${p:-}" ] && [ -x "$p" ]; then echo "$p" return 0 fi done fi return 1 }SocNetV-3.7/scripts/lib/find_socnetv_gui.sh000077500000000000000000000020531524402210300210160ustar00rootroot00000000000000#!/bin/sh # POSIX helper for locating the SocNetV GUI binary (macOS/Linux), analogous to # find_socnetv_cli.sh. Binary name is platform-specific per CMakeLists.txt: a macOS .app # bundle, "SocNetV" on Windows, lowercase "socnetv" everywhere else (Linux). # Usage: # . "$ROOT_DIR/scripts/lib/find_socnetv_gui.sh" # GUI="$(find_socnetv_gui "$ROOT_DIR" "$BUILD_TYPE")" || exit 1 find_socnetv_gui() { root=$1 build_type=$2 for p in \ "$root/build/SocNetV.app/Contents/MacOS/SocNetV" \ "$root/build/socnetv" \ "$root/build/SocNetV" \ "$root/build/$build_type/socnetv" \ "$root/builds/__unspec__/$build_type/socnetv" \ "$root/builds/__unspec__/Debug/socnetv" do if [ -x "$p" ]; then echo "$p" return 0 fi done if [ -d "$root/builds" ]; then for t in "$build_type" Debug Release; do p=$(find "$root/builds" -maxdepth 3 -type f -name socnetv -path "*/$t/*" 2>/dev/null | sed -n '1p') if [ -n "${p:-}" ] && [ -x "$p" ]; then echo "$p" return 0 fi done fi return 1 } SocNetV-3.7/scripts/perf_baselines/000077500000000000000000000000001524402210300173455ustar00rootroot00000000000000SocNetV-3.7/scripts/perf_baselines/linux-x86_64/000077500000000000000000000000001524402210300214405ustar00rootroot00000000000000SocNetV-3.7/scripts/perf_baselines/linux-x86_64/perf_expected.env000066400000000000000000000010111524402210300247600ustar00rootroot00000000000000# Auto-generated by scripts/run_benchmarks.sh --record # BASELINE_SET=linux-x86_64 # BASELINE_FILE=scripts/perf_baselines/linux-x86_64/perf_expected.env # BUILD_TYPE=Debug EXP_BA500_M3_C0_W0_MEDIAN_MS=250 EXP_BA500_M3_C1_W0_MEDIAN_MS=249 EXP_DIST_GRAPHML_1000N_10000A_C0_W0_MEDIAN_MS=2652 EXP_DIST_GRAPHML_1000N_10000A_C1_W0_MEDIAN_MS=3793 EXP_EIES48_T1_C1_W1_MEDIAN_MS=26 EXP_EIES48_T2_C1_W1_MEDIAN_MS=29 EXP_IO_GRAPHML_1000N_10000A_MEDIAN_MS=244 EXP_IO_GRAPHML_2000N_40000E_MEDIAN_MS=851 EXP_IO_PAJ_BA500_MEDIAN_MS=38 SocNetV-3.7/scripts/perf_baselines/macos-arm64/000077500000000000000000000000001524402210300213765ustar00rootroot00000000000000SocNetV-3.7/scripts/perf_baselines/macos-arm64/perf_expected.env000066400000000000000000000010071524402210300247230ustar00rootroot00000000000000# Auto-generated by scripts/run_benchmarks.sh --record # BASELINE_SET=macos-arm64 # BASELINE_FILE=scripts/perf_baselines/macos-arm64/perf_expected.env # BUILD_TYPE=Debug EXP_BA500_M3_C0_W0_MEDIAN_MS=153 EXP_BA500_M3_C1_W0_MEDIAN_MS=156 EXP_DIST_GRAPHML_1000N_10000A_C0_W0_MEDIAN_MS=5439 EXP_DIST_GRAPHML_1000N_10000A_C1_W0_MEDIAN_MS=7963 EXP_EIES48_T1_C1_W1_MEDIAN_MS=68 EXP_EIES48_T2_C1_W1_MEDIAN_MS=80 EXP_IO_GRAPHML_1000N_10000A_MEDIAN_MS=138 EXP_IO_GRAPHML_2000N_40000E_MEDIAN_MS=507 EXP_IO_PAJ_BA500_MEDIAN_MS=11 SocNetV-3.7/scripts/perf_baselines/macos-arm64/render_perf_expected.env000066400000000000000000000006241524402210300262660ustar00rootroot00000000000000# Auto-generated by scripts/run_render_perf_bench.sh --record # BASELINE_SET=macos-arm64 # BASELINE_FILE=scripts/perf_baselines/macos-arm64/render_perf_expected.env # Thresholds are 2x the median-of-7 measured reference run (upper bound, not exact-value). EXP_RENDER_INITIAL_MAX_MS=256 EXP_BULK_NODE_SIZE_MAX_MS=84 EXP_BULK_EDGE_COLOR_MAX_MS=114 EXP_MOVE_MAX_MS=15 EXP_RENDER_AFTER_MUTATIONS_MAX_MS=192 SocNetV-3.7/scripts/perf_baselines/macos-m5/000077500000000000000000000000001524402210300207665ustar00rootroot00000000000000SocNetV-3.7/scripts/perf_baselines/macos-m5/perf_expected.env000066400000000000000000000010011524402210300243050ustar00rootroot00000000000000# Auto-generated by scripts/run_benchmarks.sh --record # BASELINE_SET=macos-m5 # BASELINE_FILE=scripts/perf_baselines/macos-m5/perf_expected.env # BUILD_TYPE=Debug EXP_BA500_M3_C0_W0_MEDIAN_MS=146 EXP_BA500_M3_C1_W0_MEDIAN_MS=154 EXP_DIST_GRAPHML_1000N_10000A_C0_W0_MEDIAN_MS=5331 EXP_DIST_GRAPHML_1000N_10000A_C1_W0_MEDIAN_MS=7991 EXP_EIES48_T1_C1_W1_MEDIAN_MS=70 EXP_EIES48_T2_C1_W1_MEDIAN_MS=81 EXP_IO_GRAPHML_1000N_10000A_MEDIAN_MS=139 EXP_IO_GRAPHML_2000N_40000E_MEDIAN_MS=501 EXP_IO_PAJ_BA500_MEDIAN_MS=11 SocNetV-3.7/scripts/perf_baselines/macos-m5/render_perf_expected.env000066400000000000000000000006161524402210300256570ustar00rootroot00000000000000# Auto-generated by scripts/run_render_perf_bench.sh --record # BASELINE_SET=macos-m5 # BASELINE_FILE=scripts/perf_baselines/macos-m5/render_perf_expected.env # Thresholds are 2x the median-of-7 measured reference run (upper bound, not exact-value). EXP_RENDER_INITIAL_MAX_MS=250 EXP_BULK_NODE_SIZE_MAX_MS=86 EXP_BULK_EDGE_COLOR_MAX_MS=114 EXP_MOVE_MAX_MS=15 EXP_RENDER_AFTER_MUTATIONS_MAX_MS=194 SocNetV-3.7/scripts/perf_expected.env000066400000000000000000000003451524402210300177150ustar00rootroot00000000000000# Expected COMPUTE_MS_MEDIAN values (milliseconds) # Warn if actual median > expected * 1.10 EXP_EIES48_T1_C1_W1_MEDIAN_MS=105 EXP_EIES48_T2_C1_W1_MEDIAN_MS=114 EXP_BA500_M3_C1_W0_MEDIAN_MS=419 EXP_BA500_M3_C0_W0_MEDIAN_MS=146 SocNetV-3.7/scripts/run_benchmarks.sh000077500000000000000000000324101524402210300177240ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail # SocNetV performance micro-benchmark runner (macOS/Linux) # # Usage: # ./scripts/run_benchmarks.sh # ./scripts/run_benchmarks.sh --type distance # ./scripts/run_benchmarks.sh --type prominence # ./scripts/run_benchmarks.sh --type io # ./scripts/run_benchmarks.sh --type clustering # ./scripts/run_benchmarks.sh --strict # ./scripts/run_benchmarks.sh --record # SOCNETV_CLI=./build/socnetv-cli ./scripts/run_benchmarks.sh # # Baseline selection priority: # 1) BENCH_BASELINE=/path/to/perf_expected.env # 2) BENCH_BASELINE_SET= (scripts/perf_baselines//perf_expected.env) # 3) auto: - (scripts/perf_baselines//perf_expected.env) RECORD=0 STRICT=0 BENCH_TYPE="all" # all|distance|prominence|io|clustering LARGE_NETS_DIR="${HOME}/socnetv/library/nets/large" while [[ $# -gt 0 ]]; do case "$1" in --strict) STRICT=1; shift ;; --record) RECORD=1; shift ;; --type) [[ $# -ge 2 ]] || { echo "ERROR: --type requires an argument" >&2; exit 2; } BENCH_TYPE="$2" shift 2 ;; *) echo "ERROR: unknown argument: $1" >&2 exit 2 ;; esac done case "$BENCH_TYPE" in all|distance|prominence|io|clustering) ;; *) echo "ERROR: invalid --type: $BENCH_TYPE (expected: all|distance|prominence|io|clustering)" >&2 exit 2 ;; esac ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" BENCH_BUILD_TYPE="${BENCH_BUILD_TYPE:-Debug}" # Debug|Release (hint only) # shellcheck source=/dev/null . "$ROOT_DIR/scripts/lib/find_socnetv_cli.sh" if [[ -n "${SOCNETV_CLI:-}" ]]; then if [[ ! -x "$SOCNETV_CLI" ]]; then echo "ERROR: SOCNETV_CLI is set but not executable: $SOCNETV_CLI" >&2 exit 1 fi else SOCNETV_CLI="$(find_socnetv_cli "$ROOT_DIR" "$BENCH_BUILD_TYPE" 2>/dev/null || true)" fi if [[ -z "${SOCNETV_CLI:-}" || ! -x "$SOCNETV_CLI" ]]; then echo "ERROR: Could not find socnetv-cli." >&2 echo "Hint: SOCNETV_CLI=/full/path/to/socnetv-cli $0" >&2 exit 1 fi echo "[bench] Using SOCNETV_CLI=$SOCNETV_CLI" BASELINE_ROOT="$ROOT_DIR/scripts/perf_baselines" sanitize_id() { echo "$1" | tr -cd '[:alnum:]._-' | sed 's/^\.*//; s/\.*$//' } auto_baseline_set() { local os arch os="$(uname -s 2>/dev/null || echo unknownOS)" arch="$(uname -m 2>/dev/null || echo unknownARCH)" # Normalize OS naming a bit case "$os" in Darwin) os="macos" ;; Linux) os="linux" ;; *) os="$(echo "$os" | tr '[:upper:]' '[:lower:]')" ;; esac sanitize_id "${os}-${arch}" } resolve_expected_file() { if [[ -n "${BENCH_BASELINE:-}" ]]; then echo "${BENCH_BASELINE}" return 0 fi local set_name="${BENCH_BASELINE_SET:-$(auto_baseline_set)}" set_name="$(sanitize_id "${set_name}")" local candidate="${BASELINE_ROOT}/${set_name}/perf_expected.env" if [[ -f "${candidate}" ]]; then echo "${candidate}" return 0 fi echo "ERROR: no baseline found for set '${set_name}'" >&2 echo "Expected: ${candidate}" >&2 exit 2 } EXPECTED_FILE="$(resolve_expected_file)" RESOLVED_SET="$(sanitize_id "${BENCH_BASELINE_SET:-$(auto_baseline_set)}")" if [[ -n "${BENCH_BASELINE:-}" ]]; then BASELINE_SET_USED="custom" else BASELINE_SET_USED="${RESOLVED_SET}" fi if [[ "${RECORD}" == "1" && -n "${BENCH_BASELINE_SET:-}" && "${BENCH_RECORD_ALLOW_OVERRIDE:-0}" != "1" ]]; then AUTO_SET="$(auto_baseline_set)" if [[ "${RESOLVED_SET}" != "${AUTO_SET}" ]]; then echo "ERROR: refusing to --record into BENCH_BASELINE_SET=${BENCH_BASELINE_SET} (auto=${AUTO_SET})." >&2 echo "Run on the target machine, or set BENCH_RECORD_ALLOW_OVERRIDE=1 to force." >&2 exit 2 fi fi echo "INFO: BENCH_BASELINE_SET=${BASELINE_SET_USED} EXPECTED_FILE=${EXPECTED_FILE} BUILD_TYPE=${BENCH_BUILD_TYPE} RECORD=${RECORD}" >&2 if [[ ! -x "$SOCNETV_CLI" ]]; then echo "ERROR: socnetv-cli not found or not executable: $SOCNETV_CLI" >&2 echo "Set SOCNETV_CLI=/path/to/socnetv-cli" >&2 exit 2 fi if [[ "${RECORD}" != "1" ]]; then if [[ ! -f "$EXPECTED_FILE" ]]; then echo "ERROR: expected perf file missing: $EXPECTED_FILE" >&2 exit 2 fi # shellcheck disable=SC1090 source "$EXPECTED_FILE" fi REC_LINES=() extract_kv_int() { local key="$1" awk -F= -v k="$key" '$1==k { print $2 }' } within_threshold() { local actual="$1" local expected="$2" [[ -z "$actual" || -z "$expected" ]] && return 1 if (( 100 * actual > 110 * expected )); then return 1 fi return 0 } record_expected_var() { local tag="$1" local median="$2" local expected_var="EXP_${tag}_MEDIAN_MS" REC_LINES+=("${expected_var}=${median}") } write_record_file() { local out_file="$1" local rel="$out_file" rel="${rel#${ROOT_DIR}/}" { echo "# Auto-generated by scripts/run_benchmarks.sh --record" echo "# BASELINE_SET=$(sanitize_id "${BASELINE_SET_USED}")" echo "# BASELINE_FILE=${rel}" echo "# BUILD_TYPE=${BENCH_BUILD_TYPE}" echo printf '%s\n' "${REC_LINES[@]}" | LC_ALL=C sort } > "$out_file" } run_case_io_bench() { local tag="$1" local input="$2" local ftype="$3" shift 3 echo "=== $tag ===" local out if ! out="$("$SOCNETV_CLI" --kernel io_roundtrip -i "$input" -f "$ftype" "$@" 2>/dev/null)"; then echo "ERROR: socnetv-cli failed for $tag" >&2 return 2 fi local ok load_ms ok="$(printf '%s\n' "$out" | extract_kv_int LOAD_OK | tail -n1)" load_ms="$(printf '%s\n' "$out" | extract_kv_int LOAD_MS | tail -n1)" echo "OK=$ok LOAD_MS=$load_ms" if [[ "$ok" != "1" || -z "$load_ms" ]]; then echo "ERROR: io_roundtrip benchmark did not produce valid output for $tag" >&2 return 2 fi if [[ "${RECORD}" == "1" ]]; then record_expected_var "$tag" "$load_ms" echo "OK: recorded ${load_ms}ms" return 0 fi local expected_var="EXP_${tag}_MEDIAN_MS" local expected="${!expected_var:-}" if [[ -z "$expected" ]]; then echo "WARN: no expected load_ms configured for $tag" return 0 fi # Skip enforcement if baseline is 0 (too fast to measure reliably) if (( expected == 0 )); then echo "TIMING_SKIP: baseline=0ms, not enforced for $tag" return 0 fi local allowed pct_delta pct_str allowed=$(( expected + (expected / 10) )) # Percent delta vs baseline: negative = faster, positive = slower if (( expected > 0 )); then pct_delta=$(( (load_ms - expected) * 100 / expected )) else pct_delta=0 fi if (( pct_delta < 0 )); then pct_str="${pct_delta#-}% faster than" elif (( pct_delta > 0 )); then pct_str="+${pct_delta}% slower than" else pct_str="at" fi if within_threshold "$load_ms" "$expected"; then echo "OK: ${load_ms}ms <= ${allowed}ms, ${pct_str} baseline ${expected}ms (+10% threshold)" return 0 else echo "FAIL: ${load_ms}ms > ${allowed}ms, ${pct_str} baseline ${expected}ms (+10% threshold)" [[ "$STRICT" == "1" ]] && return 1 return 0 fi } run_case() { local tag="$1" shift echo "=== $tag ===" # We want stable output (suppress stderr), but still detect failures. local out if ! out="$("$SOCNETV_CLI" "$@" 2>/dev/null)"; then echo "ERROR: socnetv-cli failed for $tag" >&2 return 2 fi local ok median ok="$(printf '%s\n' "$out" | extract_kv_int LOAD_OK | tail -n1)" median="$(printf '%s\n' "$out" | extract_kv_int COMPUTE_MS_MEDIAN | tail -n1)" echo "OK=$ok MEDIAN_MS=$median" if [[ "$ok" != "1" || -z "$median" ]]; then echo "ERROR: benchmark did not produce valid output for $tag" >&2 return 2 fi if [[ "${RECORD}" == "1" ]]; then record_expected_var "$tag" "$median" echo "OK: recorded ${median}ms" return 0 fi local expected_var="EXP_${tag}_MEDIAN_MS" local expected="${!expected_var:-}" if [[ -z "$expected" ]]; then echo "WARN: no expected median configured for $tag" return 0 fi local allowed pct_delta pct_str # allowed threshold = expected + 10% allowed=$(( expected + (expected / 10) )) # Percent delta vs baseline: negative = faster, positive = slower if (( expected > 0 )); then pct_delta=$(( (median - expected) * 100 / expected )) else pct_delta=0 fi if (( pct_delta < 0 )); then pct_str="${pct_delta#-}% faster than" elif (( pct_delta > 0 )); then pct_str="+${pct_delta}% slower than" else pct_str="at" fi if within_threshold "$median" "$expected"; then echo "OK: ${median}ms <= ${allowed}ms, ${pct_str} baseline ${expected}ms (+10% threshold)" return 0 else echo "FAIL: ${median}ms > ${allowed}ms, ${pct_str} baseline ${expected}ms (+10% threshold)" [[ "$STRICT" == "1" ]] && return 1 return 0 fi } fail=0 should_run_type() { local wanted="$1" if [[ "$BENCH_TYPE" == "$wanted" ]]; then return 0 fi if [[ "$BENCH_TYPE" == "all" ]]; then [[ "$wanted" == "distance" || "$wanted" == "prominence" || "$wanted" == "io" ]] return fi return 1 } # ---------------- benchmark cases ---------------- # NOTE: CLI enforces --bench only for --kernel distance/prominence (default kernel is distance). if should_run_type distance; then # Medium (UCINET time_1) run_case "EIES48_T1_C1_W1" \ -i "$ROOT_DIR/src/data/Freeman_EIES_network_48actors_Acquaintanceship_at_time_1.dl" \ -f 5 -c 1 -w 1 -x 1 -k 0 --bench 20 || fail=1 # Medium (UCINET time_2) run_case "EIES48_T2_C1_W1" \ -i "$ROOT_DIR/src/data/Freeman_EIES_network_48actors_Acquaintanceship_at_time_2.dl" \ -f 5 -c 1 -w 1 -x 1 -k 0 --bench 20 || fail=1 # Large BA (500, m=3), centralities ON run_case "BA500_M3_C1_W0" \ -i "$ROOT_DIR/src/data/Benchmark_BA_Directed_N500_m3.paj" \ -f 2 -c 1 -w 0 -x 1 -k 0 --bench 20 || fail=1 # Large BA (500, m=3), distances only run_case "BA500_M3_C0_W0" \ -i "$ROOT_DIR/src/data/Benchmark_BA_Directed_N500_m3.paj" \ -f 2 -c 0 -w 0 -x 1 -k 0 --bench 20 || fail=1 # ---------------- large-net distance benchmark cases ---------------- # Only run if ~/socnetv/library/nets/large/ exists (not shipped with repo). # BA500 cases above are always the CI-reproducible fallback. if [[ -d "$LARGE_NETS_DIR" ]]; then run_case "DIST_GRAPHML_1000N_10000A_C0_W0" \ -i "$LARGE_NETS_DIR/1000actors-10000arcs.graphml" \ -f 1 -c 0 -w 0 -x 1 -k 0 --bench 2 || fail=1 run_case "DIST_GRAPHML_1000N_10000A_C1_W0" \ -i "$LARGE_NETS_DIR/1000actors-10000arcs.graphml" \ -f 1 -c 1 -w 0 -x 1 -k 0 --bench 2 || fail=1 else echo "[bench] LARGE_NETS_DIR not found ($LARGE_NETS_DIR), skipping large-net distance benchmarks" >&2 fi fi # ---------------- prominence benchmark cases ---------------- # N=500, not geom.net-scale: centralityInformation()/centralityEigenvector() are # O(N^3)-ish (matrix inversion / power iteration), impractically slow at N=7343 # for a benchmark that runs routinely. if should_run_type prominence; then run_case "PROM_BA500_M3" \ -i "$ROOT_DIR/src/data/Benchmark_BA_Directed_N500_m3.paj" \ -f 2 --kernel prominence --bench 20 || fail=1 fi # ---------------- IO roundtrip benchmark cases ---------------- # Only large nets (N>=50) are meaningful for IO timing. # Uses ~/socnetv/library/nets/large/ if available, falls back to src/data/. if should_run_type io; then # Always run the shipped dataset (reproducible in CI) run_case_io_bench "IO_PAJ_BA500" \ "$ROOT_DIR/src/data/Benchmark_BA_Directed_N500_m3.paj" 2 || fail=1 if [[ -d "$LARGE_NETS_DIR" ]]; then run_case_io_bench "IO_GRAPHML_2000N_40000E" \ "$LARGE_NETS_DIR/2000actors-40000edges.graphml" 1 || fail=1 run_case_io_bench "IO_GRAPHML_1000N_10000A" \ "$LARGE_NETS_DIR/1000actors-10000arcs.graphml" 1 || fail=1 else echo "[bench] LARGE_NETS_DIR not found ($LARGE_NETS_DIR), skipping large-net IO benchmarks" >&2 fi fi # ---------------- optional clustering timing (non-bench) ---------------- # NOTE: # - clustering kernel does NOT support --bench # - this is informational timing only (single run) # - not enforced in STRICT mode # - runs only with --type clustering run_case_clustering() { local tag="$1" shift echo "=== $tag ===" local start end ms out ok start=$(date +%s%3N 2>/dev/null || date +%s) if ! out="$("$SOCNETV_CLI" --kernel clustering "$@" 2>/dev/null)"; then echo "ERROR: socnetv-cli failed for $tag" >&2 return 2 fi end=$(date +%s%3N 2>/dev/null || date +%s) if [[ ${#start} -le 10 ]]; then ms=$(( (end - start) * 1000 )) else ms=$(( end - start )) fi ok="$(printf '%s\n' "$out" | extract_kv_int LOAD_OK | tail -n1)" echo "OK=$ok CLUSTERING_MS=$ms" if [[ "$ok" != "1" ]]; then echo "ERROR: clustering run failed for $tag" >&2 return 2 fi return 0 } if should_run_type clustering; then echo "[bench] Running clustering timing probes (informational only)" >&2 run_case_clustering "CLUST_KITE_N10" \ -i "$ROOT_DIR/src/data/Krackhardt_Kite_N10.paj" \ -f 2 -w 0 -x 1 -k 0 || fail=1 run_case_clustering "CLUST_SAMPSON_N18" \ -i "$ROOT_DIR/src/data/Sampson_Monks_N18.net" \ -f 2 -w 0 -x 1 -k 0 || fail=1 fi echo "=== DONE ===" if [[ "${RECORD}" == "1" ]]; then if [[ -n "${BENCH_BASELINE:-}" ]]; then OUT_FILE="${BENCH_BASELINE}" else SET_DIR="${BASELINE_ROOT}/$(sanitize_id "${BASELINE_SET_USED}")" mkdir -p "${SET_DIR}" OUT_FILE="${SET_DIR}/perf_expected.env" fi write_record_file "${OUT_FILE}" echo "INFO: wrote baseline: ${OUT_FILE}" >&2 echo "RESULT=OK" exit 0 fi if [[ "$fail" == "1" ]]; then echo "RESULT=FAIL" exit 1 fi echo "RESULT=OK" exit 0SocNetV-3.7/scripts/run_golden_compares.sh000077500000000000000000000527361524402210300207650ustar00rootroot00000000000000#!/usr/bin/env bash # run_golden_compares.sh β€” SocNetV CLI golden regression harness # # Runs all registered kernel cases and compares their output against # committed JSON baseline files. Exits non-zero if any case fails. # # Usage: # ./scripts/run_golden_compares.sh # SOCNETV_CLI=./build/socnetv-cli ./scripts/run_golden_compares.sh # # Kernels covered: # v1 distance β€” DistanceEngine + geodesic centralities # v2 reachability β€” reachability matrix (R(i,j) = 1 if finite geodesic) # v3 walks_matrix β€” walks matrix A^K # v4 prominence β€” all node-level centrality + prestige indices # v5 io_roundtrip β€” load β†’ export β†’ reload signature comparison # (export skipped for formats without exporter; # baseline locks in the skipped outcome too) # v6 clustering β€” clustering coefficient + triad census + clique census # v7 connectivity β€” weakly/strongly connected components count + per-node component IDs # v8 matrix β€” raw contents of every Matrix-producing operation (adjacency, # inverse, distances, similarity, reachability, walks, cliques) # v9 vertex_connectivity β€” local (Menger's theorem/max-flow) or global (pairwise-minimum) # vertex connectivity # # Baselines: # src/tools/baselines/distance/ (v1) # src/tools/baselines/reachability/ (v2) # src/tools/baselines/walks/ (v3) # src/tools/baselines/prominence/ (v4) # src/tools/baselines/io_roundtrip/ (v5) # src/tools/baselines/clustering/ (v6) # src/tools/baselines/connectivity/ (v7) # src/tools/baselines/matrix/ (v8) # src/tools/baselines/vertex_connectivity/ (v9) # # To add a new case: # 1. Run socnetv-cli --kernel ... --dump-json # 2. Commit the baseline JSON # 3. Add a run_case_ call below in the appropriate section # # To regenerate a baseline after a deliberate semantic fix: # Run the dump command again and commit the updated JSON. # Never regenerate baselines to silence a real regression. set -uo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" BUILD_TYPE="${BUILD_TYPE:-Debug}" # Debug|Release (hint only) # shellcheck source=/dev/null . "$ROOT_DIR/scripts/lib/find_socnetv_cli.sh" if [[ -n "${SOCNETV_CLI:-}" ]]; then CLI="$SOCNETV_CLI" else CLI="$(find_socnetv_cli "$ROOT_DIR" "$BUILD_TYPE" 2>/dev/null || true)" fi if [[ -z "${CLI:-}" || ! -x "$CLI" ]]; then echo "[ERROR] socnetv-cli not found/executable." >&2 echo "Hint: SOCNETV_CLI=/full/path/to/socnetv-cli $0" >&2 exit 2 fi echo "[golden] Using CLI: $CLI" BASE_DISTANCE="${ROOT_DIR}/src/tools/baselines/distance" BASE_REACH="${ROOT_DIR}/src/tools/baselines/reachability" BASE_WALKS="${ROOT_DIR}/src/tools/baselines/walks" BASE_PROM="${ROOT_DIR}/src/tools/baselines/prominence" BASE_IO="${ROOT_DIR}/src/tools/baselines/io_roundtrip" BASE_CLUST="${ROOT_DIR}/src/tools/baselines/clustering" BASE_CONN="${ROOT_DIR}/src/tools/baselines/connectivity" BASE_MATRIX="${ROOT_DIR}/src/tools/baselines/matrix" BASE_VCONN="${ROOT_DIR}/src/tools/baselines/vertex_connectivity" DATA="${ROOT_DIR}/src/data" if [[ ! -x "$CLI" ]]; then echo "[ERROR] socnetv-cli not found/executable at: $CLI" echo "Build it first (e.g. cmake --build build -j)." exit 2 fi FAILS=0 run_case() { local input="$1" local ftype="$2" local flags=("${@:3:${#}-3}") # all but last arg local baseline="${!#}" # last arg echo "==> $(basename "$baseline")" if ! "$CLI" -i "$input" -f "$ftype" "${flags[@]}" --compare-json "$baseline"; then echo "[FAIL] $(basename "$baseline")" FAILS=$((FAILS+1)) fi } run_case_reachability() { local input="$1" local ftype="$2" local flags=("${@:3:${#}-3}") local baseline="${!#}" echo "==> $(basename "$baseline")" if ! "$CLI" --kernel reachability -i "$input" -f "$ftype" "${flags[@]}" --compare-json "$baseline"; then echo "[FAIL] $(basename "$baseline")" FAILS=$((FAILS+1)) fi } run_case_walks() { local input="$1" local ftype="$2" local walks_len="$3" local flags=("${@:4:${#}-4}") local baseline="${!#}" echo "==> $(basename "$baseline")" if ! "$CLI" --kernel walks_matrix --walks-length "$walks_len" -i "$input" -f "$ftype" "${flags[@]}" --compare-json "$baseline"; then echo "[FAIL] $(basename "$baseline")" FAILS=$((FAILS+1)) fi } run_case_prominence() { local input="$1" local ftype="$2" local flags=("${@:3:${#}-3}") local baseline="${!#}" echo "==> $(basename "$baseline")" if ! "$CLI" --kernel prominence -i "$input" -f "$ftype" "${flags[@]}" --compare-json "$baseline"; then echo "[FAIL] $(basename "$baseline")" FAILS=$((FAILS+1)) fi } run_case_clustering() { local input="$1" local ftype="$2" local flags=("${@:3:${#}-3}") local baseline="${!#}" echo "==> $(basename "$baseline")" if ! "$CLI" --kernel clustering -i "$input" -f "$ftype" "${flags[@]}" --compare-json "$baseline"; then echo "[FAIL] $(basename "$baseline")" FAILS=$((FAILS+1)) fi } run_case_connectivity() { local input="$1" local ftype="$2" local flags=("${@:3:${#}-3}") local baseline="${!#}" echo "==> $(basename "$baseline")" if ! "$CLI" --kernel connectivity -i "$input" -f "$ftype" "${flags[@]}" --compare-json "$baseline"; then echo "[FAIL] $(basename "$baseline")" FAILS=$((FAILS+1)) fi } run_case_matrix() { local input="$1" local ftype="$2" local baseline="${!#}" echo "==> $(basename "$baseline")" if ! "$CLI" --kernel matrix -i "$input" -f "$ftype" -c 0 --compare-json "$baseline"; then echo "[FAIL] $(basename "$baseline")" FAILS=$((FAILS+1)) fi } run_case_vertex_connectivity() { local input="$1" local ftype="$2" local flags=("${@:3:${#}-3}") local baseline="${!#}" echo "==> $(basename "$baseline")" if ! "$CLI" --kernel vertex_connectivity -i "$input" -f "$ftype" "${flags[@]}" --compare-json "$baseline"; then echo "[FAIL] $(basename "$baseline")" FAILS=$((FAILS+1)) fi } run_case_io() { local input="$1" local ftype="$2" shift 2 local baseline="${!#}" local flags=() if (( $# > 1 )); then flags=("${@:1:$#-1}") fi echo "==> $(basename "$baseline")" if ! "$CLI" --kernel io_roundtrip -i "$input" -f "$ftype" \ ${flags[@]+"${flags[@]}"} --compare-json "$baseline"; then echo "[FAIL] $(basename "$baseline")" FAILS=$((FAILS+1)) fi } # --- Cases (extend this list as kernels grow) --- # DISTANCE (schema v1) run_case \ "${DATA}/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" \ 2 \ -c 1 -w 0 -x 1 -k 0 \ "${BASE_DISTANCE}/DunbarGelada_H22a__FT2__C1_W0_IW1_DI0.json" run_case \ "${DATA}/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" \ 2 \ -c 1 -w 1 -x 1 -k 0 \ "${BASE_DISTANCE}/DunbarGelada_H22a__FT2__C1_W1_IW1_DI0.json" run_case \ "${DATA}/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" \ 5 \ -c 1 -w 0 -x 1 -k 0 \ "${BASE_DISTANCE}/StokmanZiegler_Netherlands__FT5__C1_W0_IW1_DI0.json" run_case \ "${DATA}/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" \ 5 \ -c 1 -w 1 -x 1 -k 0 \ "${BASE_DISTANCE}/StokmanZiegler_Netherlands__FT5__C1_W1_IW1_DI0.json" # REACHABILITY (schema v2) run_case_reachability \ "${DATA}/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" \ 2 \ -w 1 -x 1 -k 0 -c 0 \ "${BASE_REACH}/DunbarGelada_H22a__REACH__V2.json" run_case_reachability \ "${DATA}/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" \ 5 \ -w 1 -x 1 -k 0 -c 0 \ "${BASE_REACH}/StokmanZiegler_Netherlands__REACH__V2.json" # WALKS MATRIX (schema v3) run_case_walks \ "${DATA}/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" \ 2 \ 6 \ -w 1 -x 1 -k 0 -c 0 \ "${BASE_WALKS}/DunbarGelada_H22a__WALKS_K6__V3.json" run_case_walks \ "${DATA}/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" \ 5 \ 6 \ -w 1 -x 1 -k 0 -c 0 \ "${BASE_WALKS}/StokmanZiegler_Netherlands__WALKS_K6__V3.json" run_case_walks \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ 2 \ -w 1 -x 1 -k 0 -c 0 \ "${BASE_WALKS}/TinyPath_N3_E2__WALKS_K2__V3.json" # PROMINENCE (schema v4) run_case_prominence \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ -w 0 -x 1 -k 0 \ "${BASE_PROM}/TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0.json" run_case_prominence \ "${DATA}/TinyDirChain_N3.paj" \ 2 \ -w 0 -x 1 -k 0 \ "${BASE_PROM}/TinyDirChain_N3__PROM__V4__FT2__W0_IW1_DI0.json" run_case_prominence \ "${DATA}/Krackhardt_Kite_N10.paj" \ 2 \ -w 0 -x 1 -k 0 \ "${BASE_PROM}/Krackhardt_Kite_N10__PROM__V4__FT2__W0_IW1_DI0.json" run_case_prominence \ "${DATA}/Krackhardt_Kite_N10.paj" \ 2 \ -w 1 -x 1 -k 0 \ "${BASE_PROM}/Krackhardt_Kite_N10__PROM__V4__FT2__W1_IW1_DI0.json" run_case_prominence \ "${DATA}/Sampson_Monks_N18.net" \ 2 \ -w 0 -x 1 -k 0 \ "${BASE_PROM}/Sampson_Monks_N18__PROM__V4__FT2__W0_IW1_DI0.json" # Weighted + isolate coverage (previously missing - see kernel_prominence_v4.cpp's # centralityInformation()/centralityEigenvector() arg-shift fix, which this combination # would have caught: considerWeights=1 with an isolate present, varying inverseWeights and # dropIsolates independently). run_case_prominence \ "${DATA}/TinyWeightedIsolate_Undir_N4_E2.paj" \ 2 \ -w 1 -x 1 -k 0 \ "${BASE_PROM}/TinyWeightedIsolate_Undir_N4_E2__PROM__V4__FT2__W1_IW1_DI0.json" run_case_prominence \ "${DATA}/TinyWeightedIsolate_Undir_N4_E2.paj" \ 2 \ -w 1 -x 1 -k 1 \ "${BASE_PROM}/TinyWeightedIsolate_Undir_N4_E2__PROM__V4__FT2__W1_IW1_DI1.json" run_case_prominence \ "${DATA}/TinyWeightedIsolate_Undir_N4_E2.paj" \ 2 \ -w 1 -x 0 -k 0 \ "${BASE_PROM}/TinyWeightedIsolate_Undir_N4_E2__PROM__V4__FT2__W1_IW0_DI0.json" # Katz/Bonacich on binary vs. real (non-unit) weights, both raw and inverted (WS11, #10/#39) - # closes a verification gap: every other Katz/Bonacich baseline above only ever exercised binary # adjacency (considerWeights=0), never real edge weights or the inverseWeights transformation. # All three computed independently via Gauss-Jordan elimination in plain Python against the same # A-B(2.5)-C(1.5) weighted path (isolate D dropped) before being dumped. run_case_prominence \ "${DATA}/TinyWeightedIsolate_Undir_N4_E2.paj" \ 2 \ -w 0 -x 1 -k 1 --katz-alpha 0.2 --bonacich-alpha 1 --bonacich-beta 0.3 \ "${BASE_PROM}/TinyWeightedIsolate_Undir_N4_E2__PROM__V4__FT2__W0_IW1_DI1_KA0.2_BA1_BB0.3.json" run_case_prominence \ "${DATA}/TinyWeightedIsolate_Undir_N4_E2.paj" \ 2 \ -w 1 -x 0 -k 1 --katz-alpha 0.171499 --bonacich-alpha 1 --bonacich-beta 0.171499 \ "${BASE_PROM}/TinyWeightedIsolate_Undir_N4_E2__PROM__V4__FT2__W1_IW0_DI1_KA0.171499_BA1_BB0.171499.json" run_case_prominence \ "${DATA}/TinyWeightedIsolate_Undir_N4_E2.paj" \ 2 \ -w 1 -x 1 -k 1 --katz-alpha 0.2 --bonacich-alpha 1 --bonacich-beta 0.3 \ "${BASE_PROM}/TinyWeightedIsolate_Undir_N4_E2__PROM__V4__FT2__W1_IW1_DI1_KA0.2_BA1_BB0.3.json" # Katz Centrality (WS11, #10) - each value independently cross-checked against a hand-derived # reference computation (Gauss-Jordan elimination of (I - alpha*A^T) in plain Python) before # being dumped, not just accepted as "whatever the code produced." run_case_prominence \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ -w 0 -x 1 -k 0 --katz-alpha 0.2 \ "${BASE_PROM}/TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0_KA0.2.json" run_case_prominence \ "${DATA}/TinyDirChain_N3.paj" \ 2 \ -w 0 -x 1 -k 0 --katz-alpha 0.5 \ "${BASE_PROM}/TinyDirChain_N3__PROM__V4__FT2__W0_IW1_DI0_KA0.5.json" # alpha=0.8 exceeds this graph's 1/lambda_max (~0.707) - locks in the boundary-rejection path # (all-zero KC/SKC), not just the happy path. run_case_prominence \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ -w 0 -x 1 -k 0 --katz-alpha 0.8 \ "${BASE_PROM}/TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0_KA0.8_reject.json" run_case_prominence \ "${DATA}/Krackhardt_Kite_N10.paj" \ 2 \ -w 0 -x 1 -k 0 --katz-alpha 0.1 \ "${BASE_PROM}/Krackhardt_Kite_N10__PROM__V4__FT2__W0_IW1_DI0_KA0.1.json" # Bonacich Power Centrality (WS11, #39) - each value independently cross-checked against a # hand-derived reference computation (Gauss-Jordan elimination of (I - beta*A^T) in plain Python) # before being dumped, same discipline as Katz above. run_case_prominence \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ -w 0 -x 1 -k 0 --bonacich-alpha 1 --bonacich-beta 0.5 \ "${BASE_PROM}/TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0_BA1_BB0.5.json" run_case_prominence \ "${DATA}/TinyDirChain_N3.paj" \ 2 \ -w 0 -x 1 -k 0 --bonacich-alpha 1 --bonacich-beta -0.5 \ "${BASE_PROM}/TinyDirChain_N3__PROM__V4__FT2__W0_IW1_DI0_BA1_BB-0.5.json" # beta=0.75 exceeds this graph's 1/lambda_max (~0.707) - locks in the boundary-rejection path # (all-zero BPC/SBPC), not just the happy path. run_case_prominence \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ -w 0 -x 1 -k 0 --bonacich-alpha 1 --bonacich-beta 0.75 \ "${BASE_PROM}/TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0_BA1_BB0.75_reject.json" # beta=-0.6 (negative, within bound) deliberately exercises Bonacich's signature sign-flip # behavior: node 2 (tied to both endpoints) gains a large positive score while both endpoints # come out NEGATIVE - hand-verified via the same Gauss-Jordan cross-check. run_case_prominence \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ -w 0 -x 1 -k 0 --bonacich-alpha 1 --bonacich-beta -0.6 \ "${BASE_PROM}/TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0_BA1_BB-0.6_negflip.json" run_case_prominence \ "${DATA}/Krackhardt_Kite_N10.paj" \ 2 \ -w 0 -x 1 -k 0 --bonacich-alpha 1 --bonacich-beta 0.1 \ "${BASE_PROM}/Krackhardt_Kite_N10__PROM__V4__FT2__W0_IW1_DI0_BA1_BB0.1.json" # Directed acyclic (nilpotent) + weighted, alpha/beta=1 (WS11, #10/#39) - locks in the # Matrix::powerIteration() fix for lambdaMax on nilpotent matrices (any directed graph with no # cycles - a DAG/tree/chain): the true lambda_max is exactly 0 there (no convergence bound, any # value works), but a pre-existing divide-by-zero safety substitution was incorrectly reported # as lambdaMax=1 instead, causing Katz/Bonacich to wrongly reject alpha/beta >= 1 on such graphs. # alpha=beta=1 here would have been (incorrectly) rejected before the fix. Both raw and inverted # weights independently verified via Gauss-Jordan elimination in plain Python. run_case_prominence \ "${DATA}/TinyDirWeighted_N3.paj" \ 2 \ -w 1 -x 0 -k 0 --katz-alpha 1 --bonacich-alpha 1 --bonacich-beta 1 \ "${BASE_PROM}/TinyDirWeighted_N3__PROM__V4__FT2__W1_IW0_DI0_KA1_BA1_BB1.json" run_case_prominence \ "${DATA}/TinyDirWeighted_N3.paj" \ 2 \ -w 1 -x 1 -k 0 --katz-alpha 1 --bonacich-alpha 1 --bonacich-beta 1 \ "${BASE_PROM}/TinyDirWeighted_N3__PROM__V4__FT2__W1_IW1_DI0_KA1_BA1_BB1.json" # IO ROUNDTRIP (schema v5) run_case_io "${DATA}/TinyAdj_Undir_N3.adj" 3 -d " " -l 0 "${BASE_IO}/TinyAdj_Undir_N3__FT3.json" run_case_io "${DATA}/TinyAdj_Weighted_Dir_N3.adj" 3 -d " " -l 0 "${BASE_IO}/TinyAdj_Weighted_Dir_N3__FT3.json" run_case_io "${DATA}/TinyGraphML_Weighted_Dir_N3.graphml" 1 "${BASE_IO}/TinyGraphML_Weighted_Dir_N3__FT1.json" run_case_io "${DATA}/Padgett_Florentine_Families.paj" 2 "${BASE_IO}/Padgett_Florentine_Families__FT2_multirel.json" run_case_io "${DATA}/Benchmark_BA_Directed_N500_m3.paj" 2 "${BASE_IO}/Benchmark_BA_Directed_N500_m3__FT2_big.json" # Skipped-export formats still get compared (they should remain skipped) run_case_io "${DATA}/TinyGraphviz_Dir_N3.dot" 4 "${BASE_IO}/TinyGraphviz_Dir_N3__FT4.json" run_case_io "${DATA}/TinyGML_Weighted_Dir_N3.gml" 6 "${BASE_IO}/TinyGML_Weighted_Dir_N3__FT6_weighted.json" run_case_io "${DATA}/TinyEdgeList_Weighted_Dir_N3.wlst" 7 "${BASE_IO}/TinyEdgeList_Weighted_Dir_N3__FT7.json" run_case_io "${DATA}/TinyGraphviz_Undir_N3.dot" 4 "${BASE_IO}/TinyGraphviz_Undir_N3__FT4.json" # UCINET FT5 (export not supported β€” load + signature baseline only) run_case_io "${DATA}/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" 5 "${BASE_IO}/StokmanZiegler_Netherlands__FT5__IO__V5.json" run_case_io "${DATA}/Bernard_Killworth_Fraternity.dl" 5 "${BASE_IO}/Bernard_Killworth_Fraternity__FT5__IO__V5.json" # CLUSTERING (schema v6) run_case_clustering \ "${DATA}/Krackhardt_Kite_N10.paj" \ 2 \ -w 0 -x 1 -k 0 \ "${BASE_CLUST}/Krackhardt_Kite_N10__CLUST__V6__FT2__W0_IW1_DI0.json" run_case_clustering \ "${DATA}/TinyDirChain_N3.paj" \ 2 \ -w 0 -x 1 -k 0 \ "${BASE_CLUST}/TinyDirChain_N3__CLUST__V6__FT2__W0_IW1_DI0.json" run_case_clustering \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ -w 0 -x 1 -k 0 \ "${BASE_CLUST}/TinyPath_N3_E2__CLUST__V6__FT2__W0_IW1_DI0.json" run_case_clustering \ "${DATA}/Sampson_Monks_N18.net" \ 2 \ -w 0 -x 1 -k 0 \ "${BASE_CLUST}/Sampson_Monks_N18__CLUST__V6__FT2__W0_IW1_DI0.json" run_case_clustering \ "${DATA}/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" \ 5 \ -w 0 -x 1 -k 0 \ "${BASE_CLUST}/StokmanZiegler_Netherlands__CLUST__V6__FT5__W0_IW1_DI0.json" run_case_clustering \ "${DATA}/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" \ 5 \ -w 1 -x 1 -k 0 \ "${BASE_CLUST}/StokmanZiegler_Netherlands__CLUST__V6__FT5__W1_IW1_DI0.json" run_case_clustering \ "${DATA}/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" \ 2 \ -w 0 -x 1 -k 0 \ "${BASE_CLUST}/DunbarGelada_H22a__CLUST__V6__FT2__W0_IW1_DI0.json" run_case_clustering \ "${DATA}/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" \ 2 \ -w 1 -x 1 -k 0 \ "${BASE_CLUST}/DunbarGelada_H22a__CLUST__V6__FT2__W1_IW1_DI0.json" # CONNECTIVITY (schema v7) run_case_connectivity \ "${DATA}/TinyDisconnected_Undir_N6_E4.paj" \ 2 \ "${BASE_CONN}/TinyDisconnected_Undir_N6_E4__CONN__V7__FT2.json" run_case_connectivity \ "${DATA}/TinyDisconnected_Dir_N5_E3.paj" \ 2 \ "${BASE_CONN}/TinyDisconnected_Dir_N5_E3__CONN__V7__FT2.json" run_case_connectivity \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ "${BASE_CONN}/TinyPath_N3_E2__CONN__V7__FT2.json" run_case_connectivity \ "${DATA}/TinyIsolated_Dir_N2_E0.paj" \ 2 \ "${BASE_CONN}/TinyIsolated_Dir_N2_E0__CONN__V7__FT2.json" run_case_connectivity \ "${DATA}/TinyArc_Dir_N2_E1.paj" \ 2 \ "${BASE_CONN}/TinyArc_Dir_N2_E1__CONN__V7__FT2.json" run_case_connectivity \ "${DATA}/TinyWeaklyConn_Dir_N3_E2.paj" \ 2 \ "${BASE_CONN}/TinyWeaklyConn_Dir_N3_E2__CONN__V7__FT2.json" run_case_connectivity \ "${DATA}/TinyIsolated_Undir_N3_E0.paj" \ 2 \ "${BASE_CONN}/TinyIsolated_Undir_N3_E0__CONN__V7__FT2.json" # Strong connectivity (--connectivity-type strong) - only meaningfully distinct from weak on # directed graphs, so only the datasets above that actually parse as directed get a strong-mode # baseline too (TinyIsolated_Dir_N2_E0 has zero edges and parses as undirected, so it's skipped). run_case_connectivity \ "${DATA}/TinyDisconnected_Dir_N5_E3.paj" \ 2 \ --connectivity-type strong \ "${BASE_CONN}/TinyDisconnected_Dir_N5_E3__CONN__V7__FT2__STRONG.json" run_case_connectivity \ "${DATA}/TinyArc_Dir_N2_E1.paj" \ 2 \ --connectivity-type strong \ "${BASE_CONN}/TinyArc_Dir_N2_E1__CONN__V7__FT2__STRONG.json" run_case_connectivity \ "${DATA}/TinyWeaklyConn_Dir_N3_E2.paj" \ 2 \ --connectivity-type strong \ "${BASE_CONN}/TinyWeaklyConn_Dir_N3_E2__CONN__V7__FT2__STRONG.json" # MATRIX (schema v8) - see WS6.7 in roadmap_ws6_testing_ci_regression.md. # Note: Benchmark_BA_Directed_N500_m3 is dumped in summary mode (row/col sums, trace, # sampled cells) and skips the total_walks category - see kTotalWalksSkipThreshold in # kernel_matrix_v8.cpp for why (total walks alone measured ~9 min at N=500). run_case_matrix \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ "${BASE_MATRIX}/TinyPath_N3_E2__MATRIX__V8__FT2__W0_IW1_DI0.json" run_case_matrix \ "${DATA}/TinyDisconnected_Undir_N6_E4.paj" \ 2 \ "${BASE_MATRIX}/TinyDisconnected_Undir_N6_E4__MATRIX__V8__FT2__W0_IW1_DI0.json" run_case_matrix \ "${DATA}/Benchmark_BA_Directed_N500_m3.paj" \ 2 \ "${BASE_MATRIX}/Benchmark_BA_Directed_N500_m3__MATRIX__V8__FT2__W0_IW1_DI0.json" # VERTEX CONNECTIVITY (schema v9) - deliberately Tiny*/toy datasets only. The global mode's # pairwise-minimum algorithm is O(n^2) local-connectivity computations in the worst case (see # Graph::graphConnectivity()'s doc comment) - fine for a handful of nodes, not for the # Benchmark_*/500-node datasets used elsewhere in this file. run_case_vertex_connectivity \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ --conn-mode global \ "${BASE_VCONN}/TinyPath_N3_E2__VCONN__V9__FT2__global.json" run_case_vertex_connectivity \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ --conn-mode local --conn-source 1 --conn-target 3 \ "${BASE_VCONN}/TinyPath_N3_E2__VCONN__V9__FT2__local_1_3.json" run_case_vertex_connectivity \ "${DATA}/TinyPath_N3_E2.paj" \ 2 \ --conn-mode local --conn-source 1 --conn-target 2 \ "${BASE_VCONN}/TinyPath_N3_E2__VCONN__V9__FT2__local_1_2_adjacent.json" run_case_vertex_connectivity \ "${DATA}/TinyDisconnected_Undir_N6_E4.paj" \ 2 \ --conn-mode global \ "${BASE_VCONN}/TinyDisconnected_Undir_N6_E4__VCONN__V9__FT2__global.json" run_case_vertex_connectivity \ "${DATA}/TinyWeaklyConn_Dir_N3_E2.paj" \ 2 \ --conn-mode global --connectivity-type weak \ "${BASE_VCONN}/TinyWeaklyConn_Dir_N3_E2__VCONN__V9__FT2__global_weak.json" run_case_vertex_connectivity \ "${DATA}/TinyWeaklyConn_Dir_N3_E2.paj" \ 2 \ --conn-mode global --connectivity-type strong \ "${BASE_VCONN}/TinyWeaklyConn_Dir_N3_E2__VCONN__V9__FT2__global_strong.json" run_case_vertex_connectivity \ "${DATA}/TinyComplete_Undir_N4_E6.paj" \ 2 \ --conn-mode global \ "${BASE_VCONN}/TinyComplete_Undir_N4_E6__VCONN__V9__FT2__global.json" echo if [[ "$FAILS" -eq 0 ]]; then echo "[OK] All golden comparisons passed." exit 0 else echo "[ERROR] Golden comparisons failed: $FAILS case(s)." exit 1 fiSocNetV-3.7/scripts/run_golden_io_roundtrip.sh000077500000000000000000000042121524402210300216530ustar00rootroot00000000000000#!/bin/sh set -eu # Runs the io_roundtrip kernel for all JSON baselines in src/tools/baselines/io_roundtrip/*.json. # Default: compares against each baseline. # With --update: regenerates each baseline JSON in-place from current code. UPDATE=0 KEEP_BAK=1 ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) BUILD_TYPE=${BUILD_TYPE:-Debug} . "$ROOT_DIR/scripts/lib/find_socnetv_cli.sh" if [ -n "${SOCNETV_CLI:-}" ]; then CLI="$SOCNETV_CLI" else CLI=$(find_socnetv_cli "$ROOT_DIR" "$BUILD_TYPE" 2>/dev/null || true) fi if [ -z "${CLI:-}" ] || [ ! -x "$CLI" ]; then echo "[ERROR] socnetv-cli not found/executable." >&2 echo "Hint: SOCNETV_CLI=/full/path/to/socnetv-cli $0" >&2 exit 2 fi echo "[io] Using CLI: $CLI" usage() { echo "Usage: $0 [--update] [--no-bak]" >&2 exit 2 } while [ $# -gt 0 ]; do case "$1" in --update) UPDATE=1 ;; --no-bak) KEEP_BAK=0 ;; -h|--help) usage ;; *) echo "Unknown arg: $1" >&2; usage ;; esac shift done for j in src/tools/baselines/io_roundtrip/*.json; do echo "json filename: $j" [ -f "$j" ] || continue # Extract dataset.path (a JSON string) and dataset.filetype (a JSON number) path=$( tr -d '\n' < "$j" \ | sed -n 's/.*"dataset"[[:space:]]*:[[:space:]]*{[^}]*"path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' ) ft=$( tr -d '\n' < "$j" \ | sed -n 's/.*"dataset"[[:space:]]*:[[:space:]]*{[^}]*"filetype"[[:space:]]*:[[:space:]]*\([-0-9][0-9]*\).*/\1/p' ) [ -n "$path" ] || { echo "ERROR: dataset.path not found in $j" >&2; exit 1; } [ -n "$ft" ] || { echo "ERROR: dataset.filetype not found in $j" >&2; exit 1; } [ -f "$path" ] || { echo "ERROR: dataset.path '$path' not found on disk (baseline $j)" >&2; exit 1; } echo "==> $j (dataset=$path FT=$ft)" if [ "$UPDATE" -eq 1 ]; then tmp="$(mktemp "${j}.tmp.XXXXXX")" # Generate fresh JSON and overwrite baseline "$CLI" --kernel io_roundtrip -i "$path" -f "$ft" --dump-json "$tmp" >/dev/null if [ "$KEEP_BAK" -eq 1 ]; then cp -f "$j" "${j}.bak" fi mv -f "$tmp" "$j" echo "UPDATED: $j" else "$CLI" --kernel io_roundtrip -i "$path" -f "$ft" --compare-json "$j" || exit 1 fi doneSocNetV-3.7/scripts/run_io_roundtrip_shipped_datasets.sh000077500000000000000000000021561524402210300237340ustar00rootroot00000000000000#!/usr/bin/env bash set -uo pipefail # Runs the io_roundtrip kernel for all datasets shipped in src/data, using the filetype inferred from the extension. # The kernel is run without --compare-json ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" BUILD_TYPE="${BUILD_TYPE:-Debug}" # shellcheck source=/dev/null . "$ROOT_DIR/scripts/lib/find_socnetv_cli.sh" if [[ -n "${SOCNETV_CLI:-}" ]]; then CLI="$SOCNETV_CLI" else CLI="$(find_socnetv_cli "$ROOT_DIR" "$BUILD_TYPE" 2>/dev/null || true)" fi if [[ -z "${CLI:-}" || ! -x "$CLI" ]]; then echo "[ERROR] socnetv-cli not found/executable." >&2 echo "Hint: SOCNETV_CLI=/full/path/to/socnetv-cli $0" >&2 exit 2 fi echo "[io_all] Using CLI: $CLI" # Safe iteration over filenames while IFS= read -r -d '' f; do case "$f" in *.graphml) ft=1 ;; *.paj|*.net) ft=2 ;; *.adj) ft=3 ;; *.dot) ft=4 ;; *.dl) ft=5 ;; *.gml) ft=6 ;; *.wlst) ft=7 ;; *.lst) ft=8 ;; *) continue ;; esac echo "=== IO_ROUNDTRIP $f (FT$ft) ===" "$CLI" --kernel io_roundtrip -i "$f" -f "$ft" || exit 1 done < <(find "$ROOT_DIR/src/data" -type f -print0)SocNetV-3.7/scripts/run_matrix_storage_bench.sh000077500000000000000000000050201524402210300217730ustar00rootroot00000000000000#!/usr/bin/env bash # run_matrix_storage_bench.sh β€” WS5 A2.0 empirical validation driver # # Runs the standalone matrix-storage-bench tool once per (N, topology, structure) # configuration β€” one process per run, so each measurement starts from a clean process. # Wraps each run in the platform's time tool to get an accurate peak-RSS reading: the # tool's own in-process attempts at this (mach_task_basic_info, getrusage) were found to # under-report by 6x-14x for this tool's tight compute-bound shape (see the comment above # buildComponents() in src/tools/matrix_storage_bench.cpp) β€” only the parent-side wait4() # view that `time -l`/`time -v` use is accurate here. # # Usage: # cmake -S . -B build -DBUILD_MATRIX_BENCH=ON ... # cmake --build build --target matrix-storage-bench -j$(nproc) # ./scripts/run_matrix_storage_bench.sh # MATRIX_BENCH=./build/matrix-storage-bench ./scripts/run_matrix_storage_bench.sh set -uo pipefail BENCH="${MATRIX_BENCH:-./build/matrix-storage-bench}" if [[ ! -x "$BENCH" ]]; then echo "error: $BENCH not found or not executable." >&2 echo "Build it first: cmake -S . -B build -DBUILD_MATRIX_BENCH=ON && cmake --build build --target matrix-storage-bench" >&2 exit 1 fi PLATFORM="$(uname)" HAVE_GNU_TIME_V=0 if [[ "$PLATFORM" != "Darwin" ]] && /usr/bin/time -v true >/dev/null 2>&1; then HAVE_GNU_TIME_V=1 fi run_one() { local n="$1" topology="$2" structure="$3" local stderr_file csv_line peak_rss_bytes stderr_file="$(mktemp)" if [[ "$PLATFORM" == "Darwin" ]]; then csv_line="$(/usr/bin/time -l "$BENCH" --n "$n" --topology "$topology" --structure "$structure" 2>"$stderr_file")" peak_rss_bytes="$(awk '/maximum resident set size/ {print $1}' "$stderr_file")" elif [[ "$HAVE_GNU_TIME_V" -eq 1 ]]; then csv_line="$(/usr/bin/time -v "$BENCH" --n "$n" --topology "$topology" --structure "$structure" 2>"$stderr_file")" local peak_rss_kb peak_rss_kb="$(awk -F': ' '/Maximum resident set size/ {print $2}' "$stderr_file")" peak_rss_bytes="$((peak_rss_kb * 1024))" else csv_line="$("$BENCH" --n "$n" --topology "$topology" --structure "$structure")" peak_rss_bytes="NA" fi rm -f "$stderr_file" echo "${csv_line},${peak_rss_bytes}" } echo "n,topology,structure,construct_ms,lookup_ms,checksum,peak_rss_bytes" for n in 100 1000 7343; do for topology in connected disconnected giant; do for structure in qhash matrix; do run_one "$n" "$topology" "$structure" done done done SocNetV-3.7/scripts/run_render_perf_bench.sh000077500000000000000000000224251524402210300212460ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail # run_render_perf_bench.sh β€” WS10/WS6.6 canvas rendering-performance regression kernel (#240) # # Drives the real SocNetV GUI binary through --interactive-script (WS12) with # QT_QPA_PLATFORM=offscreen (no visible window, but the real QGraphicsScene/QGraphicsView # paint/geometry code path - not a reimplementation). Compares each BENCH line's elapsed_ms # against an upper-bound threshold: "must be faster than X ms", not exact equality, since # GUI-driven timing has more run-to-run variance than the headless compute kernels. # # Runs the full fixture RENDER_BENCH_RUNS times (default 7) and uses the per-metric MEDIAN # across runs, both when recording and when comparing - a single-shot measurement was found to # vary run-to-run by ~25% on the recording machine, which made both the recorded threshold and # any comparison against it noisy in either direction. Matches run_benchmarks.sh's median-of-N # convention for the headless compute kernels (there via the CLI's own --bench flag; here via an # external loop, since each run launches a fresh GUI process). # # Why offscreen, and why its numbers differ from a real on-screen session (measured, not # assumed): on the recording machine, the `render` command is ~2x-2.4x FASTER offscreen than # on-screen (no compositor/backing-store round trip) - but non-paint bulk operations # (bulk-node-size, bulk-edge-color) measure the same in both modes. So these thresholds are # only meaningful compared against other offscreen runs, not against a real user's on-screen # experience; they exist to catch regressions in the underlying paint/geometry code, not to # report perceived performance. See roadmap_ws6_testing_ci_regression.md's WS6.6 section for # the full numbers. # # Usage: # ./scripts/run_render_perf_bench.sh # ./scripts/run_render_perf_bench.sh --record # RENDER_BENCH_RUNS=11 ./scripts/run_render_perf_bench.sh --record # RENDER_BENCH_BASELINE_SET=macos-m5 ./scripts/run_render_perf_bench.sh # SOCNETV_GUI=./build/SocNetV.app/Contents/MacOS/SocNetV ./scripts/run_render_perf_bench.sh # # Baseline selection priority: # 1) RENDER_BENCH_BASELINE=/path/to/render_perf_expected.env # 2) RENDER_BENCH_BASELINE_SET= (scripts/perf_baselines//render_perf_expected.env) # 3) auto: - (scripts/perf_baselines//render_perf_expected.env) RECORD=0 BUILD_TYPE="${BENCH_BUILD_TYPE:-Debug}" # hint only, matches run_benchmarks.sh's convention RUNS="${RENDER_BENCH_RUNS:-7}" while [[ $# -gt 0 ]]; do case "$1" in --record) RECORD=1; shift ;; *) echo "ERROR: unknown argument: $1" >&2 exit 2 ;; esac done if ! [[ "$RUNS" =~ ^[0-9]+$ ]] || (( RUNS < 1 )); then echo "ERROR: RENDER_BENCH_RUNS must be a positive integer, got: $RUNS" >&2 exit 2 fi ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" FIXTURE="$ROOT_DIR/scripts/fixtures/render_perf_script.txt" BASELINE_ROOT="$ROOT_DIR/scripts/perf_baselines" # shellcheck source=/dev/null . "$ROOT_DIR/scripts/lib/find_socnetv_gui.sh" if [[ -n "${SOCNETV_GUI:-}" ]]; then if [[ ! -x "$SOCNETV_GUI" ]]; then echo "ERROR: SOCNETV_GUI is set but not executable: $SOCNETV_GUI" >&2 exit 1 fi else SOCNETV_GUI="$(find_socnetv_gui "$ROOT_DIR" "$BUILD_TYPE" 2>/dev/null || true)" fi if [[ -z "${SOCNETV_GUI:-}" || ! -x "$SOCNETV_GUI" ]]; then echo "ERROR: Could not find the SocNetV GUI binary. Build the SocNetV target first." >&2 echo "Hint: SOCNETV_GUI=/full/path/to/SocNetV $0" >&2 exit 1 fi sanitize_id() { echo "$1" | tr -cd '[:alnum:]._-' | sed 's/^\.*//; s/\.*$//' } auto_baseline_set() { local os arch os="$(uname -s 2>/dev/null || echo unknownOS)" arch="$(uname -m 2>/dev/null || echo unknownARCH)" case "$os" in Darwin) os="macos" ;; Linux) os="linux" ;; *) os="$(echo "$os" | tr '[:upper:]' '[:lower:]')" ;; esac sanitize_id "${os}-${arch}" } resolve_expected_file() { if [[ -n "${RENDER_BENCH_BASELINE:-}" ]]; then echo "${RENDER_BENCH_BASELINE}" return 0 fi local set_name set_name="$(sanitize_id "${RENDER_BENCH_BASELINE_SET:-$(auto_baseline_set)}")" echo "${BASELINE_ROOT}/${set_name}/render_perf_expected.env" } # Prints the median of its integer arguments to stdout. Odd count picks the middle sorted # value directly; even count averages the two middle sorted values (integer division). median_of() { local -a sorted mapfile -t sorted < <(printf '%s\n' "$@" | sort -n) local n="${#sorted[@]}" local mid=$(( n / 2 )) if (( n % 2 == 1 )); then echo "${sorted[$mid]}" else echo $(( (sorted[mid - 1] + sorted[mid]) / 2 )) fi } EXPECTED_FILE="$(resolve_expected_file)" RESOLVED_SET="$(sanitize_id "${RENDER_BENCH_BASELINE_SET:-$(auto_baseline_set)}")" BASELINE_SET_USED="${RESOLVED_SET}" [[ -n "${RENDER_BENCH_BASELINE:-}" ]] && BASELINE_SET_USED="custom" if [[ "${RECORD}" == "1" && -n "${RENDER_BENCH_BASELINE_SET:-}" && "${RENDER_BENCH_RECORD_ALLOW_OVERRIDE:-0}" != "1" ]]; then AUTO_SET="$(auto_baseline_set)" if [[ "${RESOLVED_SET}" != "${AUTO_SET}" ]]; then echo "ERROR: refusing to --record into RENDER_BENCH_BASELINE_SET=${RENDER_BENCH_BASELINE_SET} (auto=${AUTO_SET})." >&2 echo "Run on the target machine, or set RENDER_BENCH_RECORD_ALLOW_OVERRIDE=1 to force." >&2 exit 2 fi fi if [[ "${RECORD}" != "1" ]]; then if [[ ! -f "$EXPECTED_FILE" ]]; then echo "INFO: no render-perf baseline for this machine (${EXPECTED_FILE} not found)." >&2 echo "Hint: run with --record on this machine to create one. Continuing with all metrics SKIPPED." >&2 else # shellcheck disable=SC1090 source "$EXPECTED_FILE" fi fi echo "INFO: SOCNETV_GUI=$SOCNETV_GUI BASELINE_SET=${BASELINE_SET_USED} EXPECTED_FILE=${EXPECTED_FILE} RECORD=${RECORD} RUNS=${RUNS}" >&2 NAMES=(RENDER_INITIAL BULK_NODE_SIZE BULK_EDGE_COLOR MOVE RENDER_AFTER_MUTATIONS) declare -a RUN_VALUES_0=() RUN_VALUES_1=() RUN_VALUES_2=() RUN_VALUES_3=() RUN_VALUES_4=() for (( run = 1; run <= RUNS; run++ )); do RAW_OUTPUT="$(QT_QPA_PLATFORM=offscreen "$SOCNETV_GUI" --interactive-script "$FIXTURE" 2>&1)" # Allowlist match on the metric commands only - every interactive-script command logs its own # "BENCH ..." line (uniform logging added after this kernel first shipped), so a bare # "BENCH " grep would also pick up setup/teardown commands (erdos-m, delay, quit, ...) and # shift every downstream value out of alignment. Matching by name is robust against the # fixture script gaining more setup commands later without needing this filter updated again. BENCH_LINES="$(echo "$RAW_OUTPUT" | grep -E "^BENCH (render|bulk-node-size|bulk-edge-color|move) " || true)" if [[ -z "$BENCH_LINES" ]]; then echo "ERROR: no BENCH lines produced on run ${run}/${RUNS}. Full output:" >&2 echo "$RAW_OUTPUT" >&2 exit 1 fi echo "--- run ${run}/${RUNS} ---" >&2 echo "$BENCH_LINES" >&2 # BENCH lines appear in fixed script order: render, bulk-node-size, bulk-edge-color, move, render. declare -a MS_VALUES=() while IFS= read -r line; do # qInfo()'s stream operator inserts a space after "elapsed_ms=" between the two << operands. MS_VALUES+=("$(echo "$line" | sed -nE 's/.*elapsed_ms= *(-?[0-9]+).*/\1/p')") done <<< "$BENCH_LINES" if [[ "${#MS_VALUES[@]}" -ne "${#NAMES[@]}" ]]; then echo "ERROR: expected ${#NAMES[@]} BENCH lines on run ${run}/${RUNS}, got ${#MS_VALUES[@]}." >&2 exit 1 fi RUN_VALUES_0+=("${MS_VALUES[0]}") RUN_VALUES_1+=("${MS_VALUES[1]}") RUN_VALUES_2+=("${MS_VALUES[2]}") RUN_VALUES_3+=("${MS_VALUES[3]}") RUN_VALUES_4+=("${MS_VALUES[4]}") done MEDIANS=( "$(median_of "${RUN_VALUES_0[@]}")" "$(median_of "${RUN_VALUES_1[@]}")" "$(median_of "${RUN_VALUES_2[@]}")" "$(median_of "${RUN_VALUES_3[@]}")" "$(median_of "${RUN_VALUES_4[@]}")" ) for i in "${!NAMES[@]}"; do echo "MEDIAN ${NAMES[$i]}=${MEDIANS[$i]}ms (of ${RUNS} runs)" done if [[ "${RECORD}" == "1" ]]; then OUT_DIR="${BASELINE_ROOT}/${BASELINE_SET_USED}" mkdir -p "$OUT_DIR" OUT_FILE="${OUT_DIR}/render_perf_expected.env" { echo "# Auto-generated by scripts/run_render_perf_bench.sh --record" echo "# BASELINE_SET=${BASELINE_SET_USED}" echo "# BASELINE_FILE=scripts/perf_baselines/${BASELINE_SET_USED}/render_perf_expected.env" echo "# Thresholds are 2x the median-of-${RUNS} measured reference run (upper bound, not exact-value)." echo for i in "${!NAMES[@]}"; do value="${MEDIANS[$i]:-0}" threshold=$(( value * 2 )) # Floor at 15ms: some operations (e.g. a single-vertex "move") measure near-instant, and # doubling a 0-1ms reading would produce a threshold too tight to survive normal jitter. if (( threshold < 15 )); then threshold=15 fi echo "EXP_${NAMES[$i]}_MAX_MS=${threshold}" done } > "$OUT_FILE" echo "Recorded: $OUT_FILE" exit 0 fi FAIL=0 for i in "${!NAMES[@]}"; do name="${NAMES[$i]}" value="${MEDIANS[$i]:-}" var="EXP_${name}_MAX_MS" expected="${!var:-}" if [[ -z "$expected" ]]; then echo "SKIP: $name (no baseline for $var)" continue fi if [[ -z "$value" ]]; then echo "FAIL: $name - no measurement captured" FAIL=1 continue fi if (( value > expected )); then echo "FAIL: $name median=${value}ms exceeds threshold=${expected}ms" FAIL=1 else echo "OK: $name median=${value}ms within threshold=${expected}ms" fi done exit "$FAIL" SocNetV-3.7/scripts/run_report_export_bench.sh000077500000000000000000000126111524402210300216630ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail # run_report_export_bench.sh β€” WS16 (#113, CSV report export) baseline/regression tool # # Rough before/after measurement tool for Graph::write*() report-writer functions # (src/graph/reporting/graph_reports.cpp), driven headlessly via --interactive-script (WS12), # since socnetv-cli's kernels never call these functions (they only ever emit JSON) - this is # the only headless path that can reach a report writer end-to-end (compute + file I/O). # # Covers the matrix-family 'distances' command (Step 1), all 12 centrality/prestige report # commands (Step 2), and the Step 3 long tail (Reciprocity, standalone Eccentricity, Clustering # Coefficient, Triad Census) - see the fixtures for the exact list. # # Deliberately NOT a CI-threshold-gated regression kernel like run_render_perf_bench.sh's # EXP_*_MAX_MS baselines - this exists to capture a rough "before" number ahead of the WS16 # CSV-export work, and a comparable "after" number once each step lands. Formal threshold-gating # can follow later if this becomes a standing regression concern, matching the plan's own # "rough baselines" framing rather than building out the full apparatus pre-emptively. # # Usage: # ./scripts/run_report_export_bench.sh # REPORT_BENCH_RUNS=11 ./scripts/run_report_export_bench.sh # SOCNETV_GUI=./build/SocNetV.app/Contents/MacOS/SocNetV ./scripts/run_report_export_bench.sh BUILD_TYPE="${BENCH_BUILD_TYPE:-Debug}" # hint only, matches run_benchmarks.sh's convention RUNS="${REPORT_BENCH_RUNS:-5}" if ! [[ "$RUNS" =~ ^[0-9]+$ ]] || (( RUNS < 1 )); then echo "ERROR: REPORT_BENCH_RUNS must be a positive integer, got: $RUNS" >&2 exit 2 fi ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" FIXTURES=( "$ROOT_DIR/scripts/fixtures/report_export_bench_small.txt" "$ROOT_DIR/scripts/fixtures/report_export_bench_large.txt" ) # Every report command the fixtures exercise, in the order they appear there. Kept as one list # so adding a new report command to a fixture only requires adding its name here too. COMMANDS=( distances report-centrality-degree report-centrality-closeness report-centrality-closeness-ir report-centrality-betweenness report-centrality-stress report-centrality-eccentricity report-centrality-power report-centrality-information report-centrality-eigenvector report-prestige-degree report-prestige-proximity report-prestige-pagerank report-reciprocity report-eccentricity report-clustering-coefficient report-triad-census ) # shellcheck source=/dev/null . "$ROOT_DIR/scripts/lib/find_socnetv_gui.sh" if [[ -n "${SOCNETV_GUI:-}" ]]; then if [[ ! -x "$SOCNETV_GUI" ]]; then echo "ERROR: SOCNETV_GUI is set but not executable: $SOCNETV_GUI" >&2 exit 1 fi else SOCNETV_GUI="$(find_socnetv_gui "$ROOT_DIR" "$BUILD_TYPE" 2>/dev/null || true)" fi if [[ -z "${SOCNETV_GUI:-}" || ! -x "$SOCNETV_GUI" ]]; then echo "ERROR: Could not find the SocNetV GUI binary. Build the SocNetV target first." >&2 echo "Hint: SOCNETV_GUI=/full/path/to/SocNetV $0" >&2 exit 1 fi # Prints the median of its integer arguments to stdout. Odd count picks the middle sorted # value directly; even count averages the two middle sorted values (integer division). median_of() { local -a sorted mapfile -t sorted < <(printf '%s\n' "$@" | sort -n) local n="${#sorted[@]}" local mid=$(( n / 2 )) if (( n % 2 == 1 )); then echo "${sorted[$mid]}" else echo $(( (sorted[mid - 1] + sorted[mid]) / 2 )) fi } echo "INFO: SOCNETV_GUI=$SOCNETV_GUI RUNS=$RUNS" >&2 BENCH_PATTERN="$(IFS='|'; echo "${COMMANDS[*]}")" for FIXTURE in "${FIXTURES[@]}"; do FIXTURE_NAME="$(basename "$FIXTURE" .txt)" # One values-array per command, named VALUES_ to stay in lockstep with COMMANDS - bash # has no associative-array-of-arrays, so this is the simplest portable equivalent. for i in "${!COMMANDS[@]}"; do declare -a "VALUES_${i}=()"; done for (( run = 1; run <= RUNS; run++ )); do RAW_OUTPUT="$(QT_QPA_PLATFORM=offscreen "$SOCNETV_GUI" --interactive-script "$FIXTURE" 2>&1)" BENCH_LINES="$(echo "$RAW_OUTPUT" | grep -E "^BENCH (${BENCH_PATTERN}) " || true)" if [[ -z "$BENCH_LINES" ]]; then echo "ERROR: no BENCH lines produced on run ${run}/${RUNS} for ${FIXTURE_NAME}. Full output:" >&2 echo "$RAW_OUTPUT" >&2 exit 1 fi echo "--- ${FIXTURE_NAME} run ${run}/${RUNS} ---" >&2 echo "$BENCH_LINES" >&2 for i in "${!COMMANDS[@]}"; do CMD="${COMMANDS[$i]}" # qInfo()'s stream operator inserts a space after "elapsed_ms=" between the two << operands. MS="$(echo "$BENCH_LINES" | grep "^BENCH ${CMD} " | sed -nE 's/.*elapsed_ms= *(-?[0-9]+).*/\1/p')" if [[ -z "$MS" ]]; then # Not every fixture runs every command (e.g. report-triad-census is O(n^3) and only # in the small fixture - impractically slow at the large fixture's N=2000) - skip # rather than fail, so one shared COMMANDS list still works across divergent fixtures. continue fi declare -n VALUES_REF="VALUES_${i}" VALUES_REF+=("$MS") done done for i in "${!COMMANDS[@]}"; do CMD="${COMMANDS[$i]}" declare -n VALUES_REF="VALUES_${i}" if [[ ${#VALUES_REF[@]} -eq 0 ]]; then continue fi MEDIAN="$(median_of "${VALUES_REF[@]}")" echo "MEDIAN ${FIXTURE_NAME} ${CMD} (HTML)=${MEDIAN}ms (of ${RUNS} runs)" done for i in "${!COMMANDS[@]}"; do unset "VALUES_${i}"; done done SocNetV-3.7/scripts/travis_before.sh000066400000000000000000000044051524402210300175550ustar00rootroot00000000000000#!/bin/bash echo "*********************************************************************" echo "* STAGE 'before_install': Adding extra repos and updating platform *" echo "*********************************************************************" # Check current directory project_dir=$(pwd) echo "Project dir is: ${project_dir}" if [ "${TRAVIS_OS_NAME}" == "linux" ]; then # # OPTIONAL: Install repos for linux (Qt6, openSSL etc) # echo "Installing third-party Qt6 repo (to allow using focal as linuxdeployqt wants https://github.com/probonopd/linuxdeployqt/issues/377)..." sudo apt-get -qq update sudo add-apt-repository -y ppa:okirby/qt6-backports sudo apt-get update -qq #echo "Downloading openSSL 1.1.1k sources..." #wget --no-verbose "https://github.com/openssl/openssl/archive/refs/tags/OpenSSL_1_1_1k.tar.gz" #echo "Unzipping openSSL sources..." #tar zxfv OpenSSL_1_1_1k.tar.gz #echo "Entering openSSL sources dir..." #cd openssl-OpenSSL_1_1_1k/ #echo "Entering openSSL sources dir..." #echo "we are going to build openssl 1.1.1k from source using following setup:" #echo "# ./config shared --prefix=/opt/openssl-1.1.1/ && make --jobs=\`nproc --all\` && sudo make install" #./config shared --prefix=/opt/openssl-1.1.1/ && make --jobs=`nproc --all` && sudo make install #echo "Exiting openSSL sources dir..." #cd .. #echo "Removing openSSL sources dir..." #rm -rf openssl-OpenSSL_1_1_1k/ #echo "Verifying openSSL installed in /opt/openssl-1.1.1/..." #find /opt/openssl-1.1.1/ #echo "addin openssl libraries to build env" #echo "# export LD_LIBRARY_PATH=\"/opt/openssl-1.1.1/lib/:\$LD_LIBRARY_PATH\"" #export LD_LIBRARY_PATH="/opt/openssl-1.1.1/lib/:$LD_LIBRARY_PATH" elif [ "${TRAVIS_OS_NAME}" == "osx" ]; then # # Update brew, we will use it later to install Qt6 # echo "NOT Untapping core and cask as per homebrew instructions..." # brew untap homebrew/core # brew untap homebrew/cask # echo "Updating brew..." # brew update # echo "Finished updating brew." # echo "Reinstall wget (to avoid errors if wget is not present)" # brew reinstall wget else exit 1 fi echo "" echo "travis_before.sh: DONE. Returning now to main script." echo "" exit 0 SocNetV-3.7/scripts/travis_install_deps.sh000066400000000000000000000057501524402210300210000ustar00rootroot00000000000000#!/bin/bash echo "********************************************************" echo "* STAGE 'install': Installing dependencies (Qt6, etc) *" echo "********************************************************" # Check current directory project_dir=$(pwd) echo "Project dir is: ${project_dir}" if [ "${TRAVIS_OS_NAME}" == "linux" ]; then # # Install required development packages # echo "Installing required linux development packages..." sudo apt-get install mesa-common-dev sudo apt-get install build-essential libgl1-mesa-dev # # Install Qt6 packages and configure host environment # echo "Installing Qt6 packages..." # You need to change this if you need to update to a more recent Qt version sudo apt-get -y install qt6-base-dev qt6-base-dev-tools qt6-tools-dev libqt6charts6-dev libqt6svg6-dev libqt6core5compat6-dev libqt6opengl6-dev sudo apt install libfuse2 # otherwise Appimage will not start in Ubuntu 22.04 -- https://github.com/OpenShot/openshot-qt/issues/4789 echo "" echo "Is there a script to set it up in the host system??..." ls /opt/ #source /opt/qt512/bin/qt512-env.sh echo "Check qtchooser -l" qtchooser -l echo "In Ubuntu 22.04 there is a qtchooser bug (see 'QtChooser doesnt support qt6') " echo "so qtchooser -l does not list any qt6 option." echo "So, we must manually select Qt6 for current user only." echo "First, we generate qt6.conf based on the path to qmake6..." qtchooser -install qt6 $(which qmake6) echo "Selecting Qt6 as default with export QT_SELECT=qt6 (also placing it in ~/.bashrc for persistence):" export QT_SELECT=qt6 echo 'export QT_SELECT=qt6' >> ~/.bashrc echo "cat ~/.bashrc to check..." cat ~/.bashrc echo "Finished installing and configuring Qt6 packages..." elif [ "${TRAVIS_OS_NAME}" == "osx" ]; then # # Install Qt for macOS via brew and configure host environment # # echo "Installing Qt6 for macOS via brew..." # brew install qt # echo "installing p7zip" # brew install p7zip # echo "installing create-dmg" # brew install create-dmg brew install --no-quarantine qt cmake ninja brotli c-ares icu4c@76 pkgconf libnghttp2 autoconf automake libtool ca-certificates mpdecimal openssl@3 p7zip create-dmg # brew install --no-quarantine qt cmake ninja brotli vulkan-headers vulkan-loader ## Install npm appdmg if you want to create custom dmg files with it ## npm install -g appdmg # echo "Running brew link to symlink various Qt binaries into /usr/local/bin etc so..." # brew link --force qt@6 # Add Qt binaries to path echo "Adding qt binaries installation path to system PATH..." # PATH=/usr/local/opt/qt/bin/:${PATH} # Ensure Homebrew Qt is accessible in PATH echo 'export PATH="$(brew --prefix qt)/bin:$PATH"' >> ~/.bash_profile; source ~/.bash_profile; else exit 1 fi echo "" echo "travis_install_deps.sh: DONE installing dependencies. Returning now to main script." echo "" exit 0 SocNetV-3.7/scripts/travis_make_build_linux.sh000066400000000000000000000106671524402210300216350ustar00rootroot00000000000000#!/bin/bash set -e # Exit on any error set -o pipefail # Ensure errors in pipelines are caught echo "************************************************" echo "* STAGE 'script': Building SocNetV for Linux *" echo "************************************************" # Check current directory project_dir=$(pwd) echo "Project dir is: ${project_dir}" echo "TRAVIS_OS_NAME = $TRAVIS_OS_NAME" echo "TRAVIS_TAG = $TRAVIS_TAG" echo "TRAVIS_COMMIT = $TRAVIS_COMMIT" echo "SOCNETV_VERSION = $SOCNETV_VERSION" echo "TAG_NAME = ${TAG_NAME}" LAST_COMMIT_SHORT=$(git rev-parse --short HEAD) echo "LAST_COMMIT_SHORT = $LAST_COMMIT_SHORT" echo "" # # NOTE: # # linuxdeployqt always uses the output of 'git rev-parse --short HEAD' as the version. # We change this by exporting $VERSION environment variable # echo "Checking TRAVIS_TAG to fix the VERSION..." echo "If this is a tag, then version will be the tag, i.e. 3.1 or 3.1-dev" echo "If this is not a tag, the version will include the LAST_COMMIT_SHORT, i.e. 3.1-beta-a0be9cd" if [ ! -z "$TRAVIS_TAG" ]; then export VERSION=${TRAVIS_TAG} # Use tag if available else export VERSION=${SOCNETV_VERSION}-${LAST_COMMIT_SHORT} # Use commit short hash if no tag fi echo "exported VERSION = ${VERSION}"; if [ "${TRAVIS_OS_NAME}" == "linux" ]; then echo "Building SocNetV AppImage for Linux..." # System Information lsb_release -a echo "OpenSSL version: $(openssl version)" echo "Checking qmake version..." which qmake6 qmake6 -v # Running qmake and build with make echo "Running qmake6 to configure project..." qmake6 # default configuration for all targets echo "Building with 'make -j$(nproc)' (using all available cores)..." make -j$(nproc) # Output build results echo "Build finished! Files in current directory:" find . # Install to appdir echo "Attempting make install in appdir..." make INSTALL_ROOT=appdir install echo "SocNetV files installed in appdir:" find appdir/ # Verify dependencies echo "Checking SocNetV executable libraries:" ldd appdir/usr/bin/socnetv # Copy application assets echo "Copying .desktop and icon files..." cp appdir/usr/share/applications/socnetv.desktop ./appdir cp appdir/usr/share/pixmaps/socnetv.png . #echo "copying custom openssl libs to ./appdir/usr/bin..." #cp /opt/openssl-1.1.1/lib/libssl.so.1.1 ./appdir/usr/bin/ #cp /opt/openssl-1.1.1/lib/libcrypto.so.1.1 ./appdir/usr/bin/ # Check contents of appdir echo "SocNetV files installed in appdir -- final:" find appdir/ # # Check Qt plugins # echo "Checking Qt plugins directory..." # find /opt/ | grep "/plugins" | grep qt # echo "Checking /opt directory for Qt:" # find /opt | grep qt # Download and run linuxdeployqt tool echo "Downloading linuxdeployqt tool: " wget --no-verbose "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" # echo "Downloading appimagetool tool (in GO): " # wget -c https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous -O - | grep "appimagetool-.*-x86_64.AppImage" | head -n 1 | cut -d '"' -f 2) # Make linuxdeployqt executable echo "Making linuxdeployqt executable..." chmod a+x linuxdeployqt*.AppImage unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH # Run linuxdeployqt to bundle dependencies echo "Running linuxdeployqt to create AppImage..." ./linuxdeployqt*.AppImage appdir/usr/share/applications/*.desktop -appimage -extra-plugins=iconengines,imageformats -qmake=/usr/bin/qmake6 # echo "Run the appimagetool: " # ./appimagetool-*.AppImage -s deploy appdir/usr/share/applications/*.desktop -appimage -extra-plugins=iconengines,imageformats # Bundle EVERYTHING # or # ./appimagetool-*.AppImage deploy appdir/usr/share/applications/*.desktop # Bundle everything expect what comes with the base system # and # VERSION=1.0 ./appimagetool-*.AppImage ./Some.AppDir # turn AppDir into AppImage # Clean up echo "Cleaning up linuxdeployqt tool..." rm linuxdeployqt-continuous-x86_64.AppImage echo "" echo "Check whether the SocNetV AppImage has been created..." find . -type f -name "*AppImage" else echo "Error: This script should be running on Linux, but detected TRAVIS_OS_NAME=${TRAVIS_OS_NAME}." exit 1 fi echo "Done!" # always return zero exit 0 SocNetV-3.7/scripts/travis_make_build_macos.sh000066400000000000000000000111031524402210300215620ustar00rootroot00000000000000#!/bin/bash set -e # Exit on any error set -o pipefail # Ensure errors in pipelines are caught echo "************************************************" echo "* STAGE 'script': Building SocNetV for macOS *" echo "************************************************" # Constants APP_NAME="SocNetV" project_dir=$(pwd) LAST_COMMIT_SHORT=$(git rev-parse --short HEAD) # linuxdeployqt always uses the output of 'git rev-parse --short HEAD' as the version. # We change this as follows: # If this is a tag, then version will be the tag, i.e. 2.5 or 2.5-beta # If this is not a tag, the we want version to be like "2.5-beta-a0be9cd" VERSION=${TRAVIS_TAG:-"${SOCNETV_VERSION}-${LAST_COMMIT_SHORT}"} # Print basic vars echo "PATH: $PATH" echo "TRAVIS_OS_NAME = $TRAVIS_OS_NAME" echo "TRAVIS_TAG = $TRAVIS_TAG" echo "TRAVIS_COMMIT = $TRAVIS_COMMIT" echo "SOCNETV_VERSION = $SOCNETV_VERSION" echo "TAG_NAME = ${TAG_NAME}" echo "Project dir: ${project_dir}" echo "Building version: ${VERSION}" # Ensure Qt is installed and in PATH export PATH="$(brew --prefix qt)/bin:$PATH" if ! command -v qmake &> /dev/null; then echo "Error: qmake not found. Ensure Qt is installed via Homebrew." exit 1 fi echo "qmake version:" qmake -version # Print system info echo "macOS version: $(sw_vers -productVersion)" echo "Xcode SDK Path: $(xcrun -sdk macosx --show-sdk-path)" # Print build env echo "Xcode build version = " xcrun -sdk macosx --show-sdk-path # Build the app echo "*****************************" echo "Building ${APP_NAME} ${VERSION}..." echo "*****************************" echo "Entering project dir:" cd ${project_dir} echo "Running qmake to configure it as release..." qmake QMAKE_APPLE_DEVICE_ARCHS="x86_64 arm64" -config release || { echo "Error: qmake failed." exit 1 } echo "Running make to compile the source code..." make -j$(sysctl -n hw.ncpu) || { echo "Error: make failed." exit 1 } echo "Build complete. Verifying contents..." find . -type f -name "${APP_NAME}*" # Package the app echo "*****************************" echo "Packaging ${APP_NAME} ${VERSION}..." echo "*****************************" echo "Entering project dir ${project_dir} ..." cd ${project_dir}/ # Remove build directories that we don't want to deploy echo "Removing items we do not deploy from project dir ${project_dir}..." rm -rf moc obj qrc # echo "Contents of ${APP_NAME}.app:" # find ${APP_NAME}.app -type f echo "Calling macdeployqt to create dmg archive from ${APP_NAME}.app:" macdeployqt "${APP_NAME}.app" -dmg || { echo "Error: macdeployqt failed." exit 1 } # Ensure universal binaries (x86_64 and arm64) are consistently included, using lipo: lipo -info ${APP_NAME}.app/Contents/MacOS/${APP_NAME} # echo "Finished macdeployqt -- ${APP_NAME}.app now has these files inside:" # find ${APP_NAME}.app -type f # Rename and package DMG_FILE="${APP_NAME}-${VERSION}.dmg" echo "Rename dmg archive to ${DMG_FILE} ..." if [[ -f ${APP_NAME}.dmg ]]; then mv "${APP_NAME}.dmg" "${DMG_FILE}" else echo "Error: DMG creation failed. No DMG file found." exit 1 fi echo "Verifying DMG contents:" hdiutil attach ./${APP_NAME}-${VERSION}.dmg -nobrowse || exit 1 # Create the zipped archive 7z a "${APP_NAME}-${VERSION}-macos.zip" "${DMG_FILE}" README.md COPYING || { echo "Error: Failed to create zip archive." exit 1 } # Remove the first DMG file [[ -f ${APP_NAME}-${VERSION}.dmg ]] && rm ${APP_NAME}-${VERSION}.dmg if ! command -v create-dmg &> /dev/null; then echo "create-dmg not found. Skipping alternate DMG creation step." else echo "Using create-dmg for additional DMG creation..." echo "Creating release subdirectory..." mkdir -p release echo "Moving app and extra files inside the release subdirectory..." mv ${APP_NAME}.app release/ cp README.md release/ cp LICENSE release/ # --background $project_dir/packaging/macosx/DMG-Background.png \ create-dmg \ --volname ${APP_NAME}-${VERSION} \ --volicon $project_dir/src/images/socnetv.icns \ --window-pos 200 120 \ --window-size 800 400 \ --icon-size 100 \ --icon "${APP_NAME}" 200 205 \ --hide-extension "${APP_NAME}.app" \ --app-drop-link 600 205 \ ./${APP_NAME}-${VERSION}.dmg \ ./release echo "Verifying DMG contents:" hdiutil attach ./${APP_NAME}-${VERSION}.dmg -nobrowse || exit 1 fi echo "Release files:" find . -type f -name "${APP_NAME}*" echo "************************************************" echo "* Build and packaging completed successfully. *" echo "************************************************" exit 0SocNetV-3.7/scripts/travis_upload_packages.sh000066400000000000000000000024331524402210300214340ustar00rootroot00000000000000#!/bin/bash echo "************************************************" echo "* STAGE 'after_success': Uploading packages *" echo "************************************************" # Check current directory project_dir=$(pwd) echo "Project dir is: ${project_dir}" echo "TRAVIS_OS_NAME: $TRAVIS_OS_NAME"; if [ ! -z "${TRAVIS_TAG}" ]; then echo "TRAVIS_TAG: $TRAVIS_TAG"; echo "The upload tool will upload files to tag TRAVIS_TAG"; elif [ -z "$TRAVIS_TAG" ] ; then echo "TRAVIS_TAG is empty."; echo "The upload tool will upload files to 'continuous' CI/CD pipeline"; fi echo " Downloading upload.sh tool. Please wait..."; wget --no-verbose https://github.com/probonopd/uploadtool/raw/master/upload.sh if [ "${TRAVIS_OS_NAME}" == "linux" ]; then echo "Listing any *SocNetV*.AppImage* package files for Linux..."; ls -lsh *SocNetV*.AppImage* echo "Using upload.sh tool to upload binaries to GitHub..." bash upload.sh *SocNetV*.AppImage* elif [ "${TRAVIS_OS_NAME}" == "osx" ]; then echo "Listing any *SocNetV*.* package files for macOS..."; ls -lsh *SocNetV*.* bash upload.sh *SocNetV*.zip* bash upload.sh SocNetV*.dmg*; else exit 1 fi echo "" echo "travis_upload_packages.sh: DONE uploading our packages. Returning now to main script." echo "" exit 0 SocNetV-3.7/scripts/update_translations.sh000077500000000000000000000132621524402210300210120ustar00rootroot00000000000000#!/usr/bin/env bash # ============================================================================= # update_translations.sh β€” SocNetV Translation Update Helper # ============================================================================= # Location: app/scripts/update_translations.sh # # Usage: # ./update_translations.sh [OPTIONS] # # Options: # --src Source directory to scan (default: ../src) # --ts Specific .ts file to update (repeatable) # --no-obsolete Remove obsolete entries (strings no longer in source) # -h, --help Show this help # # Examples: # ./update_translations.sh # ./update_translations.sh --no-obsolete # ./update_translations.sh --src ../src/ --ts ../translations/socnetv_de.ts # # What it does: # 1. Finds lupdate in PATH or Qt installation (macOS/Linux) # 2. Ensures every .ts file has a proper language= attribute in its header # (lupdate silently skips files without one) # 3. Runs lupdate to pull in new/changed strings from source # 4. Reports next steps # ============================================================================= set -euo pipefail # --- Resolve app root relative to this script -------------------------------- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" APP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" # --- Defaults ---------------------------------------------------------------- SRC_DIR="$APP_DIR/src" TRANSLATIONS_DIR="$APP_DIR/translations" TS_FILES=() NO_OBSOLETE=false # --- Argument parsing -------------------------------------------------------- while [[ $# -gt 0 ]]; do case "$1" in --src) SRC_DIR="$2"; shift 2 ;; --ts) TS_FILES+=("$2"); shift 2 ;; --no-obsolete) NO_OBSOLETE=true; shift ;; -h|--help) grep '^#' "$0" | grep -v '#!/' | sed 's/^# \{0,1\}//' exit 0 ;; *) echo "ERROR: Unknown option: $1"; exit 1 ;; esac done # --- Sanity checks ----------------------------------------------------------- if [[ ! -d "$SRC_DIR" ]]; then echo "ERROR: Source directory not found: $SRC_DIR" echo " Override with: --src " exit 1 fi if [[ ! -d "$TRANSLATIONS_DIR" ]]; then echo "ERROR: Translations directory not found: $TRANSLATIONS_DIR" exit 1 fi # --- Locate lupdate ---------------------------------------------------------- find_lupdate() { if command -v lupdate &>/dev/null; then echo "lupdate"; return fi if [[ "$OSTYPE" == "darwin"* ]]; then local found found=$(find "$HOME/Qt" -type f -name "lupdate" 2>/dev/null | sort -Vr | head -1) [[ -n "$found" ]] && echo "$found" && return fi for dir in /usr/lib/qt6/bin \ /usr/lib/x86_64-linux-gnu/qt6/bin \ /opt/Qt/*/gcc_64/bin \ /usr/local/Qt/*/gcc_64/bin; do [[ -x "$dir/lupdate" ]] && echo "$dir/lupdate" && return done echo "" } LUPDATE=$(find_lupdate) if [[ -z "$LUPDATE" ]]; then echo "ERROR: lupdate not found." echo " macOS: install Qt via https://www.qt.io/download" echo " Linux: sudo apt install qt6-tools-dev-tools # or distro equivalent" exit 1 fi echo "lupdate : $LUPDATE" echo "version : $("$LUPDATE" -version 2>&1 | head -1)" echo "app dir : $APP_DIR" echo "sources : $SRC_DIR" echo "no-obsolete : $NO_OBSOLETE" echo # --- Auto-detect .ts files if none given ------------------------------------- if [[ ${#TS_FILES[@]} -eq 0 ]]; then while IFS= read -r f; do TS_FILES+=("$f") done < <(find "$TRANSLATIONS_DIR" -name "*.ts" | sort) fi if [[ ${#TS_FILES[@]} -eq 0 ]]; then echo "ERROR: No .ts files found in $TRANSLATIONS_DIR" echo " Use --ts to specify manually." exit 1 fi echo "Translation files:" printf ' %s\n' "${TS_FILES[@]}" echo # --- Fix missing language= attribute ----------------------------------------- # lupdate silently skips .ts files whose element has no language= attr. # We derive the code from the filename: socnetv_de.ts -> "de" fix_language_attribute() { local tsfile="$1" local lang_code lang_code=$(basename "$tsfile" .ts | sed 's/.*_//') if grep -q ']*language=' "$tsfile"; then echo " [ok] $(basename "$tsfile") (language=\"$lang_code\")" return fi echo " [fixing] $(basename "$tsfile") β€” adding language=\"$lang_code\"" if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s|||" "$tsfile" else sed -i "s|||" "$tsfile" fi } echo "Checking language attributes..." for ts in "${TS_FILES[@]}"; do [[ -f "$ts" ]] || { echo " [warn] $ts β€” not found, skipping"; continue; } fix_language_attribute "$ts" done echo # --- Build lupdate command --------------------------------------------------- LUPDATE_ARGS=("$SRC_DIR" -ts "${TS_FILES[@]}") if [[ "$NO_OBSOLETE" == true ]]; then LUPDATE_ARGS+=("-no-obsolete") fi # --- Run lupdate ------------------------------------------------------------- echo "Running lupdate..." echo "------------------------------------------------------" "$LUPDATE" "${LUPDATE_ARGS[@]}" echo "------------------------------------------------------" echo # --- Next steps -------------------------------------------------------------- LRELEASE="${LUPDATE/lupdate/lrelease}" echo "Done. Next steps:" echo " 1. Translate new/unfinished strings in Qt Linguist:" echo " linguist ${TS_FILES[*]}" echo echo " 2. Compile to .qm (or just rebuild via CMake):" if [[ -x "$LRELEASE" ]]; then echo " $LRELEASE ${TS_FILES[*]}" else echo " lrelease ${TS_FILES[*]}" fiSocNetV-3.7/socnetv.appdata.xml000066400000000000000000000044601524402210300165150ustar00rootroot00000000000000 org.socnetv.social_network_visualizer CC0-1.0 GPL-2.0+ Social Network Visualizer Social Network Analysis and Visualization software application

Social Network Visualizer (SocNetV) is a cross-platform, user-friendly free software tool for social network analysis and visualization.

With SocNetV you can draw social networks with a few clicks on a virtual canvas, load field data from a file in a supported format (GraphML, GraphViz, Adjacency, EdgeList, GML, Pajek, UCINET, etc) or crawl the internet to create a social network of connected webpages.

You can edit actors and ties through point-and-click, analyse graph and social network properties, produce beautiful HTML reports and embed visualization layouts to the network.

socnetv.desktop https://socnetv.org https://github.com/socnetv/app/issues https://socnetv.org/data/uploads/screenshots/30/socnetv-3.0.2.png Dimitris B. Kalamaras dimitris.kalamaras_AT_gmail.com socnetv
SocNetV-3.7/socnetv.desktop000066400000000000000000000004631524402210300157540ustar00rootroot00000000000000[Desktop Entry] Name=SocNetV X-SuSE-translate=true GenericName=Social Network Visualizer Comment=Social Network Analysis and Visualization software Exec=socnetv Terminal=false Type=Application Icon=socnetv Categories=Education;Science;Math;Qt; Keywords=Network Analysis,Social Network Analysis,Visualizer; SocNetV-3.7/socnetv.pro000077500000000000000000000273701524402210300151140ustar00rootroot00000000000000lessThan(QT_VERSION, 6.0) { error("SocNetV requires at least Qt 6.0!") } MY_TARGET_BUILD = release #MY_TARGET_BUILD = debug #ALLOW_WARNINGS = warn_on ALLOW_WARNINGS = warn_off TEMPLATE = app CONFIG += qt thread $${ALLOW_WARNINGS} $${MY_TARGET_BUILD} CONFIG += c++17 TARGET = socnetv VERSION=3.7 LANGUAGE = C++ # add Qt module support QT += core QT += gui QT += xml QT += network QT += widgets QT += openglwidgets QT += printsupport QT += charts QT += svg QT += core5compat QT += concurrent INCLUDEPATH += ./src FORMS += src/forms/dialogfilteredgesbyweight.ui \ src/forms/dialogfilternodesbycentrality.ui \ src/forms/dialogsettings.ui \ src/forms/dialogsysteminfo.ui \ src/forms/dialogwebcrawler.ui \ src/forms/dialogdatasetselect.ui \ src/forms/dialograndsmallworld.ui \ src/forms/dialograndscalefree.ui \ src/forms/dialogranderdosrenyi.ui \ src/forms/dialograndregular.ui \ src/forms/dialograndlattice.ui \ src/forms/dialogsimilaritypearson.ui \ src/forms/dialogsimilaritymatches.ui \ src/forms/dialogdissimilarities.ui \ src/forms/dialogclusteringhierarchical.ui \ src/forms/dialognodeedit.ui \ src/forms/dialognodefind.ui \ src/forms/dialogedgedichotomization.ui \ src/forms/dialogedgeedit.ui \ src/forms/dialogfilterbyattribute.ui \ src/forms/dialogexportpdf.ui \ src/forms/dialogexportimage.ui \ src/forms/dialogbulkedit.ui \ src/forms/dialogcentralitykatz.ui \ src/forms/dialogcentralitybonacich.ui HEADERS += src/mainwindow.h \ src/texteditor.h \ src/graph.h \ src/engine/distance_engine.h \ src/engine/distance_progress_sink.h \ src/engine/graph_distance_progress_sink.h \ src/engine/null_distance_progress_sink.h \ src/engine/per_source_scratch.h \ src/engine/thread_local_state.h \ src/graphvertex.h \ src/matrix.h \ src/parser.h \ src/webcrawler.h \ src/chart.h \ src/graphicswidget.h \ src/graphicsedge.h \ src/graphicsedgeweight.h \ src/graphicsedgelabel.h \ src/graphicsguide.h \ src/graphicsnode.h \ src/graphicsnodelabel.h \ src/graphicsnodenumber.h \ src/widgets/filterbarwidget.h \ src/widgets/graphtablewidget.h \ src/widgets/nodetablemodel.h \ src/widgets/edgetablemodel.h \ src/forms/dialogedgeedit.h \ src/forms/dialogfilterbyattribute.h \ src/forms/dialogquerybuilder.h \ src/graph/filters/graph_query.h \ src/graph/filters/filter_condition.h \ src/graph/filters/filter_spec.h \ src/graph/io/graph_parse_sink.h \ src/graph/io/graph_parse_sink_graph.h \ src/graph/io/table_export.h \ src/graph/io/table_import.h \ src/forms/dialogimportattributes.h \ src/forms/dialogfilteredgesbyweight.h \ src/forms/dialogfilternodesbycentrality.h \ src/forms/dialogedgedichotomization.h \ src/forms/dialogwebcrawler.h \ src/forms/dialogdatasetselect.h \ src/forms/dialogpreviewfile.h \ src/forms/dialognodeedit.h \ src/forms/dialogranderdosrenyi.h \ src/forms/dialograndsmallworld.h \ src/forms/dialograndscalefree.h \ src/forms/dialograndregular.h \ src/forms/dialogsettings.h \ src/forms/dialogsimilaritypearson.h \ src/forms/dialogsimilaritymatches.h \ src/forms/dialogdissimilarities.h \ src/forms/dialogclusteringhierarchical.h \ src/forms/dialograndlattice.h \ src/forms/dialognodefind.h \ src/forms/dialogexportpdf.h \ src/forms/dialogexportimage.h \ src/forms/dialogsysteminfo.h \ src/forms/dialogbulkedit.h \ src/forms/dialogcentralitykatz.h \ src/forms/dialogcentralitybonacich.h \ src/global.h SOURCES += src/main.cpp \ src/mainwindow.cpp \ src/mainwindow/lifecycle/mainwindow_lifecycle.cpp \ src/mainwindow/init/mainwindow_init_settings.cpp \ src/mainwindow/init/mainwindow_init_actions.cpp \ src/mainwindow/init/mainwindow_init_menu.cpp \ src/mainwindow/init/mainwindow_init_panels.cpp \ src/mainwindow/init/mainwindow_init_app.cpp \ src/mainwindow/network/mainwindow_network_file.cpp \ src/mainwindow/network/mainwindow_network_import_export.cpp \ src/mainwindow/network/mainwindow_network_random.cpp \ src/mainwindow/network/mainwindow_network_web.cpp \ src/mainwindow/network/mainwindow_network_view.cpp \ src/mainwindow/edit/mainwindow_edit_node.cpp \ src/mainwindow/edit/mainwindow_edit_edge.cpp \ src/mainwindow/edit/mainwindow_edit_selection.cpp \ src/mainwindow/edit/mainwindow_edit_relation.cpp \ src/mainwindow/edit/mainwindow_edit_filters.cpp \ src/mainwindow/analyze/mainwindow_analyze_centrality.cpp \ src/mainwindow/analyze/mainwindow_analyze_distance.cpp \ src/mainwindow/analyze/mainwindow_analyze_matrices.cpp \ src/mainwindow/analyze/mainwindow_analyze_cohesion.cpp \ src/mainwindow/analyze/mainwindow_analyze_clustering.cpp \ src/mainwindow/analyze/mainwindow_analyze_similarity.cpp \ src/mainwindow/analyze/mainwindow_analyze_prominence.cpp \ src/mainwindow/analyze/mainwindow_analyze_shared.cpp \ src/mainwindow/dispatch/mainwindow_dispatch.cpp \ src/mainwindow/scripting/mainwindow_interactive_script.cpp \ src/mainwindow/layout/mainwindow_layout.cpp \ src/mainwindow/options/mainwindow_options.cpp \ src/mainwindow/help/mainwindow_help.cpp \ src/mainwindow/edit/mainwindow_edit_shared.cpp \ src/texteditor.cpp \ src/engine/graph_distance_progress_sink.cpp \ src/engine/distance_engine.cpp \ src/graph.cpp \ src/graph/util/graph_type_strings.cpp \ src/graph/core/graph_structure_metrics.cpp \ src/graph/core/graph_state_flags.cpp \ src/graph/core/graph_metadata.cpp \ src/graph/storage/graph_vertices.cpp \ src/graph/storage/graph_edges.cpp \ src/graph/storage/graph_subgraphs.cpp \ src/graph/io/graph_io.cpp \ src/graph/io/graph_parse_sink_graph.cpp \ src/graph/io/table_export.cpp \ src/graph/io/table_import.cpp \ src/graph/relations/graph_relations.cpp \ src/graph/filters/graph_edge_filters.cpp \ src/graph/filters/graph_node_filters.cpp \ src/graph/filters/graph_query_filters.cpp \ src/graph/ui/graph_ui_facade.cpp \ src/graph/ui/graph_ui_prominence_distribution.cpp \ src/graph/ui/graph_canvas.cpp \ src/graph/ui/graph_selection.cpp \ src/graph/ui/graph_vertex_style.cpp \ src/graph/ui/graph_edge_style.cpp \ src/graph/distances/graph_distance_facade.cpp \ src/graph/distances/graph_distance_cache.cpp \ src/graph/centrality/graph_centrality.cpp \ src/graph/centrality/graph_centrality_katz.cpp \ src/graph/centrality/graph_centrality_bonacich.cpp \ src/graph/centrality/graph_prestige.cpp \ src/graph/prominence/graph_prominence_distribution.cpp \ src/graph/matrices/graph_matrix_adjacency.cpp \ src/graph/matrices/graph_matrix_distances.cpp \ src/graph/matrices/graph_matrix_reachability.cpp \ src/graph/generators/graph_random_networks.cpp \ src/graph/crawler/graph_crawler.cpp \ src/graph/layouts/graph_layouts_basic.cpp \ src/graph/layouts/graph_layouts_force.cpp \ src/graph/reachability/graph_reachability_walks.cpp \ src/graph/cohesion/graph_cliques.cpp \ src/graph/cohesion/graph_connectivity.cpp \ src/graph/clustering/graph_triad_census.cpp \ src/graph/clustering/graph_clustering_coefficients.cpp \ src/graph/clustering/graph_clustering_hierarchical.cpp \ src/graph/similarity/graph_similarity_matrices.cpp \ src/graph/reporting/graph_reports.cpp \ src/graph/reporting/graph_reports_settings.cpp \ src/graphvertex.cpp \ src/matrix.cpp \ src/parser.cpp \ src/parser/parser_common.cpp \ src/parser/parser_graphml.cpp \ src/parser/parser_edgelist.cpp \ src/parser/parser_adjacency.cpp \ src/parser/parser_dl.cpp \ src/parser/parser_dot.cpp \ src/parser/parser_gml.cpp \ src/parser/parser_pajek.cpp \ src/webcrawler.cpp \ src/chart.cpp \ src/graphicswidget.cpp \ src/graphicsedge.cpp \ src/graphicsedgeweight.cpp \ src/graphicsedgelabel.cpp \ src/graphicsguide.cpp \ src/graphicsnode.cpp \ src/graphicsnodelabel.cpp \ src/graphicsnodenumber.cpp \ src/widgets/filterbarwidget.cpp \ src/widgets/graphtablewidget.cpp \ src/widgets/nodetablemodel.cpp \ src/widgets/edgetablemodel.cpp \ src/forms/dialogedgeedit.cpp \ src/forms/dialogfilterbyattribute.cpp \ src/forms/dialogquerybuilder.cpp \ src/forms/dialogimportattributes.cpp \ src/forms/dialogfilteredgesbyweight.cpp \ src/forms/dialogfilternodesbycentrality.cpp \ src/forms/dialogedgedichotomization.cpp \ src/forms/dialogwebcrawler.cpp \ src/forms/dialogdatasetselect.cpp \ src/forms/dialogpreviewfile.cpp \ src/forms/dialognodeedit.cpp \ src/forms/dialogranderdosrenyi.cpp \ src/forms/dialograndsmallworld.cpp \ src/forms/dialograndregular.cpp \ src/forms/dialograndscalefree.cpp \ src/forms/dialogsettings.cpp \ src/forms/dialogsimilaritypearson.cpp \ src/forms/dialogsimilaritymatches.cpp \ src/forms/dialogdissimilarities.cpp \ src/forms/dialogclusteringhierarchical.cpp \ src/forms/dialograndlattice.cpp \ src/forms/dialognodefind.cpp \ src/forms/dialogexportpdf.cpp \ src/forms/dialogexportimage.cpp \ src/forms/dialogsysteminfo.cpp \ src/forms/dialogbulkedit.cpp \ src/forms/dialogcentralitykatz.cpp \ src/forms/dialogcentralitybonacich.cpp RESOURCES = src/images.qrc \ src/data.qrc # This is Windows only win32 { VERSION = 3.7.0.1 # major.minor.patch.build VERSION_PE_HEADER = 3.7 # MSVC link.exe option /VERSION:x.y expects two numeric components (major.minor) #RC_FILE = src/icon.rc RC_ICONS = src/images/socnetv.ico # Company name QMAKE_TARGET_COMPANY = "socnetv.org" # Product name QMAKE_TARGET_PRODUCT = "Social Network Visualizer" # Document description QMAKE_TARGET_DESCRIPTION = "SocNetV: Open-source social network analysis application based on Qt." # Copyright Information QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2023 Dimitris B. Kalamaras. GPLv3." TARGET = SocNetV target.path = release/ } win32:msvc { # Fixes msvc compile/linking error "unresolved external symbol WinMain referenced in function ..." # see https://stackoverflow.com/questions/39689162/qt-project-in-visual-studio-2015-unresolved-external-symbol-wwinmain QMAKE_LFLAGS += /ENTRY:mainCRTStartup } # This is Linux/Unix only unix:!macx{ # No matter what PREFIX the user enters when # executing qmake to create the Makefile, # we always set it to be /usr because # it simplifies the .travis CI and .deb creation # The user may still install to a different folder # with the command: # make INSTALL_ROOT= install; PREFIX = /usr QMAKE_STRIP = : target.path = $${PREFIX}/bin TARGET = socnetv pixmap.path = $${PREFIX}/share/pixmaps pixmap.files = src/images/socnetv.png desktop.path = $${PREFIX}/share/applications desktop.files = socnetv.desktop manpage.path = $${PREFIX}/share/man/man1 manpage.files = man/socnetv.1 appstream.path = $${PREFIX}/share/metainfo appstream.files = socnetv.appdata.xml translations.path = $${PREFIX}/share/socnetv translations.files = translations/socnetv_*.qm doc.path = $${PREFIX}/share/doc/socnetv doc.files = CHANGELOG.md README.md TODO COPYING AUTHORS INSTALL INSTALLS += pixmap desktop manpage translations appstream } # This is macOS only macx { ICON = src/images/socnetv.icns TARGET = SocNetV QMAKE_CXXFLAGS = -Wno-unused-variable -Wdeprecated-declarations QMAKE_CXXFLAGS_WARN_ON = -Wall -Wno-unused-parameter -Wdeprecated-declarations # Build app for both x86_64 and arm64, see https://doc.qt.io/qt-6/macos.html#architectures QMAKE_APPLE_DEVICE_ARCHS = x86_64 arm64 } INSTALLS += target TRANSLATIONS = translations/socnetv_es.ts \ translations/socnetv_de.tsSocNetV-3.7/socnetv.spec000066400000000000000000000217161524402210300152410ustar00rootroot00000000000000# # spec file for package socnetv # # Dimitris Kalamaras dimitris.kalamaras@gmail.com # # Refer to the following for more info on .spec file syntax: # # http://www.rpm.org/max-rpm/ # http://www.rpm.org/max-rpm-snapshot/ (Updated version) # https://docs.fedoraproject.org/en-US/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ # https://rpm-packaging-guide.github.io/ # # See also http://www.rpm.org # Name: socnetv Version: 3.7 Release: 1%{?dist} Summary: A Social Networks Analyser and Visualiser License: GPL-3.0-or-later Group: Productivity/Scientific/Math URL: https://socnetv.org/ Source0: app-%{version}.tar.gz BuildRequires: gcc-c++ BuildRequires: gzip BuildRequires: cmake # Remove ninja-build from BuildRequires # cmake will use make by default %if 0%{?suse_version} BuildRequires: qt6-linguist-devel BuildRequires: qt6-qt5compat-devel BuildRequires: qt6-concurrent-devel %endif %if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos_version} BuildRequires: qt6-qttools-devel BuildRequires: glibc-all-langpacks BuildRequires: pkgconfig(Qt6Core5Compat) %endif BuildRequires: desktop-file-utils BuildRequires: pkgconfig(Qt6Core) BuildRequires: pkgconfig(Qt6Gui) BuildRequires: pkgconfig(Qt6PrintSupport) BuildRequires: pkgconfig(Qt6Widgets) BuildRequires: pkgconfig(Qt6Network) BuildRequires: pkgconfig(Qt6Xml) BuildRequires: pkgconfig(Qt6OpenGL) BuildRequires: pkgconfig(Qt6OpenGLWidgets) # qt6-qtsvg-devel BuildRequires: pkgconfig(Qt6Svg) # qt6-qtcharts-devel BuildRequires: pkgconfig(Qt6Charts) %description SocNetV (Social Network Visualizer) is a flexible, user-friendly free software application for social network analysis and visualisation. ### Added to avoid "empty files file debugsourcefiles.list " error ### see https://en.opensuse.org/Fedora_packaging ### Another solution would be to add CONFIG += force_debug_info in qmake %global debug_package %{nil} %prep ### Runn autosetup. The autosetup macro is new, see: https://rpm.org/user_doc/autosetup.html %autosetup -p 0 -n app-%{version} ### Debugging: Show files pwd find . %build cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ -DBUILD_CLI=OFF cmake --build build -- %{?_smp_mflags} %install DESTDIR=%{buildroot} cmake --install build ### Debugging: Show where we are and show files in build root. pwd find %{buildroot} %check desktop-file-validate %{buildroot}%{_datadir}/applications/%{name}.desktop ### Debugging: show where we are again pwd pwd ### Run post install and post uninstall Scriptlets ### Read more: https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/ %post ### Scriptlet executed before the package is installed on the target system. /usr/bin/update-desktop-database &> /dev/null || : %postun ### Scriptlet executed just after the package is uninstalled from the target system. /usr/bin/update-desktop-database &> /dev/null || : ### Debugging: show where we are for a last time pwd pwd pwd ### No license, no doc lines β€” ### the `%{_datadir}/doc/%{name}/` covers everything CMake installs there. %files %defattr(-,root,root) %dir /usr/share/socnetv %{_datadir}/doc/%{name}/ %{_bindir}/%{name} %{_datadir}/%{name}/%{name}_*.qm %{_datadir}/applications/%{name}.desktop %{_datadir}/pixmaps/%{name}.png %{_datadir}/metainfo/%{name}.appdata.xml %{_mandir}/man1/%{name}.1.gz ### ### CHANGELOG SECTION ### %changelog * Thu Aug 27 2026 Dimitris Kalamaras - 3.7-1 - Upstream v3.7 * Mon Jun 15 2026 Dimitris Kalamaras - 3.6-1 - Upstream v3.6 * Fri May 08 2026 Dimitris Kalamaras - 3.5-1 - Upstream v3.5 * Tue Mar 10 2026 Dimitris Kalamaras - 3.4-1 - Upstream v3.4 * Thu Feb 26 2026 Dimitris Kalamaras - 3.3.1-1 - Upstream v3.3.1 * Wed Feb 25 2026 Dimitris Kalamaras - 3.3-1 - Upstream v3.3 * Tue Apr 1 2025 Dimitris Kalamaras - 3.2-1 - Upstream v3.2 * Mon Jun 12 2023 Dimitris Kalamaras - 3.1-1 - Upstream v3.1 * Fri Jul 30 2021 Dimitris Kalamaras - 3.0.4-1 - Upstream v3.0.4 * Fri Jul 30 2021 Dimitris Kalamaras - 3.0.3-1 - Upstream v3.0.3 * Fri Jul 30 2021 Dimitris Kalamaras - 3.0.2-1 - Upstream v3.0.2 * Fri Jul 30 2021 Dimitris Kalamaras - 3.0.1-1 - Upstream v3.0.1 * Fri Jul 30 2021 Dimitris Kalamaras - 3.0-1 - Upstream v3.0 * Mon Jun 14 2021 Dimitris Kalamaras - 2.9-1 - Upstream v2.9 * Sun Jan 03 2021 Dimitris Kalamaras - 2.8-1 - Upstream v2.8 * Mon Dec 28 2020 Dimitris Kalamaras - 2.7-1 - Sync with upstream v2.7 * Mon Dec 28 2020 Dimitris Kalamaras - 2.6-2 - Synced with fixed upstream development 2.6 version * Mon Dec 28 2020 Dimitris Kalamaras - 2.6-1 - Synced with upstream development 2.6 version * Fri Mar 8 2019 Dimitris Kalamaras - 2.5-3 - Synced with new 2.5 version from upstream * Wed Feb 20 2019 Dimitris Kalamaras - 2.5-2 - Synced with new beta2 version from upstream * Tue Feb 19 2019 Dimitris Kalamaras - 2.5-1 - Synced with new beta version from upstream * Wed Feb 28 2018 Dimitris Kalamaras - 2.4-2 - Synced with fixed table version from upstream * Tue Feb 27 2018 Dimitris Kalamaras - 2.4-1 - Synced with new stable version from upstream * Wed Jul 5 2017 Dimitris Kalamaras - 2.3-1 - Synced with new stable version from upstream * Sat Jan 21 2017 Dimitris Kalamaras - 2.2-1 - Synced with new stable version from upstream * Wed Sep 28 2016 Dimitris Kalamaras - 2.1-1 - Synced with new stable version from upstream. * Tue Sep 13 2016 Dimitris Kalamaras - 2.0-2 - Spec patch for Buildservice * Mon Sep 12 2016 Dimitris Kalamaras - 2.0-1 - Synced with new stable version from upstream. * Tue Jun 23 2015 Dimitris Kalamaras - 1.9-1 - Synced with DEV version from upstream. * Fri Jun 05 2015 Dimitris Kalamaras - 1.8-1 - Synced with new stable version from upstream. * Wed May 20 2015 Dimitris Kalamaras - 1.7-1 - Synced with new stable version from upstream. * Mon May 11 2015 Dimitris Kalamaras - 1.6-1 - Synced with new stable version from upstream. * Fri Oct 10 2014 Dimitris Kalamaras - 1.5-1 - Synced with new stable version from upstream. * Mon Sep 01 2014 Dimitris Kalamaras - 1.4-1 - Synced with new stable version from upstream. * Wed Aug 27 2014 Dimitris Kalamaras - 1.3-1 - Synced with new stable version from upstream. * Mon Aug 18 2014 Dimitris Kalamaras - 1.2-1 - Synced with new stable version 1.2 from upstream. * Fri Aug 01 2014 Dimitris Kalamaras - 1.1-1 - Synced with new version from upstream. * Thu Feb 27 2014 Dimitris Kalamaras - 1.0-2 - Fixed spec for openSUSE * Thu Feb 27 2014 Dimitris Kalamaras - 1.0-1 - Synced with new version from upstream. * Thu Oct 14 2010 Dimitris Kalamaras - 0.90-1 - Synced with upstream. * Thu Jan 28 2010 Dimitris Kalamaras - 0.81-1 - Synced with upstream. - Bugfixes for Windows version * Sat Jan 09 2010 Dimitris Kalamaras - 0.80-1 - Synced with upstream, * Mon Jun 29 2009 Dimitris Kalamaras - 0.70-1 - Synced with upstream * Wed May 27 2009 Dimitris Kalamaras - 0.6.0-1 - Synced with upstream * Thu Feb 26 2009 Dimitris Kalamaras - 0.52-1 - Synced with upstream. - Bugfixes into .spec.in for RPMs (Fedora, openSUSE and Mandriva). * Tue Feb 17 2009 Dimitris Kalamaras - 0.51-3 - Bugfixes into .spec.in for Fedora and Mandriva. - RPM for Fedora * Mon Feb 16 2009 Dimitris Kalamaras - 0.51-2 - Minor changes to RPM * Mon Feb 16 2009 Dimitris Kalamaras - 0.51-1 - Updated to upstream version 0.51 * Fri Feb 13 2009 Dimitris Kalamaras - 0.50-1 - Updated to upstream version 0.50 * Wed Jan 14 2009 Dimitris Kalamaras - 0.49-2 - Package .spec fixes * Tue Jan 13 2009 Dimitris Kalamaras - 0.49-1 - Updated to 0.49 * Wed Sep 17 2008 Dimitris Kalamaras - 0.48-1 - First RPM release SocNetV-3.7/src/000077500000000000000000000000001524402210300134645ustar00rootroot00000000000000SocNetV-3.7/src/chart.cpp000066400000000000000000000254761524402210300153070ustar00rootroot00000000000000/** * @file chart.h * @brief Declares the Chart class for generating and displaying statistical charts in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "chart.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(lcChart, "socnetv.chart") Chart::Chart(QWidget *parent) : QChartView (parent ), m_chart(new QChart) { qCDebug(lcChart) << "Constructing a Chart"; setChart(m_chart); m_chart->setAnimationOptions(QChart::SeriesAnimations); } Chart::~Chart(){ qCDebug(lcChart)<< "Deleting m_chart pointer"; delete m_chart; //m_series->clear(); } /** * @brief Adds QAbstractSeries series to chart * If no series are passed, a new QSplineSeries is created with 1 point at (0,0) * * @param series */ void Chart::addSeries(QAbstractSeries *series) { qCDebug(lcChart) << "Adding a series to chart" ; if (series) { m_chart->addSeries(series); qCDebug(lcChart) << "added series with name"<< series->name() ; } else { // default: a trivial series with one point // We need this for resetToTrivial() // to be able to call createDefaultAxes() m_series = new QSplineSeries(); *m_series << QPointF(0,0); m_series->setName("trivial"); m_chart->addSeries(m_series); qCDebug(lcChart) << "trivial series with one point created."; } } /** * @brief Adds the data point p(qreal,qreal) to the series. * @param p */ void Chart::appendToSeries(const QPointF &p) { qCDebug(lcChart) <<"Appending a QPoint to series" ; m_series->append(p); } /** * @brief Removes and deletes all series objects that have been added to the chart. */ void Chart::removeAllSeries() { qCDebug(lcChart) <<"Removing all series... " ; if ( ! m_chart->series().empty() ) { m_chart->removeAllSeries(); qCDebug(lcChart) <<"series count:" << m_chart->series().size(); } } /** * @brief Creates default axes. Must be called AFTER loading a series to the chart */ void Chart::createDefaultAxes(){ qCDebug(lcChart) << "Creating default axes..." ; m_chart->createDefaultAxes(); } QList Chart::axes(Qt::Orientations orientation, QAbstractSeries *series) const { qCDebug(lcChart) << "Chart::axes()" ; if (series == Q_NULLPTR) { qCDebug(lcChart) << "Chart::axes() - no series defined" ; return m_chart->axes(orientation); } else { qCDebug(lcChart) << "Chart::axes() - a series was defined" ; return m_chart->axes(orientation, series); } } /** * @brief Removes all previously attached X,Y axes from the QChart */ void Chart::removeAllAxes(){ qCDebug(lcChart) << "Removing all axes"; if ( ! axes(Qt::Horizontal).isEmpty() ) { qCDebug(lcChart) << "Looping over horizontal axes to delete - count: "<< m_chart->axes(Qt::Horizontal).size(); foreach ( QAbstractAxis *axe, axes(Qt::Horizontal) ) { m_chart->removeAxis(axe); } } if ( ! axes(Qt::Vertical).isEmpty() ) { qCDebug(lcChart) << "Looping over vertical axes to delete - count: "<< m_chart->axes(Qt::Vertical).size(); foreach ( QAbstractAxis *axe, axes(Qt::Vertical) ) { m_chart->removeAxis(axe); } } } /** * @brief Adds the axis axis to the chart and attaches it to the series series * as a bottom-aligned horizontal axis. * The chart takes ownership of both the axis and the series. * @param axis * @param series */ void Chart::setAxisX(QAbstractAxis *axis, QAbstractSeries *series) { qCDebug(lcChart)<<"Adding axis X to chart"; addAxis(axis, Qt::AlignBottom); qCDebug(lcChart)<<"Attaching axis X to current series " << series->name(); series->attachAxis(axis); } /** * @brief Adds the axis axis to the chart and attaches it to the series series * as a left-aligned horizontal axis. * The chart takes ownership of both the axis and the series. * @param axis * @param series */ void Chart::setAxisY(QAbstractAxis *axis, QAbstractSeries *series) { qCDebug(lcChart)<<"Adding axis Y to chart"; addAxis(axis, Qt::AlignLeft); qCDebug(lcChart)<<"Attaching axis Y to current series " << series->name(); series->attachAxis(axis); } /** * @brief Add Axis axis to the QChart. Does not delete previously attached axis * @param axis * @param alignment */ void Chart::addAxis(QAbstractAxis *axis, Qt::Alignment alignment) { qCDebug(lcChart)<< "Adding axis to chart"; m_chart->addAxis(axis,alignment); // We could also check if m_series and do: // barSeries->attachAxis(axisY); } /** * @brief Set the range of the (first) horizontal axis * @param min * @param max */ void Chart::setAxisXRange(const QVariant &min, const QVariant &max){ qCDebug(lcChart)<< "Setting axis X range..."; m_chart->axes(Qt::Horizontal).first()->setRange(min, max); } /** * @brief Sets the minimum value shown on the horizontal axis. * @param min */ void Chart::setAxisXMin(const QVariant &min){ qCDebug(lcChart)<< "Setting axis X min..."; m_chart->axes(Qt::Horizontal).first()->setMin(min); } /** * @brief Set the range of the vertical axis * @param min * @param max */ void Chart::setAxisYRange(const QVariant &min, const QVariant &max){ qCDebug(lcChart)<< "Setting axis Y range..."; m_chart->axes(Qt::Vertical).first()->setRange(min, max); } /** * @brief Sets the minimum value shown on the vertical axis. * @param min */ void Chart::setAxisYMin(const QVariant &min){ qCDebug(lcChart)<< "Setting axis X min..."; m_chart->axes(Qt::Vertical).first()->setMin(min); } void Chart::setAxisXLabelsAngle (const int &angle){ qCDebug(lcChart)<< "Setting axis X label angle..."; m_chart->axes(Qt::Horizontal).first()->setLabelsAngle(angle); } /** * @brief Set the label font of the horizontal axis * @param font */ void Chart::setAxisXLabelFont(const QFont &font){ qCDebug(lcChart)<< "Setting axis X label font..."; m_chart->axes(Qt::Horizontal).first()->setLabelsFont(font); } /** * @brief Set the label font of the vertical axis * @param font */ void Chart::setAxisYLabelFont(const QFont &font){ qCDebug(lcChart)<< "Setting axis Y label font..."; m_chart->axes(Qt::Vertical).first()->setLabelsFont(font); } /** * @brief Set the line pen of the horizontal axis * @param font */ void Chart::setAxisXLinePen(const QPen &pen){ qCDebug(lcChart)<< "Setting axis X line pen..."; m_chart->axes(Qt::Horizontal).first()->setLinePen(pen); } /** * @brief Set the line pen of the vertical axis * @param font */ void Chart::setAxisYLinePen(const QPen &pen){ qCDebug(lcChart)<< "Setting axis Y line pen..."; m_chart->axes(Qt::Vertical).first()->setLinePen(pen); } /** * @brief Set the grid line pen of the horizontal axis * @param font */ void Chart::setAxisXGridLinePen(const QPen &pen){ qCDebug(lcChart)<< "Setting axis X grid line pen..."; m_chart->axes(Qt::Horizontal).first()->setGridLinePen(pen); } /** * @brief Set the grid line pen of the vertical axis * @param font */ void Chart::setAxisYGridLinePen(const QPen &pen){ qCDebug(lcChart)<< "Setting axis Y grid line pen..."; m_chart->axes(Qt::Vertical).first()->setGridLinePen(pen); } /** * @brief Toggle the legend of the QChart * @param toggle */ void Chart::toggleLegend(const bool &toggle){ qCDebug(lcChart)<< "toggling chart legend..."; if (toggle) { m_chart->legend()->show(); } else { m_chart->legend()->hide(); } } /** * @brief Sets the background brush of the QChart * If no brush defined, it uses a transparent brush. * @param brush */ void Chart::setChartBackgroundBrush(const QBrush & brush) { qCDebug(lcChart)<< "Setting chart background brush..."; m_chart->setBackgroundBrush(brush); } /** * @brief Sets the background pen of the QChart * If no pen defined, it uses a transparent pen. * @param brush */ void Chart::setChartBackgroundPen(const QPen & pen) { qCDebug(lcChart)<< "Setting chart background pen..."; m_chart->setBackgroundPen(pen); } /** * @brief Set the theme of the QChart * @param theme */ void Chart::setTheme(QChart::ChartTheme theme) { qCDebug(lcChart)<< "Setting chart theme..."; m_chart->setTheme(theme); } /** * @brief Set the theme for when our chart widget is small * @param chartHeight */ void Chart::setThemeSmallWidget(const int minWidth, const int minHeight) { qCDebug(lcChart)<< "Setting small chart widget theme..."; setTheme(); setBackgroundBrush(QBrush(Qt::transparent)); setChartBackgroundBrush(); setChartBackgroundPen(); toggleLegend(false); setRenderHint(QPainter::Antialiasing); setMinimumWidth(minWidth); setMaximumHeight(1.5*minHeight); setMinimumHeight(minHeight); setFrameShape(QFrame::NoFrame); } /** * @brief Set the margins of the QChart * @param margins */ void Chart::setMargins(const QMargins &margins){ qCDebug(lcChart)<< "Setting chart margins..."; m_chart->setMargins(margins); } /** * @brief Set the title of the QChart * @param title */ void Chart::setTitle(const QString &title, const QFont &font){ qCDebug(lcChart) << "Setting chart title..." ; m_chart->setTitleFont(font); m_chart->setTitle(title); } /** * @brief Applies a simple theme to axes (default label fonts, line and grid line pen). * WARNING: Axes must be already attached to m_chart */ void Chart::setAxesThemeDefault() { qCDebug(lcChart)<< "Setting a simple theme to chart axes..."; setAxisXLabelFont(); setAxisXLinePen(); setAxisXGridLinePen(); setAxisYLabelFont(); setAxisYLinePen(); setAxisYGridLinePen(); setMargins(QMargins()); } void Chart::resetToTrivial() { qCDebug(lcChart)<< "Resetting chart to trivial..."; removeAllSeries(); addSeries(); createDefaultAxes(); axes(Qt::Horizontal).first()->setLabelsAngle(-90); setTitle("Chart", QFont("Times",8)); setAxisXRange(0,1); setAxisYRange(0,1); setAxesThemeDefault(); } SocNetV-3.7/src/chart.h000066400000000000000000000055661524402210300147520ustar00rootroot00000000000000/** * @file chart.h * @brief Declares the Chart class for generating and displaying statistical charts in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef CHART_H #define CHART_H #include #include #include class QSplineSeries; class QChart; class QAbstractAxis; class Chart : public QChartView { Q_OBJECT public: explicit Chart(QWidget *parent = Q_NULLPTR); // explicit Chart ( QChart *ch = Q_NULLPTR, QWidget *parent = Q_NULLPTR ); ~Chart(); void setTitle(const QString &title = QString(), const QFont &font = QFont()); void addSeries(QAbstractSeries *series = Q_NULLPTR); void appendToSeries(const QPointF &p); void removeAllSeries(); void createDefaultAxes(); QList axes(Qt::Orientations orientation = Qt::Horizontal | Qt::Vertical, QAbstractSeries *series = Q_NULLPTR) const; void removeAllAxes(); void addAxis(QAbstractAxis *axis, Qt::Alignment alignment); void setAxisX(QAbstractAxis *axis, QAbstractSeries *series = Q_NULLPTR); void setAxisY(QAbstractAxis *axis, QAbstractSeries *series = Q_NULLPTR); void setAxisXRange(const QVariant &min, const QVariant &max); void setAxisXMin(const QVariant &min); void setAxisYRange(const QVariant &min, const QVariant &max); void setAxisYMin(const QVariant &min); void setAxesThemeDefault(); void setAxisXLabelsAngle(const int &angle); void setAxisXLabelFont(const QFont &font = QFont("Helvetica", 6)); void setAxisYLabelFont(const QFont &font = QFont("Helvetica", 6)); void setAxisXLinePen(const QPen &pen = QPen(QColor("#d0d0d0"), 1, Qt::SolidLine)); void setAxisYLinePen(const QPen &pen = QPen(QColor("#d0d0d0"), 1, Qt::SolidLine)); void setAxisXGridLinePen(const QPen &pen = QPen(QColor("#e0e0e0"), 1, Qt::DotLine)); void setAxisYGridLinePen(const QPen &pen = QPen(QColor("#e0e0e0"), 1, Qt::DotLine)); void setTheme(QChart::ChartTheme theme = QChart::ChartThemeLight); void setThemeSmallWidget(const int minWidth, const int minHeight); void setChartBackgroundBrush(const QBrush &brush = QBrush(Qt::transparent)); void setChartBackgroundPen(const QPen &pen = QPen(Qt::transparent)); void setMargins(const QMargins &margins = QMargins()); void toggleLegend(const bool &toggle = false); void resetToTrivial(); private: QChart *m_chart; QSplineSeries *m_series; }; #endif // CHART_H SocNetV-3.7/src/data.qrc000066400000000000000000000031551524402210300151100ustar00rootroot00000000000000 data/Bernard_Killworth_Fraternity.dl data/Campnet.paj data/Freeman_34_possible_graphs_with_N_5_multirelational.paj data/Freeman_EIES_network_48actors_Acquaintanceship_at_time_1.dl data/Freeman_EIES_network_48actors_Acquaintanceship_at_time_2.dl data/Freeman_EIES_network_48actors_Messages.dl data/Freeman_EIES_networks_32actors.dl data/Herschel_Graph.paj data/Knoke_Bureaucracies_Network.pajek data/Krackhardt_High-tech_managers.paj data/Mexican_Power_Network_1940s.lst data/Padgett_Florentine_Families.paj data/Petersen_Graph.paj data/Stephenson_Zelen_40_AIDS_patients_sex_contact.paj data/Stephenson_Zelen_5actors_6edges_IC_test_dataset.paj data/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj data/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl data/Stokman_Ziegler_Corporate_Interlocks_West_Germany.dl data/Thurman_Office_Networks_Coalitions.dl data/Wasserman_Faust_7actors_star_circle_line_graphs.paj data/Wasserman_Faust_Countries_Trade_Data_Basic_Manufactured_Goods.pajek data/Galaskiewicz_CEOs_and_clubs_affiliation_network_data.2sm data/Zachary_Karate_Club.dl SocNetV-3.7/src/data/000077500000000000000000000000001524402210300143755ustar00rootroot00000000000000SocNetV-3.7/src/data/Benchmark_BA_Directed_N500_m3.paj000066400000000000000000001251151524402210300222360ustar00rootroot00000000000000*Network scale-free *Vertices 500 1 "1" ic red 0.518487 0.5 circle 2 "2" ic red 0.518218 0.50034 circle 3 "3" ic red 0.521146 0.500789 circle 4 "4" ic red 0.679039 0.510024 circle 5 "5" ic red 0.533504 0.502502 circle 6 "6" ic red 0.519971 0.501865 circle 7 "7" ic red 0.678911 0.520062 circle 8 "8" ic red 0.648386 0.519426 circle 9 "9" ic red 0.678421 0.526716 circle 10 "10" ic red 0.633061 0.522434 circle 11 "11" ic red 0.522632 0.504244 circle 12 "12" ic red 0.64695 0.530346 circle 13 "13" ic red 0.597283 0.521943 circle 14 "14" ic red 0.623058 0.530109 circle 15 "15" ic red 0.67664 0.546611 circle 16 "16" ic red 0.676084 0.549861 circle 17 "17" ic red 0.645445 0.544003 circle 18 "18" ic red 0.645061 0.546713 circle 19 "19" ic red 0.648441 0.550709 circle 20 "20" ic red 0.656524 0.556554 circle 21 "21" ic red 0.674389 0.566465 circle 22 "22" ic red 0.64329 0.55747 circle 23 "23" ic red 0.673209 0.572949 circle 24 "24" ic red 0.671823 0.575841 circle 25 "25" ic red 0.64228 0.565701 circle 26 "26" ic red 0.641104 0.568056 circle 27 "27" ic red 0.716753 0.609031 circle 28 "28" ic red 0.715789 0.613053 circle 29 "29" ic red 0.676224 0.596038 circle 30 "30" ic red 0.638741 0.578562 circle 31 "31" ic red 0.666671 0.597955 circle 32 "32" ic red 0.6374 0.583732 circle 33 "33" ic red 0.640136 0.58847 circle 34 "34" ic red 0.636267 0.589045 circle 35 "35" ic red 0.635028 0.59126 circle 36 "36" ic red 0.634671 0.594068 circle 37 "37" ic red 0.633909 0.596606 circle 38 "38" ic red 0.660146 0.619249 circle 39 "39" ic red 0.659124 0.622227 circle 40 "40" ic red 0.701899 0.65989 circle 41 "41" ic red 0.630069 0.606143 circle 42 "42" ic red 0.656129 0.631229 circle 43 "43" ic red 0.628236 0.610967 circle 44 "44" ic red 0.65364 0.636817 circle 45 "45" ic red 0.653213 0.640354 circle 46 "46" ic red 0.625322 0.618057 circle 47 "47" ic red 0.627555 0.623525 circle 48 "48" ic red 0.649034 0.648322 circle 49 "49" ic red 0.622318 0.625069 circle 50 "50" ic red 0.621168 0.627255 circle 51 "51" ic red 0.620081 0.629505 circle 52 "52" ic red 0.643688 0.659098 circle 53 "53" ic red 0.642466 0.66192 circle 54 "54" ic red 0.616784 0.63622 circle 55 "55" ic red 0.615587 0.638347 circle 56 "56" ic red 0.614724 0.640881 circle 57 "57" ic red 0.613173 0.64257 circle 58 "58" ic red 0.63539 0.67495 circle 59 "59" ic red 0.633829 0.677369 circle 60 "60" ic red 0.60954 0.648892 circle 61 "61" ic red 0.630784 0.682305 circle 62 "62" ic red 0.606842 0.652727 circle 63 "63" ic red 0.628109 0.68779 circle 64 "64" ic red 0.625956 0.689334 circle 65 "65" ic red 0.605563 0.66272 circle 66 "66" ic red 0.622719 0.693984 circle 67 "67" ic red 0.600282 0.662565 circle 68 "68" ic red 0.623174 0.704782 circle 69 "69" ic red 0.597499 0.666255 circle 70 "70" ic red 0.596104 0.668097 circle 71 "71" ic red 0.594653 0.669839 circle 72 "72" ic red 0.613013 0.708052 circle 73 "73" ic red 0.591709 0.673243 circle 74 "74" ic red 0.590274 0.675014 circle 75 "75" ic red 0.60722 0.713371 circle 76 "76" ic red 0.587185 0.678127 circle 77 "77" ic red 0.585759 0.679926 circle 78 "78" ic red 0.584142 0.681323 circle 79 "79" ic red 0.582692 0.683083 circle 80 "80" ic red 0.597983 0.722944 circle 81 "81" ic red 0.596086 0.72475 circle 82 "82" ic red 0.594303 0.72683 circle 83 "83" ic red 0.592223 0.728195 circle 84 "84" ic red 0.574701 0.690216 circle 85 "85" ic red 0.573136 0.691728 circle 86 "86" ic red 0.571506 0.693074 circle 87 "87" ic red 0.569869 0.694401 circle 88 "88" ic red 0.582384 0.736326 circle 89 "89" ic red 0.566556 0.696946 circle 90 "90" ic red 0.564836 0.698029 circle 91 "91" ic red 0.56322 0.69943 circle 92 "92" ic red 0.561508 0.70053 circle 93 "93" ic red 0.559803 0.701653 circle 94 "94" ic red 0.570216 0.745071 circle 95 "95" ic red 0.568043 0.746028 circle 96 "96" ic red 0.566279 0.748492 circle 97 "97" ic red 0.564232 0.749947 circle 98 "98" ic red 0.578875 0.818906 circle 99 "99" ic red 0.55963 0.750792 circle 100 "100" ic red 0.547605 0.70853 circle 101 "101" ic red 0.545847 0.709455 circle 102 "102" ic red 0.55322 0.75395 circle 103 "103" ic red 0.551138 0.755286 circle 104 "104" ic red 0.540486 0.711817 circle 105 "105" ic red 0.546761 0.756896 circle 106 "106" ic red 0.54458 0.757732 circle 107 "107" ic red 0.542395 0.758543 circle 108 "108" ic red 0.533274 0.714624 circle 109 "109" ic red 0.531566 0.715992 circle 110 "110" ic red 0.52963 0.715797 circle 111 "111" ic red 0.533572 0.761243 circle 112 "112" ic red 0.531358 0.761849 circle 113 "113" ic red 0.529139 0.762413 circle 114 "114" ic red 0.526915 0.762936 circle 115 "115" ic red 0.520459 0.718304 circle 116 "116" ic red 0.51861 0.718668 circle 117 "117" ic red 0.52023 0.764392 circle 118 "118" ic red 0.522966 0.837967 circle 119 "119" ic red 0.513031 0.719326 circle 120 "120" ic red 0.511173 0.719552 circle 121 "121" ic red 0.509323 0.719971 circle 122 "122" ic red 0.509002 0.765618 circle 123 "123" ic red 0.506756 0.765903 circle 124 "124" ic red 0.503728 0.720164 circle 125 "125" ic red 0.502251 0.765933 circle 126 "126" ic red 0.5 0.720177 circle 127 "127" ic red 0.497749 0.765933 circle 128 "128" ic red 0.496257 0.721049 circle 129 "129" ic red 0.494385 0.720984 circle 130 "130" ic red 0.492542 0.720059 circle 131 "131" ic red 0.490674 0.720036 circle 132 "132" ic red 0.486492 0.765432 circle 133 "133" ic red 0.484183 0.766226 circle 134 "134" ic red 0.477034 0.837967 circle 135 "135" ic red 0.483249 0.71892 circle 136 "136" ic red 0.477545 0.763857 circle 137 "137" ic red 0.468465 0.836479 circle 138 "138" ic red 0.473061 0.763167 circle 139 "139" ic red 0.470821 0.762769 circle 140 "140" ic red 0.468642 0.761849 circle 141 "141" ic red 0.46638 0.761615 circle 142 "142" ic red 0.464219 0.760596 circle 143 "143" ic red 0.461793 0.761434 circle 144 "144" ic red 0.459798 0.759314 circle 145 "145" ic red 0.457589 0.758641 circle 146 "146" ic red 0.455443 0.757599 circle 147 "147" ic red 0.461284 0.7127 circle 148 "148" ic red 0.437493 0.827025 circle 149 "149" ic red 0.448888 0.755151 circle 150 "150" ic red 0.446749 0.754097 circle 151 "151" ic red 0.444511 0.753502 circle 152 "152" ic red 0.452383 0.708583 circle 153 "153" ic red 0.440336 0.750937 circle 154 "154" ic red 0.421125 0.818906 circle 155 "155" ic red 0.447134 0.705718 circle 156 "156" ic red 0.44534 0.704929 circle 157 "157" ic red 0.443654 0.703733 circle 158 "158" ic red 0.441884 0.702841 circle 159 "159" ic red 0.427706 0.743771 circle 160 "160" ic red 0.438494 0.700524 circle 161 "161" ic red 0.423675 0.740767 circle 162 "162" ic red 0.421684 0.7392 circle 163 "163" ic red 0.419623 0.737844 circle 164 "164" ic red 0.43152 0.696442 circle 165 "165" ic red 0.415666 0.734649 circle 166 "166" ic red 0.428491 0.693083 circle 167 "167" ic red 0.426896 0.691644 circle 168 "168" ic red 0.409328 0.730885 circle 169 "169" ic red 0.423695 0.688808 circle 170 "170" ic red 0.422046 0.687506 circle 171 "171" ic red 0.403998 0.724552 circle 172 "172" ic red 0.402105 0.722744 circle 173 "173" ic red 0.400227 0.7209 circle 174 "174" ic red 0.398227 0.719319 circle 175 "175" ic red 0.414234 0.67994 circle 176 "176" ic red 0.394634 0.715273 circle 177 "177" ic red 0.392718 0.713495 circle 178 "178" ic red 0.409707 0.675052 circle 179 "179" ic red 0.389203 0.709301 circle 180 "180" ic red 0.387539 0.707035 circle 181 "181" ic red 0.405347 0.669839 circle 182 "182" ic red 0.38407 0.702775 circle 183 "183" ic red 0.382362 0.700596 circle 184 "184" ic red 0.347577 0.753411 circle 185 "185" ic red 0.399401 0.663078 circle 186 "186" ic red 0.398349 0.660682 circle 187 "187" ic red 0.375649 0.69168 circle 188 "188" ic red 0.395343 0.657318 circle 189 "189" ic red 0.372329 0.687148 circle 190 "190" ic red 0.393158 0.652727 circle 191 "191" ic red 0.369326 0.682152 circle 192 "192" ic red 0.331213 0.729424 circle 193 "193" ic red 0.389241 0.646793 circle 194 "194" ic red 0.327401 0.723031 circle 195 "195" ic red 0.363211 0.672321 circle 196 "196" ic red 0.361747 0.669776 circle 197 "197" ic red 0.360445 0.667034 circle 198 "198" ic red 0.358432 0.665129 circle 199 "199" ic red 0.382229 0.633852 circle 200 "200" ic red 0.381075 0.631679 circle 201 "201" ic red 0.354855 0.656536 circle 202 "202" ic red 0.353664 0.653687 circle 203 "203" ic red 0.377848 0.6249 circle 204 "204" ic red 0.37633 0.623079 circle 205 "205" ic red 0.375687 0.620386 circle 206 "206" ic red 0.374731 0.618007 circle 207 "207" ic red 0.373687 0.615712 circle 208 "208" ic red 0.372717 0.613347 circle 209 "209" ic red 0.345214 0.633941 circle 210 "210" ic red 0.343956 0.631158 circle 211 "211" ic red 0.342996 0.628124 circle 212 "212" ic red 0.341832 0.625258 circle 213 "213" ic red 0.340794 0.62229 circle 214 "214" ic red 0.339497 0.619515 circle 215 "215" ic red 0.366415 0.596372 circle 216 "216" ic red 0.365696 0.593812 circle 217 "217" ic red 0.33694 0.610205 circle 218 "218" ic red 0.335935 0.60721 circle 219 "219" ic red 0.33504 0.604141 circle 220 "220" ic red 0.362686 0.583679 circle 221 "221" ic red 0.361995 0.581108 circle 222 "222" ic red 0.332373 0.594918 circle 223 "223" ic red 0.331583 0.591784 circle 224 "224" ic red 0.331048 0.588515 circle 225 "225" ic red 0.359276 0.570787 circle 226 "226" ic red 0.282365 0.604968 circle 227 "227" ic red 0.358331 0.565419 circle 228 "228" ic red 0.357337 0.56297 circle 229 "229" ic red 0.327548 0.57263 circle 230 "230" ic red 0.356316 0.557628 circle 231 "231" ic red 0.356296 0.55477 circle 232 "232" ic red 0.277633 0.580343 circle 233 "233" ic red 0.325308 0.559677 circle 234 "234" ic red 0.324907 0.556384 circle 235 "235" ic red 0.354555 0.544003 circle 236 "236" ic red 0.354201 0.541285 circle 237 "237" ic red 0.323339 0.546617 circle 238 "238" ic red 0.323061 0.543293 circle 239 "239" ic red 0.35325 0.5331 circle 240 "240" ic red 0.322384 0.536679 circle 241 "241" ic red 0.352646 0.527632 circle 242 "242" ic red 0.352455 0.524876 circle 243 "243" ic red 0.352319 0.522113 circle 244 "244" ic red 0.272025 0.529845 circle 245 "245" ic red 0.320944 0.520078 circle 246 "246" ic red 0.351332 0.513884 circle 247 "247" ic red 0.351662 0.511077 circle 248 "248" ic red 0.351778 0.508299 circle 249 "249" ic red 0.320491 0.506698 circle 250 "250" ic red 0.351486 0.50277 circle 251 "251" ic red 0.32073 0.5 circle 252 "252" ic red 0.351684 0.497233 circle 253 "253" ic red 0.351612 0.494463 circle 254 "254" ic red 0.35174 0.491699 circle 255 "255" ic red 0.320818 0.486619 circle 256 "256" ic red 0.351208 0.486104 circle 257 "257" ic red 0.321079 0.479937 circle 258 "258" ic red 0.352246 0.480657 circle 259 "259" ic red 0.352384 0.477897 circle 260 "260" ic red 0.352065 0.475058 circle 261 "261" ic red 0.352213 0.472287 circle 262 "262" ic red 0.352981 0.46964 circle 263 "263" ic red 0.322776 0.460026 circle 264 "264" ic red 0.323117 0.456721 circle 265 "265" ic red 0.353925 0.461454 circle 266 "266" ic red 0.354105 0.458688 circle 267 "267" ic red 0.354162 0.455878 circle 268 "268" ic red 0.354848 0.453258 circle 269 "269" ic red 0.27697 0.423811 circle 270 "270" ic red 0.325764 0.437047 circle 271 "271" ic red 0.356235 0.445207 circle 272 "272" ic red 0.356262 0.44235 circle 273 "273" ic red 0.327486 0.427343 circle 274 "274" ic red 0.328033 0.424095 circle 275 "275" ic red 0.358176 0.43451 circle 276 "276" ic red 0.35883 0.431912 circle 277 "277" ic red 0.359483 0.429317 circle 278 "278" ic red 0.360093 0.426702 circle 279 "279" ic red 0.360663 0.424065 circle 280 "280" ic red 0.3326 0.405211 circle 281 "281" ic red 0.333266 0.402008 circle 282 "282" ic red 0.334162 0.398938 circle 283 "283" ic red 0.363469 0.413806 circle 284 "284" ic red 0.335876 0.392751 circle 285 "285" ic red 0.223646 0.313225 circle 286 "286" ic red 0.225249 0.308085 circle 287 "287" ic red 0.226895 0.302975 circle 288 "288" ic red 0.228585 0.297896 circle 289 "289" ic red 0.230317 0.292849 circle 290 "290" ic red 0.232092 0.287835 circle 291 "291" ic red 0.233909 0.282855 circle 292 "292" ic red 0.235768 0.277909 circle 293 "293" ic red 0.237669 0.272997 circle 294 "294" ic red 0.239612 0.268122 circle 295 "295" ic red 0.241595 0.263283 circle 296 "296" ic red 0.243619 0.258482 circle 297 "297" ic red 0.245684 0.253719 circle 298 "298" ic red 0.247789 0.248994 circle 299 "299" ic red 0.249934 0.24431 circle 300 "300" ic red 0.252118 0.239666 circle 301 "301" ic red 0.254342 0.235062 circle 302 "302" ic red 0.256604 0.230501 circle 303 "303" ic red 0.258904 0.225982 circle 304 "304" ic red 0.261243 0.221507 circle 305 "305" ic red 0.26362 0.217075 circle 306 "306" ic red 0.266033 0.212688 circle 307 "307" ic red 0.268484 0.208347 circle 308 "308" ic red 0.270971 0.204051 circle 309 "309" ic red 0.273495 0.199803 circle 310 "310" ic red 0.276054 0.195601 circle 311 "311" ic red 0.278648 0.191448 circle 312 "312" ic red 0.281278 0.187343 circle 313 "313" ic red 0.283942 0.183288 circle 314 "314" ic red 0.28664 0.179283 circle 315 "315" ic red 0.289372 0.175329 circle 316 "316" ic red 0.292137 0.171425 circle 317 "317" ic red 0.294935 0.167574 circle 318 "318" ic red 0.297765 0.163775 circle 319 "319" ic red 0.300627 0.16003 circle 320 "320" ic red 0.303521 0.156337 circle 321 "321" ic red 0.306446 0.1527 circle 322 "322" ic red 0.309401 0.149117 circle 323 "323" ic red 0.312387 0.145589 circle 324 "324" ic red 0.315402 0.142118 circle 325 "325" ic red 0.318446 0.138703 circle 326 "326" ic red 0.321519 0.135345 circle 327 "327" ic red 0.32462 0.132044 circle 328 "328" ic red 0.327749 0.128802 circle 329 "329" ic red 0.330904 0.125618 circle 330 "330" ic red 0.334087 0.122494 circle 331 "331" ic red 0.337296 0.119429 circle 332 "332" ic red 0.34053 0.116424 circle 333 "333" ic red 0.34379 0.113479 circle 334 "334" ic red 0.347075 0.110596 circle 335 "335" ic red 0.350383 0.107774 circle 336 "336" ic red 0.353715 0.105014 circle 337 "337" ic red 0.357071 0.102317 circle 338 "338" ic red 0.360448 0.0996823 circle 339 "339" ic red 0.363848 0.0971109 circle 340 "340" ic red 0.36727 0.0946031 circle 341 "341" ic red 0.370712 0.0921592 circle 342 "342" ic red 0.374175 0.0897798 circle 343 "343" ic red 0.377657 0.0874652 circle 344 "344" ic red 0.381159 0.0852157 circle 345 "345" ic red 0.38468 0.0830317 circle 346 "346" ic red 0.388219 0.0809136 circle 347 "347" ic red 0.391775 0.0788616 circle 348 "348" ic red 0.395349 0.0768761 circle 349 "349" ic red 0.398939 0.0749575 circle 350 "350" ic red 0.402545 0.0731059 circle 351 "351" ic red 0.406167 0.0713218 circle 352 "352" ic red 0.409803 0.0696054 circle 353 "353" ic red 0.413454 0.0679569 circle 354 "354" ic red 0.417118 0.0663767 circle 355 "355" ic red 0.420795 0.0648649 circle 356 "356" ic red 0.424485 0.0634219 circle 357 "357" ic red 0.428187 0.0620478 circle 358 "358" ic red 0.4319 0.0607428 circle 359 "359" ic red 0.435624 0.0595072 circle 360 "360" ic red 0.439358 0.0583412 circle 361 "361" ic red 0.443102 0.0572449 circle 362 "362" ic red 0.446854 0.0562185 circle 363 "363" ic red 0.450615 0.0552622 circle 364 "364" ic red 0.454384 0.0543762 circle 365 "365" ic red 0.45816 0.0535605 circle 366 "366" ic red 0.461943 0.0528153 circle 367 "367" ic red 0.465731 0.0521407 circle 368 "368" ic red 0.469525 0.0515369 circle 369 "369" ic red 0.473324 0.0510038 circle 370 "370" ic red 0.477127 0.0505417 circle 371 "371" ic red 0.480934 0.0501505 circle 372 "372" ic red 0.484743 0.0498304 circle 373 "373" ic red 0.488555 0.0495813 circle 374 "374" ic red 0.492369 0.0494034 circle 375 "375" ic red 0.496184 0.0492967 circle 376 "376" ic red 0.5 0.0492611 circle 377 "377" ic red 0.503816 0.0492967 circle 378 "378" ic red 0.507631 0.0494034 circle 379 "379" ic red 0.511445 0.0495813 circle 380 "380" ic red 0.515257 0.0498304 circle 381 "381" ic red 0.519066 0.0501505 circle 382 "382" ic red 0.522873 0.0505417 circle 383 "383" ic red 0.526676 0.0510038 circle 384 "384" ic red 0.530475 0.0515369 circle 385 "385" ic red 0.534269 0.0521407 circle 386 "386" ic red 0.538057 0.0528153 circle 387 "387" ic red 0.54184 0.0535605 circle 388 "388" ic red 0.545616 0.0543762 circle 389 "389" ic red 0.549385 0.0552622 circle 390 "390" ic red 0.553146 0.0562185 circle 391 "391" ic red 0.556898 0.0572449 circle 392 "392" ic red 0.560642 0.0583412 circle 393 "393" ic red 0.564376 0.0595072 circle 394 "394" ic red 0.5681 0.0607428 circle 395 "395" ic red 0.571813 0.0620478 circle 396 "396" ic red 0.575515 0.0634219 circle 397 "397" ic red 0.579205 0.0648649 circle 398 "398" ic red 0.582882 0.0663767 circle 399 "399" ic red 0.586546 0.0679569 circle 400 "400" ic red 0.590197 0.0696054 circle 401 "401" ic red 0.593833 0.0713218 circle 402 "402" ic red 0.597455 0.0731059 circle 403 "403" ic red 0.601061 0.0749575 circle 404 "404" ic red 0.604651 0.0768761 circle 405 "405" ic red 0.608225 0.0788616 circle 406 "406" ic red 0.611781 0.0809136 circle 407 "407" ic red 0.61532 0.0830317 circle 408 "408" ic red 0.618841 0.0852157 circle 409 "409" ic red 0.622343 0.0874652 circle 410 "410" ic red 0.625825 0.0897798 circle 411 "411" ic red 0.629288 0.0921592 circle 412 "412" ic red 0.63273 0.0946031 circle 413 "413" ic red 0.636152 0.0971109 circle 414 "414" ic red 0.639552 0.0996823 circle 415 "415" ic red 0.642929 0.102317 circle 416 "416" ic red 0.646285 0.105014 circle 417 "417" ic red 0.649617 0.107774 circle 418 "418" ic red 0.652925 0.110596 circle 419 "419" ic red 0.65621 0.113479 circle 420 "420" ic red 0.65947 0.116424 circle 421 "421" ic red 0.662704 0.119429 circle 422 "422" ic red 0.665913 0.122494 circle 423 "423" ic red 0.669096 0.125618 circle 424 "424" ic red 0.672251 0.128802 circle 425 "425" ic red 0.67538 0.132044 circle 426 "426" ic red 0.68919 0.113465 circle 427 "427" ic red 0.692447 0.117025 circle 428 "428" ic red 0.695674 0.120645 circle 429 "429" ic red 0.69887 0.124325 circle 430 "430" ic red 0.702035 0.128064 circle 431 "431" ic red 0.705167 0.131862 circle 432 "432" ic red 0.708268 0.135718 circle 433 "433" ic red 0.711335 0.139631 circle 434 "434" ic red 0.714369 0.143602 circle 435 "435" ic red 0.717369 0.147629 circle 436 "436" ic red 0.720335 0.151711 circle 437 "437" ic red 0.723266 0.155848 circle 438 "438" ic red 0.726162 0.16004 circle 439 "439" ic red 0.729022 0.164286 circle 440 "440" ic red 0.731845 0.168584 circle 441 "441" ic red 0.734633 0.172935 circle 442 "442" ic red 0.737383 0.177337 circle 443 "443" ic red 0.740096 0.181791 circle 444 "444" ic red 0.74277 0.186294 circle 445 "445" ic red 0.745407 0.190847 circle 446 "446" ic red 0.748005 0.195449 circle 447 "447" ic red 0.750563 0.2001 circle 448 "448" ic red 0.753082 0.204797 circle 449 "449" ic red 0.755561 0.209541 circle 450 "450" ic red 0.758 0.214331 circle 451 "451" ic red 0.760398 0.219166 circle 452 "452" ic red 0.762755 0.224045 circle 453 "453" ic red 0.76507 0.228968 circle 454 "454" ic red 0.767343 0.233934 circle 455 "455" ic red 0.769575 0.238942 circle 456 "456" ic red 0.771763 0.243991 circle 457 "457" ic red 0.773909 0.24908 circle 458 "458" ic red 0.776012 0.254209 circle 459 "459" ic red 0.778071 0.259377 circle 460 "460" ic red 0.780086 0.264583 circle 461 "461" ic red 0.782056 0.269826 circle 462 "462" ic red 0.783983 0.275106 circle 463 "463" ic red 0.785864 0.28042 circle 464 "464" ic red 0.7877 0.28577 circle 465 "465" ic red 0.789491 0.291153 circle 466 "466" ic red 0.791236 0.29657 circle 467 "467" ic red 0.792935 0.302018 circle 468 "468" ic red 0.794588 0.307498 circle 469 "469" ic red 0.796195 0.313008 circle 470 "470" ic red 0.797754 0.318548 circle 471 "471" ic red 0.799267 0.324116 circle 472 "472" ic red 0.800732 0.329712 circle 473 "473" ic red 0.80215 0.335335 circle 474 "474" ic red 0.80352 0.340984 circle 475 "475" ic red 0.804842 0.346659 circle 476 "476" ic red 0.806116 0.352357 circle 477 "477" ic red 0.807342 0.358079 circle 478 "478" ic red 0.808519 0.363823 circle 479 "479" ic red 0.809647 0.369588 circle 480 "480" ic red 0.810727 0.375374 circle 481 "481" ic red 0.811757 0.38118 circle 482 "482" ic red 0.812739 0.387005 circle 483 "483" ic red 0.81367 0.392847 circle 484 "484" ic red 0.814553 0.398707 circle 485 "485" ic red 0.815385 0.404582 circle 486 "486" ic red 0.816168 0.410472 circle 487 "487" ic red 0.816901 0.416377 circle 488 "488" ic red 0.817584 0.422295 circle 489 "489" ic red 0.818217 0.428225 circle 490 "490" ic red 0.818799 0.434166 circle 491 "491" ic red 0.819331 0.440118 circle 492 "492" ic red 0.819813 0.446079 circle 493 "493" ic red 0.820244 0.452049 circle 494 "494" ic red 0.820625 0.458026 circle 495 "495" ic red 0.820955 0.46401 circle 496 "496" ic red 0.821234 0.47 circle 497 "497" ic red 0.821463 0.475994 circle 498 "498" ic red 0.821641 0.481992 circle 499 "499" ic red 0.821768 0.487993 circle 500 "500" ic red 0.821844 0.493996 circle *Arcs 2 1 1 c #666666 3 1 1 c #666666 3 2 1 c #666666 4 1 1 c #666666 4 2 1 c #666666 5 1 1 c #666666 5 3 1 c #666666 6 1 1 c #666666 6 2 1 c #666666 7 3 1 c #666666 7 6 1 c #666666 8 2 1 c #666666 8 3 1 c #666666 8 5 1 c #666666 9 2 1 c #666666 9 3 1 c #666666 10 1 1 c #666666 10 2 1 c #666666 10 6 1 c #666666 11 1 1 c #666666 11 2 1 c #666666 11 6 1 c #666666 12 1 1 c #666666 12 2 1 c #666666 12 3 1 c #666666 13 1 1 c #666666 13 2 1 c #666666 13 3 1 c #666666 14 1 1 c #666666 14 2 1 c #666666 14 3 1 c #666666 15 2 1 c #666666 15 11 1 c #666666 16 2 1 c #666666 16 6 1 c #666666 17 2 1 c #666666 17 3 1 c #666666 17 6 1 c #666666 18 2 1 c #666666 18 3 1 c #666666 18 6 1 c #666666 19 1 1 c #666666 19 11 1 c #666666 19 13 1 c #666666 20 2 1 c #666666 20 10 1 c #666666 20 14 1 c #666666 21 2 1 c #666666 21 5 1 c #666666 22 1 1 c #666666 22 2 1 c #666666 22 11 1 c #666666 23 2 1 c #666666 23 5 1 c #666666 24 2 1 c #666666 24 6 1 c #666666 25 3 1 c #666666 25 5 1 c #666666 25 6 1 c #666666 26 1 1 c #666666 26 2 1 c #666666 26 3 1 c #666666 27 1 1 c #666666 28 2 1 c #666666 29 1 1 c #666666 29 14 1 c #666666 30 1 1 c #666666 30 6 1 c #666666 30 11 1 c #666666 31 2 1 c #666666 31 6 1 c #666666 32 1 1 c #666666 32 3 1 c #666666 32 11 1 c #666666 33 1 1 c #666666 33 3 1 c #666666 33 13 1 c #666666 34 1 1 c #666666 34 5 1 c #666666 34 6 1 c #666666 35 1 1 c #666666 35 2 1 c #666666 35 3 1 c #666666 36 1 1 c #666666 36 2 1 c #666666 36 5 1 c #666666 37 1 1 c #666666 37 5 1 c #666666 37 6 1 c #666666 38 1 1 c #666666 38 2 1 c #666666 39 1 1 c #666666 39 2 1 c #666666 40 2 1 c #666666 41 1 1 c #666666 41 2 1 c #666666 41 11 1 c #666666 42 3 1 c #666666 42 6 1 c #666666 43 2 1 c #666666 43 3 1 c #666666 43 6 1 c #666666 44 1 1 c #666666 44 2 1 c #666666 45 1 1 c #666666 45 5 1 c #666666 46 1 1 c #666666 46 2 1 c #666666 46 11 1 c #666666 47 2 1 c #666666 47 11 1 c #666666 47 13 1 c #666666 48 1 1 c #666666 48 11 1 c #666666 49 1 1 c #666666 49 3 1 c #666666 49 11 1 c #666666 50 1 1 c #666666 50 2 1 c #666666 50 11 1 c #666666 51 1 1 c #666666 51 2 1 c #666666 51 11 1 c #666666 52 2 1 c #666666 52 6 1 c #666666 53 1 1 c #666666 53 11 1 c #666666 54 2 1 c #666666 54 3 1 c #666666 54 11 1 c #666666 55 2 1 c #666666 55 6 1 c #666666 55 11 1 c #666666 56 2 1 c #666666 56 5 1 c #666666 56 6 1 c #666666 57 2 1 c #666666 57 3 1 c #666666 57 6 1 c #666666 58 6 1 c #666666 58 11 1 c #666666 59 2 1 c #666666 59 11 1 c #666666 60 2 1 c #666666 60 3 1 c #666666 60 11 1 c #666666 61 2 1 c #666666 61 11 1 c #666666 62 1 1 c #666666 62 2 1 c #666666 62 6 1 c #666666 63 2 1 c #666666 63 5 1 c #666666 64 2 1 c #666666 64 6 1 c #666666 65 1 1 c #666666 65 6 1 c #666666 65 13 1 c #666666 66 1 1 c #666666 66 6 1 c #666666 67 1 1 c #666666 67 6 1 c #666666 67 11 1 c #666666 68 2 1 c #666666 68 13 1 c #666666 69 1 1 c #666666 69 6 1 c #666666 69 11 1 c #666666 70 2 1 c #666666 70 3 1 c #666666 70 11 1 c #666666 71 1 1 c #666666 71 6 1 c #666666 71 11 1 c #666666 72 2 1 c #666666 72 5 1 c #666666 73 1 1 c #666666 73 3 1 c #666666 73 6 1 c #666666 74 1 1 c #666666 74 6 1 c #666666 74 11 1 c #666666 75 2 1 c #666666 75 3 1 c #666666 76 1 1 c #666666 76 2 1 c #666666 76 6 1 c #666666 77 2 1 c #666666 77 6 1 c #666666 77 11 1 c #666666 78 1 1 c #666666 78 2 1 c #666666 78 6 1 c #666666 79 1 1 c #666666 79 6 1 c #666666 79 11 1 c #666666 80 2 1 c #666666 80 3 1 c #666666 81 1 1 c #666666 81 3 1 c #666666 82 3 1 c #666666 82 11 1 c #666666 83 1 1 c #666666 83 6 1 c #666666 84 1 1 c #666666 84 2 1 c #666666 84 6 1 c #666666 85 1 1 c #666666 85 3 1 c #666666 85 6 1 c #666666 86 1 1 c #666666 86 2 1 c #666666 86 11 1 c #666666 87 2 1 c #666666 87 3 1 c #666666 87 6 1 c #666666 88 2 1 c #666666 88 6 1 c #666666 89 2 1 c #666666 89 3 1 c #666666 89 6 1 c #666666 90 1 1 c #666666 90 2 1 c #666666 90 6 1 c #666666 91 2 1 c #666666 91 6 1 c #666666 91 11 1 c #666666 92 2 1 c #666666 92 3 1 c #666666 92 6 1 c #666666 93 1 1 c #666666 93 2 1 c #666666 93 11 1 c #666666 94 2 1 c #666666 94 11 1 c #666666 95 1 1 c #666666 95 2 1 c #666666 96 2 1 c #666666 96 5 1 c #666666 97 3 1 c #666666 97 5 1 c #666666 98 1 1 c #666666 99 1 1 c #666666 99 2 1 c #666666 100 1 1 c #666666 100 2 1 c #666666 100 6 1 c #666666 101 1 1 c #666666 101 2 1 c #666666 101 3 1 c #666666 102 1 1 c #666666 102 2 1 c #666666 103 3 1 c #666666 103 6 1 c #666666 104 1 1 c #666666 104 2 1 c #666666 104 6 1 c #666666 105 1 1 c #666666 105 6 1 c #666666 106 2 1 c #666666 106 6 1 c #666666 107 2 1 c #666666 107 6 1 c #666666 108 1 1 c #666666 108 2 1 c #666666 108 3 1 c #666666 109 2 1 c #666666 109 5 1 c #666666 109 6 1 c #666666 110 1 1 c #666666 110 2 1 c #666666 110 3 1 c #666666 111 1 1 c #666666 111 2 1 c #666666 112 1 1 c #666666 112 2 1 c #666666 113 1 1 c #666666 113 2 1 c #666666 114 1 1 c #666666 114 2 1 c #666666 115 2 1 c #666666 115 6 1 c #666666 115 11 1 c #666666 116 2 1 c #666666 116 6 1 c #666666 116 11 1 c #666666 117 2 1 c #666666 117 6 1 c #666666 118 2 1 c #666666 119 1 1 c #666666 119 2 1 c #666666 119 6 1 c #666666 120 1 1 c #666666 120 2 1 c #666666 120 6 1 c #666666 121 2 1 c #666666 121 6 1 c #666666 121 11 1 c #666666 122 1 1 c #666666 122 2 1 c #666666 123 2 1 c #666666 123 6 1 c #666666 124 1 1 c #666666 124 2 1 c #666666 124 3 1 c #666666 125 1 1 c #666666 125 2 1 c #666666 126 1 1 c #666666 126 2 1 c #666666 126 6 1 c #666666 127 1 1 c #666666 127 2 1 c #666666 128 2 1 c #666666 128 3 1 c #666666 128 5 1 c #666666 129 1 1 c #666666 129 5 1 c #666666 129 11 1 c #666666 130 2 1 c #666666 130 3 1 c #666666 130 6 1 c #666666 131 2 1 c #666666 131 3 1 c #666666 131 11 1 c #666666 132 1 1 c #666666 132 3 1 c #666666 133 2 1 c #666666 133 5 1 c #666666 134 2 1 c #666666 135 1 1 c #666666 135 2 1 c #666666 135 11 1 c #666666 136 1 1 c #666666 136 2 1 c #666666 137 1 1 c #666666 138 1 1 c #666666 138 3 1 c #666666 139 2 1 c #666666 139 11 1 c #666666 140 1 1 c #666666 140 2 1 c #666666 141 3 1 c #666666 141 6 1 c #666666 142 1 1 c #666666 142 2 1 c #666666 143 3 1 c #666666 143 5 1 c #666666 144 2 1 c #666666 144 6 1 c #666666 145 2 1 c #666666 145 3 1 c #666666 146 1 1 c #666666 146 2 1 c #666666 147 1 1 c #666666 147 2 1 c #666666 147 11 1 c #666666 148 6 1 c #666666 149 2 1 c #666666 149 3 1 c #666666 150 1 1 c #666666 150 6 1 c #666666 151 3 1 c #666666 151 11 1 c #666666 152 1 1 c #666666 152 2 1 c #666666 152 3 1 c #666666 153 1 1 c #666666 153 6 1 c #666666 154 1 1 c #666666 155 1 1 c #666666 155 2 1 c #666666 155 6 1 c #666666 156 2 1 c #666666 156 6 1 c #666666 156 11 1 c #666666 157 1 1 c #666666 157 2 1 c #666666 157 3 1 c #666666 158 1 1 c #666666 158 6 1 c #666666 158 11 1 c #666666 159 1 1 c #666666 159 11 1 c #666666 160 1 1 c #666666 160 3 1 c #666666 160 6 1 c #666666 161 2 1 c #666666 161 6 1 c #666666 162 1 1 c #666666 162 2 1 c #666666 163 2 1 c #666666 163 6 1 c #666666 164 3 1 c #666666 164 5 1 c #666666 164 6 1 c #666666 165 1 1 c #666666 165 2 1 c #666666 166 2 1 c #666666 166 3 1 c #666666 166 6 1 c #666666 167 1 1 c #666666 167 2 1 c #666666 167 3 1 c #666666 168 1 1 c #666666 168 5 1 c #666666 169 1 1 c #666666 169 2 1 c #666666 169 6 1 c #666666 170 2 1 c #666666 170 3 1 c #666666 170 6 1 c #666666 171 1 1 c #666666 171 2 1 c #666666 172 1 1 c #666666 172 2 1 c #666666 173 1 1 c #666666 173 2 1 c #666666 174 2 1 c #666666 174 11 1 c #666666 175 1 1 c #666666 175 6 1 c #666666 175 11 1 c #666666 176 2 1 c #666666 176 6 1 c #666666 177 1 1 c #666666 177 11 1 c #666666 178 2 1 c #666666 178 3 1 c #666666 178 11 1 c #666666 179 1 1 c #666666 179 3 1 c #666666 180 1 1 c #666666 180 2 1 c #666666 181 1 1 c #666666 181 6 1 c #666666 181 11 1 c #666666 182 1 1 c #666666 182 2 1 c #666666 183 1 1 c #666666 183 2 1 c #666666 184 1 1 c #666666 185 2 1 c #666666 185 3 1 c #666666 185 5 1 c #666666 186 1 1 c #666666 186 6 1 c #666666 186 11 1 c #666666 187 1 1 c #666666 187 6 1 c #666666 188 1 1 c #666666 188 3 1 c #666666 188 5 1 c #666666 189 1 1 c #666666 189 11 1 c #666666 190 1 1 c #666666 190 2 1 c #666666 190 6 1 c #666666 191 2 1 c #666666 191 6 1 c #666666 192 1 1 c #666666 193 2 1 c #666666 193 6 1 c #666666 193 11 1 c #666666 194 2 1 c #666666 195 2 1 c #666666 195 11 1 c #666666 196 1 1 c #666666 196 11 1 c #666666 197 1 1 c #666666 197 6 1 c #666666 198 2 1 c #666666 198 5 1 c #666666 199 1 1 c #666666 199 2 1 c #666666 199 6 1 c #666666 200 1 1 c #666666 200 2 1 c #666666 200 3 1 c #666666 201 2 1 c #666666 201 11 1 c #666666 202 2 1 c #666666 202 6 1 c #666666 203 1 1 c #666666 203 2 1 c #666666 203 6 1 c #666666 204 2 1 c #666666 204 5 1 c #666666 204 6 1 c #666666 205 1 1 c #666666 205 2 1 c #666666 205 11 1 c #666666 206 1 1 c #666666 206 2 1 c #666666 206 3 1 c #666666 207 1 1 c #666666 207 3 1 c #666666 207 6 1 c #666666 208 1 1 c #666666 208 3 1 c #666666 208 6 1 c #666666 209 1 1 c #666666 209 2 1 c #666666 210 1 1 c #666666 210 3 1 c #666666 211 1 1 c #666666 211 2 1 c #666666 212 1 1 c #666666 212 6 1 c #666666 213 2 1 c #666666 213 6 1 c #666666 214 3 1 c #666666 214 11 1 c #666666 215 2 1 c #666666 215 3 1 c #666666 215 11 1 c #666666 216 1 1 c #666666 216 3 1 c #666666 216 6 1 c #666666 217 1 1 c #666666 217 2 1 c #666666 218 2 1 c #666666 218 6 1 c #666666 219 2 1 c #666666 219 6 1 c #666666 220 2 1 c #666666 220 3 1 c #666666 220 6 1 c #666666 221 1 1 c #666666 221 2 1 c #666666 221 11 1 c #666666 222 2 1 c #666666 222 11 1 c #666666 223 2 1 c #666666 223 11 1 c #666666 224 1 1 c #666666 224 2 1 c #666666 225 3 1 c #666666 225 6 1 c #666666 225 11 1 c #666666 226 2 1 c #666666 227 1 1 c #666666 227 2 1 c #666666 227 3 1 c #666666 228 1 1 c #666666 228 2 1 c #666666 228 5 1 c #666666 229 2 1 c #666666 229 6 1 c #666666 230 1 1 c #666666 230 2 1 c #666666 230 5 1 c #666666 231 1 1 c #666666 231 2 1 c #666666 231 3 1 c #666666 232 1 1 c #666666 233 2 1 c #666666 233 6 1 c #666666 234 1 1 c #666666 234 2 1 c #666666 235 2 1 c #666666 235 3 1 c #666666 235 6 1 c #666666 236 1 1 c #666666 236 2 1 c #666666 236 11 1 c #666666 237 1 1 c #666666 237 11 1 c #666666 238 2 1 c #666666 238 3 1 c #666666 239 2 1 c #666666 239 3 1 c #666666 239 6 1 c #666666 240 2 1 c #666666 240 3 1 c #666666 241 2 1 c #666666 241 3 1 c #666666 241 11 1 c #666666 242 1 1 c #666666 242 6 1 c #666666 242 11 1 c #666666 243 1 1 c #666666 243 3 1 c #666666 243 6 1 c #666666 244 1 1 c #666666 245 3 1 c #666666 245 11 1 c #666666 246 2 1 c #666666 246 3 1 c #666666 246 5 1 c #666666 247 2 1 c #666666 247 3 1 c #666666 247 11 1 c #666666 248 1 1 c #666666 248 2 1 c #666666 248 6 1 c #666666 249 3 1 c #666666 249 11 1 c #666666 250 2 1 c #666666 250 3 1 c #666666 250 11 1 c #666666 251 1 1 c #666666 251 6 1 c #666666 252 1 1 c #666666 252 2 1 c #666666 252 6 1 c #666666 253 2 1 c #666666 253 3 1 c #666666 253 6 1 c #666666 254 1 1 c #666666 254 2 1 c #666666 254 3 1 c #666666 255 2 1 c #666666 255 11 1 c #666666 256 3 1 c #666666 256 5 1 c #666666 256 11 1 c #666666 257 1 1 c #666666 257 11 1 c #666666 258 1 1 c #666666 258 2 1 c #666666 258 6 1 c #666666 259 1 1 c #666666 259 2 1 c #666666 259 3 1 c #666666 260 1 1 c #666666 260 5 1 c #666666 260 6 1 c #666666 261 2 1 c #666666 261 3 1 c #666666 261 5 1 c #666666 262 2 1 c #666666 262 3 1 c #666666 262 6 1 c #666666 263 2 1 c #666666 263 6 1 c #666666 264 1 1 c #666666 264 6 1 c #666666 265 1 1 c #666666 265 2 1 c #666666 265 3 1 c #666666 266 2 1 c #666666 266 3 1 c #666666 266 11 1 c #666666 267 1 1 c #666666 267 2 1 c #666666 267 5 1 c #666666 268 1 1 c #666666 268 3 1 c #666666 268 11 1 c #666666 269 1 1 c #666666 270 1 1 c #666666 270 3 1 c #666666 271 1 1 c #666666 271 2 1 c #666666 271 11 1 c #666666 272 2 1 c #666666 272 5 1 c #666666 272 6 1 c #666666 273 1 1 c #666666 273 3 1 c #666666 274 2 1 c #666666 274 11 1 c #666666 275 1 1 c #666666 275 3 1 c #666666 275 11 1 c #666666 276 2 1 c #666666 276 3 1 c #666666 276 6 1 c #666666 277 1 1 c #666666 277 2 1 c #666666 277 3 1 c #666666 278 1 1 c #666666 278 2 1 c #666666 278 3 1 c #666666 279 1 1 c #666666 279 3 1 c #666666 279 6 1 c #666666 280 1 1 c #666666 280 2 1 c #666666 281 2 1 c #666666 281 3 1 c #666666 282 1 1 c #666666 282 6 1 c #666666 283 1 1 c #666666 283 2 1 c #666666 283 3 1 c #666666 284 1 1 c #666666 284 3 1 c #666666 285 1 1 c #666666 285 11 1 c #666666 286 1 1 c #666666 286 3 1 c #666666 286 6 1 c #666666 287 1 1 c #666666 287 3 1 c #666666 288 1 1 c #666666 288 11 1 c #666666 289 3 1 c #666666 289 6 1 c #666666 290 1 1 c #666666 290 6 1 c #666666 290 11 1 c #666666 291 2 1 c #666666 291 3 1 c #666666 292 2 1 c #666666 292 3 1 c #666666 292 5 1 c #666666 293 1 1 c #666666 293 3 1 c #666666 293 11 1 c #666666 294 2 1 c #666666 294 5 1 c #666666 295 1 1 c #666666 295 3 1 c #666666 295 6 1 c #666666 296 1 1 c #666666 296 2 1 c #666666 297 1 1 c #666666 297 2 1 c #666666 297 5 1 c #666666 298 2 1 c #666666 298 6 1 c #666666 299 1 1 c #666666 299 3 1 c #666666 300 2 1 c #666666 301 1 1 c #666666 301 6 1 c #666666 302 1 1 c #666666 302 2 1 c #666666 302 5 1 c #666666 303 1 1 c #666666 303 2 1 c #666666 303 6 1 c #666666 304 1 1 c #666666 304 2 1 c #666666 304 6 1 c #666666 305 1 1 c #666666 305 2 1 c #666666 306 2 1 c #666666 306 3 1 c #666666 306 6 1 c #666666 307 1 1 c #666666 307 2 1 c #666666 308 1 1 c #666666 308 2 1 c #666666 308 11 1 c #666666 309 1 1 c #666666 309 5 1 c #666666 309 6 1 c #666666 310 1 1 c #666666 310 6 1 c #666666 311 1 1 c #666666 311 2 1 c #666666 312 1 1 c #666666 312 2 1 c #666666 312 6 1 c #666666 313 1 1 c #666666 313 2 1 c #666666 314 1 1 c #666666 314 3 1 c #666666 314 6 1 c #666666 315 1 1 c #666666 315 6 1 c #666666 315 11 1 c #666666 316 1 1 c #666666 316 2 1 c #666666 316 3 1 c #666666 317 1 1 c #666666 317 3 1 c #666666 317 6 1 c #666666 318 2 1 c #666666 318 3 1 c #666666 318 11 1 c #666666 319 2 1 c #666666 319 3 1 c #666666 320 2 1 c #666666 320 3 1 c #666666 320 6 1 c #666666 321 2 1 c #666666 321 5 1 c #666666 321 6 1 c #666666 322 1 1 c #666666 322 2 1 c #666666 323 1 1 c #666666 323 2 1 c #666666 324 1 1 c #666666 324 6 1 c #666666 325 1 1 c #666666 326 1 1 c #666666 326 6 1 c #666666 327 1 1 c #666666 327 3 1 c #666666 327 6 1 c #666666 328 1 1 c #666666 328 6 1 c #666666 328 11 1 c #666666 329 1 1 c #666666 329 2 1 c #666666 330 1 1 c #666666 330 2 1 c #666666 331 1 1 c #666666 331 2 1 c #666666 332 1 1 c #666666 332 2 1 c #666666 333 2 1 c #666666 333 3 1 c #666666 333 11 1 c #666666 334 2 1 c #666666 334 3 1 c #666666 334 11 1 c #666666 335 1 1 c #666666 335 2 1 c #666666 335 6 1 c #666666 336 2 1 c #666666 336 6 1 c #666666 337 2 1 c #666666 337 6 1 c #666666 338 3 1 c #666666 338 6 1 c #666666 338 11 1 c #666666 339 1 1 c #666666 339 2 1 c #666666 339 6 1 c #666666 340 1 1 c #666666 340 3 1 c #666666 340 6 1 c #666666 341 5 1 c #666666 341 6 1 c #666666 342 2 1 c #666666 343 1 1 c #666666 344 1 1 c #666666 344 2 1 c #666666 344 3 1 c #666666 345 1 1 c #666666 345 2 1 c #666666 345 6 1 c #666666 346 1 1 c #666666 346 11 1 c #666666 347 2 1 c #666666 347 6 1 c #666666 348 2 1 c #666666 348 5 1 c #666666 348 11 1 c #666666 349 1 1 c #666666 349 2 1 c #666666 349 11 1 c #666666 350 1 1 c #666666 350 3 1 c #666666 351 2 1 c #666666 351 6 1 c #666666 351 11 1 c #666666 352 2 1 c #666666 352 3 1 c #666666 352 11 1 c #666666 353 1 1 c #666666 353 2 1 c #666666 354 1 1 c #666666 354 2 1 c #666666 355 1 1 c #666666 355 11 1 c #666666 356 2 1 c #666666 356 3 1 c #666666 356 6 1 c #666666 357 2 1 c #666666 357 3 1 c #666666 358 2 1 c #666666 358 5 1 c #666666 358 11 1 c #666666 359 2 1 c #666666 359 11 1 c #666666 360 1 1 c #666666 360 3 1 c #666666 361 1 1 c #666666 361 2 1 c #666666 362 2 1 c #666666 362 3 1 c #666666 363 1 1 c #666666 363 6 1 c #666666 363 11 1 c #666666 364 3 1 c #666666 364 5 1 c #666666 364 6 1 c #666666 365 1 1 c #666666 365 2 1 c #666666 365 5 1 c #666666 366 2 1 c #666666 366 3 1 c #666666 367 1 1 c #666666 367 2 1 c #666666 367 11 1 c #666666 368 1 1 c #666666 368 6 1 c #666666 369 1 1 c #666666 369 6 1 c #666666 370 2 1 c #666666 370 3 1 c #666666 370 6 1 c #666666 371 1 1 c #666666 371 2 1 c #666666 371 6 1 c #666666 372 1 1 c #666666 372 2 1 c #666666 372 6 1 c #666666 373 1 1 c #666666 373 2 1 c #666666 374 1 1 c #666666 374 5 1 c #666666 374 11 1 c #666666 375 2 1 c #666666 375 6 1 c #666666 376 2 1 c #666666 376 5 1 c #666666 376 6 1 c #666666 377 1 1 c #666666 377 2 1 c #666666 378 1 1 c #666666 378 11 1 c #666666 379 1 1 c #666666 379 6 1 c #666666 379 11 1 c #666666 380 1 1 c #666666 380 2 1 c #666666 381 1 1 c #666666 381 2 1 c #666666 381 6 1 c #666666 382 1 1 c #666666 382 2 1 c #666666 383 1 1 c #666666 383 2 1 c #666666 384 1 1 c #666666 384 2 1 c #666666 384 6 1 c #666666 385 3 1 c #666666 385 6 1 c #666666 385 11 1 c #666666 386 1 1 c #666666 386 3 1 c #666666 386 11 1 c #666666 387 2 1 c #666666 387 6 1 c #666666 388 2 1 c #666666 388 11 1 c #666666 389 1 1 c #666666 389 11 1 c #666666 390 1 1 c #666666 390 3 1 c #666666 391 1 1 c #666666 391 2 1 c #666666 391 3 1 c #666666 392 3 1 c #666666 392 6 1 c #666666 393 6 1 c #666666 393 11 1 c #666666 394 2 1 c #666666 394 6 1 c #666666 394 11 1 c #666666 395 1 1 c #666666 395 2 1 c #666666 395 6 1 c #666666 396 1 1 c #666666 396 2 1 c #666666 396 6 1 c #666666 397 2 1 c #666666 397 11 1 c #666666 398 2 1 c #666666 398 6 1 c #666666 399 2 1 c #666666 399 3 1 c #666666 399 6 1 c #666666 400 1 1 c #666666 400 11 1 c #666666 401 2 1 c #666666 401 6 1 c #666666 402 1 1 c #666666 402 2 1 c #666666 402 3 1 c #666666 403 1 1 c #666666 403 2 1 c #666666 403 11 1 c #666666 404 1 1 c #666666 405 1 1 c #666666 405 2 1 c #666666 405 3 1 c #666666 406 1 1 c #666666 406 2 1 c #666666 406 3 1 c #666666 407 1 1 c #666666 407 2 1 c #666666 407 6 1 c #666666 408 1 1 c #666666 408 2 1 c #666666 408 3 1 c #666666 409 2 1 c #666666 409 6 1 c #666666 409 11 1 c #666666 410 1 1 c #666666 410 2 1 c #666666 410 5 1 c #666666 411 1 1 c #666666 411 3 1 c #666666 411 6 1 c #666666 412 2 1 c #666666 412 3 1 c #666666 412 6 1 c #666666 413 1 1 c #666666 413 2 1 c #666666 414 1 1 c #666666 414 2 1 c #666666 415 1 1 c #666666 415 2 1 c #666666 415 6 1 c #666666 416 2 1 c #666666 416 6 1 c #666666 416 11 1 c #666666 417 2 1 c #666666 417 3 1 c #666666 417 11 1 c #666666 418 1 1 c #666666 418 11 1 c #666666 419 1 1 c #666666 419 3 1 c #666666 419 6 1 c #666666 420 1 1 c #666666 420 2 1 c #666666 421 1 1 c #666666 421 6 1 c #666666 422 2 1 c #666666 422 3 1 c #666666 422 6 1 c #666666 423 1 1 c #666666 423 3 1 c #666666 424 1 1 c #666666 424 6 1 c #666666 425 1 1 c #666666 425 2 1 c #666666 425 6 1 c #666666 426 1 1 c #666666 426 2 1 c #666666 427 1 1 c #666666 427 11 1 c #666666 428 3 1 c #666666 428 6 1 c #666666 428 11 1 c #666666 429 1 1 c #666666 429 2 1 c #666666 429 11 1 c #666666 430 2 1 c #666666 430 3 1 c #666666 431 2 1 c #666666 431 3 1 c #666666 431 6 1 c #666666 432 2 1 c #666666 432 3 1 c #666666 432 6 1 c #666666 433 1 1 c #666666 433 2 1 c #666666 434 2 1 c #666666 434 3 1 c #666666 435 1 1 c #666666 435 6 1 c #666666 436 1 1 c #666666 436 2 1 c #666666 436 3 1 c #666666 437 2 1 c #666666 437 6 1 c #666666 437 11 1 c #666666 438 1 1 c #666666 438 6 1 c #666666 439 1 1 c #666666 439 2 1 c #666666 439 6 1 c #666666 440 3 1 c #666666 440 6 1 c #666666 441 2 1 c #666666 441 6 1 c #666666 442 2 1 c #666666 443 5 1 c #666666 443 6 1 c #666666 443 11 1 c #666666 444 1 1 c #666666 444 2 1 c #666666 444 3 1 c #666666 445 2 1 c #666666 445 3 1 c #666666 445 6 1 c #666666 446 2 1 c #666666 446 3 1 c #666666 446 11 1 c #666666 447 2 1 c #666666 447 6 1 c #666666 447 11 1 c #666666 448 2 1 c #666666 448 3 1 c #666666 448 5 1 c #666666 449 1 1 c #666666 449 6 1 c #666666 449 11 1 c #666666 450 3 1 c #666666 450 6 1 c #666666 451 1 1 c #666666 451 2 1 c #666666 451 6 1 c #666666 452 1 1 c #666666 452 2 1 c #666666 452 6 1 c #666666 453 1 1 c #666666 453 2 1 c #666666 453 6 1 c #666666 454 1 1 c #666666 454 2 1 c #666666 455 1 1 c #666666 455 3 1 c #666666 456 2 1 c #666666 456 3 1 c #666666 456 6 1 c #666666 457 1 1 c #666666 457 3 1 c #666666 457 6 1 c #666666 458 2 1 c #666666 458 3 1 c #666666 459 1 1 c #666666 459 6 1 c #666666 460 1 1 c #666666 460 2 1 c #666666 461 1 1 c #666666 461 2 1 c #666666 461 3 1 c #666666 462 1 1 c #666666 462 3 1 c #666666 462 5 1 c #666666 463 1 1 c #666666 463 11 1 c #666666 464 1 1 c #666666 464 6 1 c #666666 465 1 1 c #666666 465 2 1 c #666666 466 1 1 c #666666 466 11 1 c #666666 467 1 1 c #666666 467 6 1 c #666666 468 1 1 c #666666 468 5 1 c #666666 469 1 1 c #666666 469 2 1 c #666666 469 3 1 c #666666 470 2 1 c #666666 470 3 1 c #666666 470 11 1 c #666666 471 1 1 c #666666 471 3 1 c #666666 471 6 1 c #666666 472 1 1 c #666666 472 3 1 c #666666 473 1 1 c #666666 473 2 1 c #666666 473 6 1 c #666666 474 6 1 c #666666 474 11 1 c #666666 475 2 1 c #666666 475 6 1 c #666666 475 11 1 c #666666 476 1 1 c #666666 476 5 1 c #666666 477 1 1 c #666666 477 2 1 c #666666 477 6 1 c #666666 478 2 1 c #666666 478 3 1 c #666666 478 6 1 c #666666 479 1 1 c #666666 479 2 1 c #666666 480 1 1 c #666666 480 6 1 c #666666 481 1 1 c #666666 481 5 1 c #666666 481 11 1 c #666666 482 1 1 c #666666 482 2 1 c #666666 482 3 1 c #666666 483 1 1 c #666666 483 2 1 c #666666 484 1 1 c #666666 484 2 1 c #666666 484 6 1 c #666666 485 1 1 c #666666 485 2 1 c #666666 486 1 1 c #666666 486 5 1 c #666666 487 2 1 c #666666 487 3 1 c #666666 488 2 1 c #666666 488 11 1 c #666666 489 2 1 c #666666 489 5 1 c #666666 490 1 1 c #666666 490 11 1 c #666666 491 1 1 c #666666 491 6 1 c #666666 491 11 1 c #666666 492 1 1 c #666666 492 6 1 c #666666 493 1 1 c #666666 493 2 1 c #666666 493 11 1 c #666666 494 1 1 c #666666 494 2 1 c #666666 495 2 1 c #666666 496 2 1 c #666666 496 3 1 c #666666 497 1 1 c #666666 497 2 1 c #666666 497 11 1 c #666666 498 1 1 c #666666 498 2 1 c #666666 498 6 1 c #666666 499 1 1 c #666666 499 11 1 c #666666 500 1 1 c #666666 500 6 1 c #666666 *Edges SocNetV-3.7/src/data/Bernard_Killworth_Fraternity.dl000066400000000000000000000405331524402210300225460ustar00rootroot00000000000000DL N=58 NM=2 FORMAT = FULLMATRIX DIAGONAL PRESENT LEVEL LABELS: BKFRAB BKFRAC DATA: 0 0 2 1 0 0 2 0 0 0 1 1 2 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 2 1 1 1 0 2 1 2 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 1 1 4 1 1 0 0 10 0 0 2 1 0 2 0 0 0 6 2 0 1 0 0 0 1 0 10 2 0 4 0 3 0 1 1 0 0 0 0 5 1 0 4 0 0 0 0 0 1 1 0 0 5 3 0 0 0 0 1 0 1 4 0 2 10 0 6 11 14 15 4 12 0 5 4 3 8 10 8 11 0 2 19 2 15 1 2 6 1 5 0 12 5 4 0 1 4 15 3 1 3 6 0 2 3 0 9 8 2 1 3 6 2 0 2 2 16 4 5 19 1 1 0 6 0 2 3 9 1 8 0 0 5 0 0 2 4 3 2 2 6 0 1 1 3 1 0 5 1 1 3 0 1 1 4 1 0 1 3 2 0 1 0 0 1 1 1 1 2 1 3 0 0 2 1 2 2 3 5 0 0 11 2 0 2 8 1 1 1 0 0 2 0 1 1 0 0 0 3 0 0 0 0 0 0 8 0 1 5 0 0 1 0 0 0 0 0 9 2 1 0 1 8 25 0 0 0 0 0 0 0 1 2 0 0 4 0 0 2 14 3 2 0 30 2 8 0 4 4 1 6 2 14 9 0 1 51 0 3 2 1 0 1 6 0 3 11 2 0 15 5 3 1 0 2 2 1 3 1 0 3 2 2 6 1 3 4 0 2 8 9 3 2 18 2 2 1 15 9 8 30 0 10 4 2 7 3 0 12 9 10 9 2 3 40 2 2 5 2 0 1 19 1 10 14 5 3 14 7 7 5 3 4 5 7 8 5 0 2 4 7 3 7 7 2 0 0 6 5 14 16 20 4 0 0 4 1 1 2 10 0 3 0 2 0 1 3 3 3 5 0 0 6 1 0 2 3 0 1 6 0 2 0 9 1 0 1 2 4 2 5 1 0 3 5 0 0 5 0 1 3 1 1 0 1 2 5 0 2 4 2 0 2 12 8 1 8 4 3 0 0 5 5 2 2 4 5 6 1 0 5 0 5 0 3 3 3 3 1 2 3 1 0 2 4 4 3 5 1 2 0 1 1 1 2 0 0 4 0 1 4 0 6 1 4 3 2 7 1 0 0 0 0 1 0 2 0 0 0 0 0 0 0 1 2 0 0 0 0 0 0 0 0 0 0 6 0 1 0 1 0 0 0 0 0 0 1 2 2 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 1 0 5 0 0 4 7 2 5 0 0 0 0 1 3 3 5 3 0 7 4 1 0 3 0 0 4 0 5 1 3 0 0 2 2 3 5 3 2 0 0 1 0 2 1 4 5 2 1 0 0 0 0 4 6 6 12 0 1 0 4 5 0 4 3 0 5 0 0 0 0 0 0 0 0 0 0 3 0 1 0 1 1 0 0 0 2 0 2 0 1 2 3 2 2 1 0 0 0 1 0 1 1 1 0 0 1 2 0 0 1 2 0 7 3 3 2 6 3 0 2 1 0 1 2 0 0 0 0 2 1 3 3 0 1 0 0 6 2 0 0 0 3 0 1 0 0 0 1 1 1 0 0 1 1 1 1 1 1 0 2 1 0 0 2 0 0 0 2 4 1 0 0 0 0 2 8 0 0 6 12 3 2 0 1 0 2 0 3 8 11 1 4 8 0 1 0 0 1 1 4 0 8 4 6 0 3 1 5 1 1 0 0 0 1 3 0 2 2 1 1 1 0 0 0 0 1 0 2 1 5 1 0 0 10 2 1 2 9 3 4 1 3 0 1 3 0 9 14 0 6 9 0 2 1 2 1 0 4 0 3 0 2 1 1 4 2 3 0 6 1 0 7 1 0 7 1 1 0 0 1 1 0 0 7 6 4 9 4 0 0 1 8 4 1 14 10 3 5 2 3 0 3 8 9 0 26 3 1 12 0 2 0 0 1 0 7 0 5 6 5 4 2 2 2 2 0 4 4 0 2 5 1 3 2 1 1 4 0 2 0 0 8 4 2 0 11 3 1 0 11 3 0 9 9 5 6 0 5 0 3 11 14 26 0 3 0 9 0 1 0 0 1 0 5 0 5 2 2 4 2 1 4 2 0 1 1 1 2 3 0 3 1 0 0 3 1 2 0 0 7 7 4 0 11 0 0 0 0 2 0 0 2 0 1 0 3 0 0 1 0 3 3 0 0 0 3 0 0 0 0 0 0 0 1 0 0 3 0 1 1 1 1 0 1 0 0 0 0 1 0 2 0 2 0 0 0 0 0 0 2 1 0 1 1 0 2 2 0 1 3 0 0 0 0 0 1 4 6 1 0 0 0 5 0 0 2 1 3 0 0 0 0 1 1 0 0 1 1 1 1 2 0 1 14 1 0 1 0 0 1 0 3 0 0 0 1 0 0 3 1 2 0 1 19 6 3 51 40 6 5 0 7 3 0 8 9 12 9 0 5 0 3 2 3 2 1 1 7 1 10 6 6 1 13 12 9 2 1 6 2 1 10 4 0 2 2 1 2 1 6 1 0 0 12 17 11 9 23 5 0 0 2 0 0 0 2 1 0 0 4 0 0 0 0 0 0 3 0 3 0 0 1 0 0 0 0 0 2 0 2 0 0 1 1 1 0 1 0 0 1 1 0 0 0 5 0 1 1 0 0 0 0 1 2 4 2 1 1 10 15 1 0 3 2 0 5 0 1 1 6 1 2 2 1 0 0 2 0 0 1 1 7 2 1 0 3 1 0 0 0 0 1 1 1 0 2 0 0 0 0 1 0 3 0 0 2 1 0 0 0 2 1 1 3 0 0 2 1 1 0 2 5 2 0 0 0 0 2 0 1 0 0 0 2 3 1 1 0 0 1 0 1 0 2 0 2 0 3 1 2 1 2 2 2 1 7 1 0 1 2 0 2 0 11 1 1 0 1 4 1 2 3 1 0 0 2 3 0 1 2 3 3 0 3 1 0 0 2 0 0 0 1 2 0 1 0 0 0 1 0 0 1 1 1 0 0 2 1 1 0 2 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 2 1 1 0 4 6 1 0 0 0 0 3 0 0 1 0 1 1 1 1 0 3 1 0 7 1 0 0 0 0 0 3 1 0 0 0 0 3 0 1 1 0 0 4 0 0 1 0 0 0 0 0 0 0 0 2 1 1 1 5 0 0 0 1 0 0 1 1 1 3 0 0 0 0 1 0 0 0 0 0 1 0 2 0 1 0 0 1 0 0 1 0 0 0 0 1 0 0 1 3 0 0 0 0 0 1 0 0 1 2 0 0 2 0 1 1 1 2 0 0 3 5 5 8 6 19 6 3 6 4 0 3 4 4 7 5 0 0 7 0 1 1 0 0 1 0 0 6 6 2 1 1 4 0 1 0 2 4 0 3 2 1 1 4 1 0 5 2 0 0 0 1 2 2 4 6 2 0 0 0 1 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 2 1 12 1 1 3 10 2 2 1 5 2 1 8 3 5 5 1 0 10 2 3 2 1 3 0 6 0 0 3 1 0 0 0 20 2 2 3 3 2 1 2 0 3 3 0 1 1 1 1 0 0 0 7 1 2 10 1 1 1 5 3 5 11 14 0 3 0 1 0 0 4 0 6 2 0 1 6 0 1 0 1 1 1 6 0 3 0 3 0 1 0 6 1 1 1 3 1 4 1 2 0 1 0 5 1 3 1 0 0 3 2 1 6 10 2 1 0 4 0 0 2 5 9 1 1 3 2 0 6 2 5 2 0 1 6 2 0 2 1 0 0 2 0 1 3 0 4 0 3 1 3 0 1 0 1 3 3 0 0 1 3 0 2 1 0 0 0 1 4 1 1 3 2 1 0 0 1 0 0 3 1 0 0 0 0 0 0 1 4 4 3 0 1 0 0 0 0 0 0 1 0 0 0 4 0 0 2 0 0 0 0 0 0 1 0 0 0 0 3 0 6 0 0 0 0 0 0 0 0 0 1 0 0 1 1 1 15 14 0 2 0 0 1 1 3 1 2 2 0 0 13 0 0 3 0 0 0 1 0 0 1 0 0 0 1 1 1 0 0 0 3 1 0 0 0 0 0 0 0 1 0 0 2 8 1 0 1 3 0 2 0 4 4 0 5 7 1 4 0 2 2 1 1 4 2 1 1 1 12 1 0 1 2 0 0 4 1 0 0 3 2 1 0 3 1 0 0 1 1 2 1 0 0 0 3 2 2 1 3 0 0 2 4 3 4 3 6 1 5 15 1 0 3 7 2 4 0 2 3 1 5 2 2 4 1 1 9 1 1 2 1 3 1 0 0 20 6 1 0 1 3 0 2 1 3 2 2 3 4 2 2 0 0 1 0 6 1 0 0 1 12 2 3 6 2 2 1 3 0 0 1 5 4 3 0 3 2 0 1 3 2 2 1 1 2 1 1 1 1 0 0 1 0 2 1 3 0 1 1 2 0 0 0 1 0 1 2 0 1 0 3 0 0 3 0 0 0 1 0 2 10 1 1 0 0 1 1 0 0 3 2 5 0 5 2 0 1 0 0 0 1 1 1 0 1 2 0 1 0 0 0 2 1 0 0 0 0 1 0 0 0 3 0 1 0 0 0 1 0 4 0 2 0 1 0 2 1 0 1 3 0 0 4 3 3 0 2 4 5 1 1 3 1 1 0 6 4 1 0 2 6 1 0 2 2 1 1 2 0 3 1 1 0 0 0 3 0 0 0 0 1 2 1 0 0 1 0 2 0 0 1 0 0 1 6 1 1 4 2 0 0 6 2 9 2 5 1 2 2 2 0 1 0 1 4 1 1 0 2 0 2 2 0 0 3 4 0 3 3 0 0 0 1 2 1 3 0 0 1 0 0 0 4 9 2 1 2 5 4 3 0 0 2 2 1 2 0 0 0 0 0 2 1 7 0 0 2 0 0 1 0 0 0 1 0 1 1 0 0 1 0 0 0 0 0 2 1 1 0 3 1 2 0 0 1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 2 0 0 2 0 1 0 2 1 1 3 8 3 1 0 0 0 1 1 7 2 2 0 14 10 1 0 7 0 4 0 3 0 1 4 3 1 1 2 3 1 1 2 0 0 0 1 1 1 1 0 0 0 9 0 0 0 4 1 1 5 1 2 0 0 3 0 0 1 5 5 1 0 1 1 1 3 1 5 3 0 1 4 1 0 1 0 0 0 2 0 2 1 3 0 0 1 4 2 0 1 0 0 1 0 1 1 1 1 1 1 1 0 0 0 2 1 1 0 3 1 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 2 0 0 0 0 2 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 9 1 8 3 2 0 2 0 2 1 0 2 7 3 3 1 1 2 0 1 1 1 1 0 1 0 3 0 0 0 0 0 2 1 0 0 4 0 1 1 0 0 2 0 1 0 2 1 0 0 0 2 1 2 3 0 0 1 8 1 25 2 4 5 0 0 1 1 2 2 1 2 1 0 0 2 0 0 2 0 0 1 4 0 3 1 1 0 0 0 0 0 1 1 9 0 1 1 0 2 0 0 1 2 4 1 1 0 0 4 0 0 1 0 0 0 2 1 0 2 7 0 0 1 4 1 1 1 1 1 0 2 0 1 5 3 0 1 0 0 1 0 0 0 3 3 0 3 0 3 0 0 2 0 0 1 0 0 0 0 0 5 1 0 0 0 0 0 1 2 4 1 0 0 1 1 0 6 3 1 4 0 5 0 0 1 0 1 0 0 1 2 0 0 2 0 0 0 0 0 1 5 0 0 0 2 1 0 4 2 1 1 0 1 0 1 1 0 0 1 2 0 2 0 3 0 0 2 6 1 0 5 3 2 0 1 7 3 0 0 2 0 0 1 0 4 3 2 0 1 1 0 0 1 0 1 5 0 1 1 2 6 0 2 0 0 0 0 2 0 0 1 0 0 2 5 1 0 3 2 0 0 0 2 1 0 2 0 1 3 6 1 0 3 7 1 1 0 1 1 2 0 1 0 1 0 3 6 1 2 11 0 0 2 2 0 1 3 1 0 1 1 6 3 2 0 5 0 9 1 0 2 4 1 2 3 0 4 0 1 4 4 2 2 3 1 0 0 2 3 0 4 2 1 4 0 0 2 0 0 1 2 2 0 0 1 0 1 1 0 0 0 0 0 1 1 0 0 0 3 1 0 0 1 4 0 0 0 0 1 1 0 0 2 4 0 1 0 0 1 1 1 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 3 0 0 0 0 0 1 0 2 0 0 1 0 0 0 0 0 0 0 0 0 0 2 0 0 2 0 1 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 2 2 1 8 6 2 1 0 0 1 2 1 7 8 7 0 1 12 0 0 1 0 2 0 1 1 0 3 1 0 8 2 1 1 2 1 0 1 4 2 0 0 0 0 3 0 4 0 0 0 0 5 1 2 4 3 1 1 16 1 2 9 5 5 4 1 4 2 4 0 6 4 7 0 0 17 1 2 4 0 1 1 2 0 7 2 4 0 1 4 12 0 1 6 2 2 1 1 0 2 4 0 0 2 4 1 0 0 5 0 5 3 10 0 1 0 4 2 0 3 14 0 3 0 6 0 1 2 4 2 4 2 0 11 2 1 1 0 1 1 2 0 1 1 1 0 0 3 2 2 0 1 2 0 1 1 1 1 0 1 0 1 2 1 0 0 1 5 0 12 7 1 4 1 5 2 0 2 16 2 2 0 6 7 0 1 9 0 0 1 3 9 4 1 2 2 1 1 4 0 2 6 1 0 1 4 3 10 1 1 1 0 5 0 0 2 0 2 2 0 2 1 0 0 2 3 12 0 12 0 1 4 19 3 4 18 20 4 7 0 12 3 0 5 4 11 11 0 1 23 2 3 3 1 5 2 6 0 10 10 3 0 3 3 6 1 3 4 2 2 1 3 0 3 1 4 6 2 3 0 0 0 4 10 7 12 0 1 1 0 1 5 0 2 4 2 1 0 0 3 0 1 0 3 0 1 2 5 1 0 1 1 0 0 2 0 1 2 2 1 0 6 2 1 0 2 0 0 2 1 0 0 0 1 1 0 1 3 0 0 3 0 1 0 1 0 0 4 4 5 4 4 5 5 4 5 5 4 4 5 5 4 4 5 5 5 5 4 5 4 4 5 5 4 4 5 5 5 4 4 4 5 4 5 4 4 5 4 5 5 4 5 5 5 4 5 4 5 4 4 5 5 5 5 3 0 2 3 4 2 2 2 3 2 3 3 5 4 3 3 3 2 3 3 2 5 4 2 5 3 2 2 3 4 4 2 4 2 5 3 3 5 3 2 3 3 3 2 4 2 4 3 3 3 2 3 4 4 2 3 2 2 2 2 0 4 5 4 4 1 4 3 3 3 3 3 4 4 4 2 2 5 2 5 3 1 4 3 3 2 4 5 4 2 3 3 5 1 2 3 3 1 2 4 4 3 4 2 2 2 2 2 1 3 2 5 3 2 5 2 4 4 5 0 5 5 4 3 5 4 4 5 3 4 4 4 4 3 5 5 3 4 5 5 4 4 5 4 4 4 5 4 5 5 4 3 3 4 4 3 5 4 4 4 4 3 4 4 5 4 2 4 5 5 4 4 5 5 2 3 5 5 0 3 2 3 3 4 4 1 4 2 4 2 2 1 2 2 1 3 4 1 2 5 4 1 3 1 3 1 1 1 1 1 2 2 5 4 2 2 2 5 5 1 3 2 5 2 3 2 1 2 2 2 4 1 3 2 5 5 2 0 5 3 4 3 4 3 3 5 4 5 5 2 2 5 3 2 3 1 1 1 3 1 4 4 4 1 4 3 3 2 1 2 2 2 2 3 1 2 2 3 5 3 3 3 1 2 4 4 3 3 5 4 2 1 3 4 2 5 0 2 2 2 2 2 1 5 5 5 4 3 3 5 3 2 2 2 2 2 3 1 4 3 3 2 5 4 3 4 2 2 2 2 3 2 2 3 2 2 2 2 3 3 1 2 3 3 3 4 5 3 5 3 3 3 2 2 3 0 3 3 4 3 3 3 3 3 2 3 3 3 2 2 3 5 2 2 3 2 2 3 5 4 3 5 3 4 3 3 3 2 3 2 2 3 2 3 2 3 4 3 2 2 3 2 2 3 3 4 2 2 5 4 2 3 3 2 0 1 3 5 2 3 3 3 3 1 3 4 2 4 3 3 4 5 2 1 3 3 2 2 3 3 4 3 3 2 2 2 2 2 2 2 2 2 4 2 3 2 1 5 2 4 2 3 4 4 3 3 5 4 4 4 3 3 2 0 3 4 2 4 5 4 4 1 2 4 2 4 2 2 3 2 5 1 5 2 3 1 2 3 4 1 1 2 4 5 3 3 2 1 3 4 1 3 2 4 1 2 2 5 4 1 3 3 3 3 3 3 3 3 3 3 3 3 0 3 3 3 3 3 3 3 3 3 5 3 3 3 3 3 3 3 3 3 5 5 3 3 3 3 3 3 3 3 3 3 3 3 3 5 3 5 3 3 2 3 3 3 3 3 3 3 2 3 4 4 2 3 2 2 5 3 3 0 2 3 2 3 3 2 3 4 2 4 2 2 3 3 3 1 2 2 3 1 2 4 3 4 2 2 4 2 2 2 3 2 1 3 1 2 3 2 2 2 2 4 2 4 3 4 2 5 4 2 4 3 2 2 2 2 2 2 0 3 2 3 2 2 2 3 1 5 3 2 5 2 3 2 4 3 3 2 3 3 3 3 2 4 4 5 2 4 3 4 4 2 3 2 3 3 3 2 3 4 3 3 3 2 3 2 5 4 3 5 5 2 4 3 3 3 3 0 4 5 4 3 4 4 3 3 2 2 3 3 4 2 5 3 4 3 4 3 4 3 2 3 4 3 4 4 3 4 4 3 3 3 3 3 2 2 4 4 4 4 5 3 2 1 3 3 4 4 5 2 3 2 2 1 1 3 0 4 5 1 5 2 2 2 3 2 1 1 4 1 3 2 3 3 3 4 4 3 1 4 2 1 5 3 1 5 3 2 2 1 4 1 1 1 3 3 3 5 5 5 3 3 4 4 3 5 5 2 3 2 3 3 4 4 5 0 5 3 5 5 2 3 2 2 2 1 2 2 4 4 3 2 3 3 3 3 2 3 4 3 4 5 3 3 4 4 3 4 3 3 2 1 5 4 2 4 3 3 2 2 5 3 3 4 4 2 3 3 3 3 2 4 5 5 0 2 4 5 2 3 2 2 3 1 2 3 4 3 2 2 4 2 5 2 1 4 3 2 3 5 3 5 3 2 2 3 3 3 1 1 4 5 3 4 5 3 5 3 2 5 3 3 5 5 3 2 5 5 4 4 3 5 4 0 2 1 5 3 3 3 3 2 2 1 2 5 5 5 3 5 4 4 2 2 3 3 3 3 3 5 5 5 2 5 3 5 2 1 2 3 4 5 5 5 3 3 4 4 2 2 3 2 4 3 3 3 3 4 5 5 5 2 0 4 2 4 5 4 5 2 3 3 4 4 3 3 4 4 2 4 4 3 1 2 5 4 3 3 3 2 4 2 5 3 3 2 3 3 3 4 4 3 3 2 4 4 2 5 5 2 3 3 3 3 2 4 3 4 3 2 3 0 2 3 3 1 1 1 2 2 3 3 3 2 5 3 3 3 2 2 2 2 3 2 2 3 2 2 1 2 3 3 1 1 5 5 3 2 5 3 3 1 2 2 1 2 3 1 2 2 5 2 1 2 2 1 1 4 1 3 0 1 2 2 2 1 1 1 1 2 5 4 1 2 2 2 1 1 1 1 2 1 1 3 1 5 2 5 3 4 1 1 1 2 2 2 3 2 2 5 5 3 3 3 2 2 4 2 2 3 4 3 3 2 3 2 3 4 3 0 3 3 5 3 3 1 4 3 3 3 3 3 4 3 2 3 3 3 4 3 3 3 3 4 3 3 4 3 1 3 2 4 3 3 4 3 2 3 4 4 5 3 4 3 4 2 2 3 2 4 4 2 2 2 5 3 1 2 0 2 4 3 2 1 3 1 2 1 4 5 2 1 4 4 5 2 5 2 1 5 5 1 5 1 5 1 4 2 5 2 3 2 4 5 4 1 2 5 2 2 3 5 4 3 4 3 2 2 3 4 2 2 4 2 1 3 2 0 2 4 4 1 1 2 5 2 2 5 2 4 1 1 1 1 4 1 2 3 1 4 4 2 2 5 1 3 1 1 3 4 2 5 2 4 5 3 3 2 2 2 3 1 3 4 4 2 2 1 3 1 4 2 2 5 4 2 0 3 2 1 3 4 2 1 2 3 3 2 2 2 3 3 2 3 4 2 2 1 1 2 3 2 1 2 2 2 1 3 4 2 3 3 4 4 4 2 2 2 5 2 3 3 2 2 2 2 2 2 3 1 2 4 3 3 4 0 2 2 2 2 2 2 2 3 4 2 4 2 2 2 2 4 4 3 4 2 3 4 3 3 3 5 2 3 2 2 3 2 4 2 5 5 5 3 5 3 1 5 5 3 1 4 4 2 1 2 1 5 2 4 3 4 1 1 0 1 5 4 5 3 4 5 4 3 1 2 1 2 1 4 1 2 3 1 1 4 2 4 1 1 4 1 2 5 5 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 0 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 3 5 3 3 3 4 2 3 4 3 2 3 5 4 4 4 1 3 3 1 4 3 1 4 1 5 2 0 2 3 2 3 2 5 3 2 4 4 3 2 4 4 3 2 1 2 1 2 1 1 2 3 5 3 3 5 2 5 2 5 5 1 3 2 3 3 1 5 1 2 2 3 3 1 1 2 5 2 2 1 3 3 1 3 1 3 0 2 2 2 3 4 2 4 3 1 1 3 4 3 3 1 1 5 2 1 2 1 2 3 3 3 3 5 3 4 2 4 4 2 4 5 5 3 3 5 3 2 3 4 3 3 3 3 4 4 3 4 5 3 2 4 2 4 2 0 5 3 4 4 4 3 3 2 2 4 4 2 3 2 4 3 4 3 4 2 4 3 4 3 4 4 3 5 3 4 4 3 4 4 4 3 3 5 3 2 4 5 4 4 5 4 5 5 4 3 4 3 2 4 2 2 5 5 0 5 5 4 3 3 3 2 2 3 3 2 4 2 5 3 5 5 5 2 2 4 4 3 4 5 5 3 3 3 5 2 5 5 2 3 2 3 4 3 4 4 4 3 3 4 5 2 2 4 2 3 3 3 4 3 3 2 3 0 4 4 3 2 3 2 3 3 3 4 2 2 2 3 2 4 3 2 3 5 4 4 4 4 4 4 3 4 5 3 4 4 4 4 3 3 3 2 2 3 3 3 4 5 5 3 2 5 5 3 4 4 2 3 4 4 4 5 0 3 4 4 3 4 3 5 3 3 3 2 4 3 3 5 5 2 4 5 3 4 5 4 5 2 4 5 3 3 4 4 2 3 4 3 4 3 3 4 2 5 2 4 4 2 4 3 2 5 3 3 2 5 4 3 2 4 2 0 2 2 4 4 3 4 4 5 3 2 1 4 1 3 3 2 2 2 5 2 3 4 2 5 1 2 5 1 4 5 1 3 3 5 5 2 5 3 4 2 3 5 5 5 2 1 1 1 1 3 3 3 2 2 2 5 4 4 0 1 1 1 1 5 3 4 4 4 5 4 5 1 2 1 2 1 3 1 5 5 4 2 3 3 4 2 4 3 3 4 3 3 4 3 3 3 3 2 2 5 3 2 3 5 2 4 4 3 2 2 3 3 2 3 4 4 2 0 4 5 2 4 3 3 4 2 2 5 2 5 3 5 3 3 3 3 2 3 3 2 5 3 3 2 3 2 2 3 3 3 3 3 3 4 4 4 2 4 4 2 2 3 2 3 2 2 3 4 3 3 3 4 2 5 3 3 0 3 2 3 4 4 3 2 3 4 3 3 3 2 2 4 4 2 3 4 3 2 2 3 4 5 3 3 2 3 4 4 5 4 4 3 5 3 2 2 4 2 5 5 2 5 4 4 2 5 2 2 2 2 3 5 2 5 2 0 3 2 5 5 3 5 2 5 2 5 3 5 2 3 4 3 3 4 3 2 3 3 1 4 3 4 2 1 2 1 2 5 3 3 3 2 2 4 4 1 2 1 2 3 2 2 1 4 1 1 1 2 3 3 3 2 2 3 0 2 3 2 3 3 1 1 1 1 2 1 5 2 4 2 2 4 2 3 2 2 5 3 4 4 2 2 4 2 2 2 4 4 3 3 2 5 5 2 4 5 2 3 2 2 2 3 4 3 2 4 3 4 4 3 3 3 2 0 2 4 3 4 2 3 2 5 3 4 3 5 2 2 4 4 5 2 1 4 1 2 3 3 2 1 1 3 1 4 4 3 5 5 1 3 3 1 2 1 1 2 2 2 1 4 3 3 1 2 2 5 1 1 4 2 1 2 0 5 2 1 1 2 1 2 1 1 1 4 4 2 3 3 1 2 3 4 3 2 2 2 1 3 2 2 2 2 3 2 3 2 1 4 2 1 2 2 2 5 4 2 2 3 3 2 1 2 1 5 3 2 3 3 2 3 4 0 1 2 1 3 1 2 1 1 2 3 4 3 3 2 1 3 2 4 4 4 3 3 2 2 1 4 2 3 4 5 3 5 5 3 3 5 4 4 2 2 2 2 4 3 2 3 2 3 2 2 3 4 3 3 2 1 3 3 0 3 3 5 3 4 2 1 1 3 2 2 3 4 2 2 3 4 4 5 2 1 1 1 3 4 3 2 4 4 4 2 1 1 1 1 2 4 1 2 3 3 1 1 1 3 1 1 1 1 1 1 1 5 3 2 1 1 3 0 1 1 1 4 1 1 1 1 1 1 1 4 1 4 2 3 1 1 2 3 3 3 4 4 3 2 3 3 3 2 4 3 4 4 3 3 2 1 1 3 1 4 3 4 5 1 2 3 4 1 2 3 2 1 1 1 4 1 0 1 5 3 3 1 2 1 3 3 4 4 1 3 3 3 3 4 4 2 2 5 1 5 3 2 2 3 3 3 1 5 3 1 3 5 2 3 3 1 2 3 5 3 2 2 2 4 3 4 3 5 2 3 2 3 5 2 1 0 1 4 2 5 2 3 1 4 3 5 1 4 4 3 3 3 3 3 3 3 4 5 3 3 2 3 4 3 4 1 4 5 3 3 1 3 4 4 2 3 3 4 5 2 2 2 3 2 2 3 2 2 3 2 3 2 5 2 0 3 4 1 2 2 3 3 3 3 2 4 4 2 5 4 4 3 2 4 3 3 4 1 4 4 4 4 1 5 4 1 3 5 3 3 3 3 2 3 2 4 3 4 5 4 3 4 3 4 3 5 2 2 5 5 2 4 2 0 5 2 3 4 2 2 3 4 5 5 4 2 5 3 4 4 4 4 5 5 3 3 3 4 3 2 2 2 5 4 3 3 4 3 3 4 3 2 3 3 2 3 5 3 2 3 3 3 3 2 3 3 4 3 5 3 5 4 0 1 3 3 1 3 5 5 5 2 2 2 3 4 2 2 2 2 5 2 2 3 3 2 2 2 2 3 1 1 1 5 1 1 4 2 1 2 2 2 2 2 1 3 1 5 2 5 2 3 1 2 2 2 1 5 1 3 2 0 1 2 2 2 1 2 1 2 2 3 3 2 2 4 2 4 2 3 4 2 3 3 2 2 3 3 2 4 4 3 3 3 4 3 2 2 2 2 2 3 3 3 2 3 2 2 4 2 2 3 3 3 3 3 3 3 3 2 0 3 3 2 3 4 3 2 2 3 5 2 5 5 1 2 1 3 2 2 4 3 4 4 1 4 5 1 1 4 1 3 1 1 5 3 5 2 2 5 4 3 1 1 3 2 1 4 4 3 1 1 1 1 1 4 2 1 1 0 3 1 2 3 4 2 3 5 3 3 5 3 2 4 3 2 4 4 2 2 3 3 1 2 5 2 5 2 1 5 2 4 3 5 3 2 1 4 2 5 3 2 4 3 4 2 4 4 3 3 2 2 2 3 4 2 2 4 0 3 4 5 3 2 2 3 2 2 3 4 2 2 3 4 2 3 3 3 2 2 4 1 5 4 3 2 2 5 1 3 1 4 2 4 1 3 3 3 2 1 2 1 1 1 2 2 2 2 3 4 2 2 4 1 1 2 5 0 2 2 2 3 1 3 4 2 5 5 3 5 3 4 5 2 4 5 5 5 1 5 3 3 3 3 3 4 2 4 1 4 4 3 3 4 4 5 5 3 1 2 4 4 3 4 4 2 5 3 5 3 5 1 4 3 5 4 0 5 5 2 1 5 5 5 3 4 1 4 1 5 2 2 3 3 4 3 1 2 5 3 2 2 1 1 1 4 1 5 5 1 1 3 2 5 5 1 2 3 2 1 3 2 3 4 2 5 1 3 2 1 2 3 3 1 5 0 5 3 2 2 5 2 4 2 3 3 3 3 4 1 3 3 3 2 3 3 2 2 2 4 4 2 2 3 1 2 3 3 2 2 5 3 2 2 2 2 2 4 2 2 2 2 2 2 2 5 4 1 3 3 3 3 3 3 0SocNetV-3.7/src/data/Campnet.paj000066400000000000000000000030371524402210300164630ustar00rootroot00000000000000*Network Campnet *Vertices 18 1 "HOLLY" ic RGBF1F5D5 0.63046 0.575472 circle 2 "BRAZEY" ic RGBF1F5D5 0.0991736 0.511006 circle 3 "CAROL" ic RGBF1F5D5 0.576151 0.43239 circle 4 "PAM" ic RGBF1F5D5 0.726092 0.371069 circle 5 "PAT" ic RGBF1F5D5 0.709563 0.5 circle 6 "JENNIE" ic RGBF1F5D5 0.876033 0.482704 circle 7 "PAULINE" ic RGBF1F5D5 0.619835 0.286164 circle 8 "ANN" ic RGBF1F5D5 0.864227 0.309748 circle 9 "MICHAEL" ic RGBF1F5D5 0.489965 0.638365 box 10 "BILL" ic RGBF1F5D5 0.475797 0.805031 box 11 "LEE" ic RGBF1F5D5 0.0885478 0.267296 box 12 "DON" ic RGBF1F5D5 0.645809 0.778302 box 13 "JOHN" ic RGBF1F5D5 0.453365 0.290881 box 14 "HARRY" ic RGBF1F5D5 0.593861 0.669811 box 15 "GERY" ic RGBF1F5D5 0.362456 0.539308 box 16 "STEVE" ic RGBF1F5D5 0.230224 0.5 box 17 "BERT" ic RGBF1F5D5 0.218418 0.245283 box 18 "RUSS" ic RGBF1F5D5 0.323495 0.29717 box *Arcs 1 4 1 c black 2 16 1 c black 2 17 1 c black 3 4 1 c black 7 5 1 c black 8 7 1 c black 9 1 1 c black 10 9 1 c black 10 12 1 c black 10 14 1 c black 13 7 1 c black 13 15 1 c black 13 18 1 c black 14 1 1 c black 15 9 1 c black 15 16 1 c black *Edges 1 4 1 c black 1 5 1 c black 1 12 1 c black 2 11 1 c black 2 16 1 c black 2 17 1 c black 3 4 1 c black 3 5 1 c black 3 7 1 c black 4 6 1 c black 4 7 1 c black 4 8 1 c black 5 6 1 c black 6 8 1 c black 9 12 1 c black 9 14 1 c black 10 12 1 c black 10 14 1 c black 11 16 1 c black 11 17 1 c black 12 14 1 c black 13 15 1 c black 13 18 1 c black 15 16 1 c black 15 18 1 c black 16 17 1 c black 16 18 1 c black 17 18 1 c blackSocNetV-3.7/src/data/ErdosRenyi_N10_E10.graphml000066400000000000000000000102471524402210300210630ustar00rootroot00000000000000 0.0 0.0 10 red circle #8d8d8d 8 1.0 #666666 1 0.504425 0.863711 circle 2 0.798673 0.162562 circle 3 0.110619 0.7422 circle 4 0.737832 0.579639 circle 5 0.299779 0.7422 circle 6 0.747788 0.487685 circle 7 0.227876 0.220033 circle 8 0.456858 0.254516 circle 9 0.382743 0.486043 circle 10 0.525442 0.536946 circle 1 1 1 1 1 1 1 1 1 1 SocNetV-3.7/src/data/Freeman_34_possible_graphs_with_N_5_multirelational.paj000066400000000000000000000047511524402210300272500ustar00rootroot00000000000000*Network "34 possible graphs of N=5" *Vertices 5 1 "1" ic red 0.221583 0.644042 circle 2 "2" ic red 0.233094 0.351433 circle 3 "3" ic red 0.696403 0.328808 circle 4 "4" ic red 0.471942 0.197587 circle 5 "5" ic red 0.726619 0.644042 circle *Matrix :1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 *Matrix :2 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 *Matrix :3 0 1 0 0 0 1 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 *Matrix :4 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 *Matrix :5 0 1 0 0 0 1 0 0 1 1 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 *Matrix :6 0 1 0 0 0 1 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 0 *Matrix :7 0 1 0 1 0 1 0 0 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 *Matrix :8 0 1 0 0 0 1 0 0 1 0 0 0 0 1 0 0 1 1 0 0 0 0 0 0 0 *Matrix :9 "star" 0 1 0 0 0 1 0 1 1 1 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 *Matrix :10 "fork" 0 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 0 *Matrix :11 "chain" 0 1 0 0 1 1 0 0 1 0 0 0 0 1 0 0 1 1 0 0 1 0 0 0 0 *Matrix :12 0 1 0 1 0 1 0 0 1 0 0 0 0 1 0 1 1 1 0 0 0 0 0 0 0 *Matrix :13 0 1 0 1 0 1 0 0 1 0 0 0 0 0 1 1 1 0 0 0 0 0 1 0 0 *Matrix :14 0 1 1 0 0 1 0 0 1 0 1 0 0 1 0 0 1 1 0 0 0 0 0 0 0 *Matrix :15 0 1 0 1 0 1 0 0 1 0 0 0 0 1 0 1 1 1 0 1 0 0 0 1 0 *Matrix :16 0 1 0 0 0 1 0 1 1 0 0 1 0 1 1 0 1 1 0 0 0 0 1 0 0 *Matrix :17 0 1 0 0 1 1 0 1 1 0 0 1 0 1 0 0 1 1 0 0 1 0 0 0 0 *Matrix :18 0 1 1 0 0 1 0 0 1 0 1 0 0 1 1 0 1 1 0 0 0 0 1 0 0 *Matrix :19 0 1 1 0 0 1 0 1 1 0 1 1 0 1 0 0 1 1 0 0 0 0 0 0 0 *Matrix :20 0 1 0 0 1 1 0 0 1 0 0 0 0 1 1 0 1 1 0 0 1 0 1 0 0 *Matrix :21 0 1 0 1 0 1 0 0 1 0 0 0 0 1 1 1 1 1 0 1 0 0 1 1 0 *Matrix :22 0 1 1 0 0 1 0 0 1 1 1 0 0 1 1 0 1 1 0 0 0 1 1 0 0 *Matrix :23 0 1 1 0 0 1 0 1 1 0 1 1 0 1 1 0 1 1 0 0 0 0 1 0 0 *Matrix :24 0 1 0 0 1 1 0 1 1 0 0 1 0 1 1 0 1 1 0 0 1 0 1 0 0 *Matrix :25 0 1 1 0 1 1 0 1 1 0 1 1 0 1 0 0 1 1 0 0 1 0 0 0 0 *Matrix :26 0 1 1 1 0 1 0 1 1 0 1 1 0 1 0 1 1 1 0 0 0 0 0 0 0 *Matrix :27 0 1 0 1 1 1 0 0 1 0 0 0 0 1 1 1 1 1 0 1 1 0 1 1 0 *Matrix :28 0 1 1 0 0 1 0 1 1 1 1 1 0 1 1 0 1 1 0 0 0 1 1 0 0 *Matrix :29 0 1 1 0 1 1 0 0 1 1 1 0 0 1 1 0 1 1 0 0 1 1 1 0 0 *Matrix :30 0 1 1 1 0 1 0 1 1 0 1 1 0 1 1 1 1 1 0 0 0 0 1 0 0 *Matrix :31 0 1 0 1 1 1 0 1 1 0 0 1 0 1 1 1 1 1 0 1 1 0 1 1 0 *Matrix :32 0 1 1 0 1 1 0 1 1 1 1 1 0 1 1 0 1 1 0 0 1 1 1 0 0 *Matrix :33 0 1 1 1 1 1 0 0 1 1 1 0 0 1 1 1 1 1 0 1 1 1 1 1 0 *Matrix :34 0 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 0 SocNetV-3.7/src/data/Freeman_EIES_network_48actors_Acquaintanceship_at_time_1.dl000066400000000000000000000123171524402210300276660ustar00rootroot00000000000000dl N=48 format=edgelist1 data: 1 2 4 1 3 2 1 6 2 1 8 2 1 10 2 1 11 2 1 13 2 1 14 2 1 18 2 1 19 2 1 20 2 1 21 2 1 22 2 1 23 2 1 24 2 1 25 2 1 26 3 1 27 2 1 31 2 1 32 2 1 33 2 1 35 2 1 36 2 1 37 2 1 38 2 1 39 3 1 40 2 1 41 2 1 42 2 1 43 2 1 44 4 1 45 2 1 46 2 2 1 4 2 3 2 2 8 1 2 11 3 2 13 3 2 14 4 2 18 1 2 19 3 2 21 2 2 22 2 2 23 2 2 24 3 2 25 2 2 27 1 2 32 2 2 33 3 2 35 2 2 37 2 2 40 2 2 41 1 2 42 2 2 43 3 2 44 4 2 45 4 2 46 2 3 1 3 3 2 1 3 6 4 3 8 1 3 13 2 3 18 2 3 19 4 3 20 4 3 22 4 3 23 1 3 24 2 3 25 2 3 26 2 3 27 1 3 31 1 3 32 2 3 33 2 3 35 2 3 36 4 3 37 2 3 39 2 3 41 1 3 42 1 3 43 1 6 1 2 6 3 2 6 8 2 6 13 2 6 14 2 6 18 2 6 19 2 6 20 2 6 21 2 6 22 2 6 23 2 6 24 1 6 27 4 6 31 1 6 32 2 6 33 2 6 35 2 6 36 2 6 37 2 6 38 2 6 40 2 6 41 2 6 42 2 6 44 2 8 1 3 8 6 2 8 13 2 8 14 3 8 18 2 8 19 2 8 20 1 8 22 2 8 23 1 8 24 2 8 25 2 8 27 1 8 32 2 8 33 2 8 35 2 8 37 2 8 38 1 8 40 1 8 41 2 8 42 2 8 44 2 8 45 2 10 1 3 10 13 2 10 22 2 10 24 1 10 27 2 10 33 1 10 40 2 10 42 2 10 44 2 11 1 3 11 2 2 11 3 1 11 13 2 11 14 2 11 19 1 11 21 3 11 41 2 13 1 2 13 2 2 13 3 2 13 6 2 13 8 2 13 14 1 13 19 2 13 21 2 13 22 2 13 23 2 13 24 2 13 25 2 13 27 1 13 32 2 13 33 2 13 35 1 13 36 1 13 37 2 13 38 2 13 40 2 13 42 2 13 43 2 14 1 3 14 2 4 14 8 2 14 13 2 14 19 1 14 21 2 14 22 1 14 33 1 14 35 3 14 40 3 14 45 4 18 1 2 18 2 1 18 3 3 18 6 3 18 8 2 18 11 1 18 13 2 18 14 2 18 19 2 18 20 3 18 22 1 18 23 2 18 24 2 18 25 2 18 27 2 18 31 2 18 32 3 18 33 2 18 35 2 18 36 4 18 37 2 18 38 2 18 41 2 18 42 2 18 43 2 19 1 1 19 2 3 19 3 2 19 6 1 19 8 1 19 13 3 19 14 1 19 18 1 19 22 2 19 23 1 19 24 2 19 25 2 19 27 1 19 31 2 19 32 2 19 33 2 19 35 2 19 36 1 19 37 2 19 38 2 19 40 2 19 41 1 19 42 1 19 44 1 20 1 1 20 3 1 20 6 2 20 13 1 20 18 3 20 22 2 20 24 1 20 27 2 20 32 2 20 33 2 20 35 2 20 38 2 20 42 2 20 43 2 21 1 3 21 2 3 21 3 1 21 6 2 21 8 1 21 11 3 21 13 3 21 14 2 21 18 1 21 19 1 21 22 1 21 23 1 21 24 1 21 27 2 21 31 1 21 32 1 21 33 1 21 35 1 21 36 1 21 39 2 21 40 4 21 41 2 21 42 2 21 43 2 21 44 3 21 45 3 22 1 3 22 2 2 22 3 4 22 6 2 22 8 3 22 13 3 22 14 2 22 18 1 22 19 2 22 20 3 22 21 1 22 23 3 22 24 4 22 25 3 22 26 2 22 27 3 22 31 2 22 32 3 22 33 3 22 35 4 22 36 3 22 37 3 22 38 3 22 39 2 22 40 1 22 41 2 22 42 4 22 43 3 22 44 2 22 46 1 23 1 3 23 2 2 23 3 2 23 6 3 23 8 1 23 11 1 23 13 2 23 14 2 23 18 2 23 19 2 23 20 1 23 22 3 23 24 2 23 25 2 23 27 2 23 31 4 23 32 1 23 33 2 23 35 1 23 36 2 23 37 2 23 38 2 23 42 3 23 44 2 23 46 1 24 1 2 24 2 2 24 3 2 24 6 1 24 8 3 24 13 3 24 14 1 24 19 2 24 22 3 24 23 2 24 25 3 24 27 1 24 31 2 24 32 2 24 33 4 24 35 3 24 37 3 24 38 2 24 42 2 25 1 3 25 2 2 25 3 3 25 8 2 25 13 3 25 14 2 25 18 1 25 19 2 25 22 3 25 23 2 25 24 2 25 27 1 25 32 3 25 33 3 25 35 3 25 37 2 25 41 1 25 42 1 25 44 2 25 46 1 26 1 4 26 2 1 26 3 2 26 19 2 26 22 2 26 23 1 26 27 1 26 37 1 26 39 2 26 40 2 26 41 1 26 42 2 26 43 2 26 44 4 26 46 2 27 1 2 27 3 2 27 6 4 27 8 1 27 13 2 27 18 2 27 20 2 27 22 2 27 23 2 27 24 1 27 32 1 27 33 2 27 35 3 27 36 2 27 37 2 27 38 2 27 39 2 27 41 2 27 42 2 27 43 1 27 44 2 27 46 2 31 1 1 31 3 2 31 6 1 31 8 1 31 18 2 31 19 2 31 20 2 31 22 2 31 23 2 31 24 2 31 32 1 31 35 3 31 36 1 31 37 3 31 38 2 31 42 1 32 1 2 32 2 2 32 3 2 32 6 2 32 8 2 32 13 2 32 18 3 32 19 2 32 20 2 32 22 3 32 23 1 32 24 2 32 25 2 32 27 2 32 31 1 32 33 3 32 35 4 32 36 2 32 37 3 32 38 3 32 41 2 32 42 3 32 43 1 33 1 3 33 2 3 33 3 2 33 6 2 33 8 2 33 13 3 33 14 1 33 18 2 33 19 3 33 20 2 33 22 2 33 23 3 33 24 4 33 25 3 33 27 2 33 31 2 33 32 2 33 35 3 33 36 2 33 37 2 33 38 3 33 40 1 33 41 2 33 42 2 33 43 1 33 45 1 35 1 2 35 2 2 35 3 2 35 6 3 35 13 2 35 14 3 35 18 2 35 19 2 35 22 3 35 24 3 35 25 2 35 27 3 35 32 3 35 33 3 35 37 4 35 38 2 35 41 2 35 42 4 36 1 2 36 3 4 36 6 3 36 18 4 36 20 1 36 22 2 36 23 1 36 24 1 36 27 2 36 31 1 36 32 2 36 33 2 36 35 1 36 37 1 36 38 2 36 41 1 36 42 2 37 1 2 37 2 2 37 3 2 37 6 2 37 8 2 37 13 3 37 14 2 37 18 2 37 19 2 37 20 2 37 22 3 37 23 2 37 24 3 37 25 2 37 27 3 37 31 4 37 32 3 37 33 3 37 35 4 37 36 2 37 38 3 37 40 2 37 41 2 37 42 4 38 1 2 38 2 2 38 3 2 38 6 2 38 8 1 38 13 2 38 18 3 38 19 2 38 20 2 38 22 3 38 23 2 38 24 3 38 27 2 38 31 2 38 32 4 38 33 3 38 35 3 38 36 3 38 37 4 38 41 1 39 1 4 39 2 1 39 3 2 39 6 1 39 8 1 39 11 1 39 13 1 39 18 1 39 19 1 39 20 1 39 21 2 39 22 2 39 23 1 39 24 1 39 26 3 39 27 2 39 32 1 39 33 1 39 35 2 39 36 1 39 37 2 39 38 1 39 41 2 39 42 2 39 44 3 39 46 1 40 1 2 40 2 2 40 3 1 40 6 2 40 8 1 40 13 2 40 14 2 40 18 1 40 19 1 40 21 4 40 22 1 40 23 1 40 24 1 40 25 1 40 27 1 40 32 1 40 33 1 40 43 2 41 1 3 41 2 2 41 6 3 41 18 1 41 19 1 41 21 1 41 22 2 41 23 2 41 24 2 41 27 3 41 32 2 41 33 2 41 35 3 41 37 2 41 38 1 41 39 2 41 40 1 41 42 2 41 44 2 42 1 2 42 2 2 42 3 2 42 6 2 42 8 2 42 13 2 42 18 2 42 20 2 42 22 3 42 23 2 42 24 2 42 26 2 42 27 2 42 32 2 42 33 2 42 35 4 42 36 2 42 37 3 42 39 2 42 40 2 42 41 2 42 43 2 42 44 2 42 46 3 43 1 3 43 2 4 43 3 1 43 13 4 43 18 2 43 21 2 43 22 2 43 24 2 43 25 2 43 26 2 43 27 2 43 32 2 43 33 2 43 35 2 43 40 2 43 41 1 43 42 2 43 45 2 43 46 1 44 1 4 44 2 4 44 3 2 44 6 2 44 8 2 44 10 2 44 11 1 44 13 2 44 14 2 44 19 2 44 21 2 44 22 2 44 23 2 44 24 1 44 25 2 44 26 3 44 27 2 44 33 1 44 35 2 44 36 2 44 37 2 44 39 2 44 40 2 44 41 2 44 42 2 44 43 2 44 46 1 45 1 3 45 2 3 45 6 1 45 8 2 45 13 3 45 14 4 45 19 1 45 21 2 45 22 1 45 24 1 45 27 1 45 32 1 45 33 1 45 40 2 45 43 3 45 44 3 45 46 1 46 1 2 46 2 2 46 42 3SocNetV-3.7/src/data/Freeman_EIES_network_48actors_Acquaintanceship_at_time_2.dl000066400000000000000000000143641524402210300276730ustar00rootroot00000000000000dl N=48 format=edgelist1 data: 1 2 4 1 3 2 1 6 2 1 8 2 1 10 2 1 11 2 1 13 3 1 14 3 1 18 2 1 19 3 1 20 2 1 21 3 1 22 2 1 23 2 1 24 2 1 25 2 1 26 3 1 27 2 1 31 2 1 32 2 1 33 2 1 35 2 1 36 2 1 37 2 1 38 2 1 39 3 1 40 2 1 41 2 1 42 3 1 43 2 1 44 4 1 45 3 1 46 3 2 1 4 2 3 2 2 6 2 2 8 1 2 10 2 2 11 2 2 13 3 2 14 4 2 18 2 2 19 3 2 21 2 2 22 2 2 23 2 2 24 2 2 25 2 2 26 2 2 27 2 2 32 2 2 33 2 2 35 2 2 36 2 2 37 2 2 38 2 2 39 2 2 40 2 2 41 2 2 42 2 2 43 3 2 44 4 2 45 4 2 46 2 3 1 3 3 2 1 3 6 4 3 8 1 3 13 2 3 18 2 3 19 4 3 20 4 3 22 4 3 23 1 3 24 2 3 25 2 3 26 2 3 27 1 3 31 1 3 32 2 3 33 2 3 35 2 3 36 4 3 37 2 3 39 2 3 41 1 3 42 1 3 43 1 6 1 2 6 2 2 6 3 2 6 8 2 6 10 2 6 13 2 6 14 2 6 18 3 6 19 2 6 20 2 6 21 1 6 22 2 6 23 2 6 24 2 6 26 2 6 27 4 6 31 1 6 32 2 6 33 2 6 35 2 6 36 2 6 37 2 6 38 2 6 39 2 6 40 2 6 41 2 6 42 2 6 43 2 6 44 2 6 46 2 8 1 3 8 6 2 8 13 2 8 14 3 8 18 2 8 19 2 8 20 1 8 22 2 8 23 1 8 24 2 8 25 2 8 27 1 8 32 2 8 33 2 8 35 2 8 37 2 8 38 1 8 40 1 8 41 2 8 42 2 8 44 2 8 45 2 10 1 4 10 2 2 10 13 3 10 18 2 10 19 2 10 22 2 10 23 2 10 24 2 10 27 2 10 31 2 10 33 2 10 37 3 10 39 2 10 40 2 10 41 2 10 42 3 10 44 4 10 45 2 10 46 3 11 1 3 11 2 2 11 3 1 11 13 2 11 14 2 11 19 1 11 21 3 11 41 2 13 1 3 13 2 2 13 3 2 13 6 2 13 8 2 13 10 2 13 11 1 13 14 1 13 18 2 13 19 4 13 20 1 13 21 2 13 22 2 13 23 2 13 24 2 13 25 2 13 26 2 13 27 2 13 32 2 13 33 2 13 35 2 13 36 2 13 37 2 13 38 2 13 40 2 13 41 1 13 42 2 13 43 2 13 44 2 13 45 4 13 46 2 14 1 3 14 2 4 14 8 2 14 13 2 14 19 2 14 21 2 14 22 1 14 24 1 14 25 2 14 33 2 14 35 2 14 40 3 14 42 1 14 44 2 14 45 4 14 46 2 18 1 3 18 3 2 18 6 3 18 8 2 18 11 1 18 13 2 18 14 1 18 19 2 18 20 3 18 21 2 18 22 1 18 23 2 18 24 2 18 25 2 18 26 2 18 27 2 18 31 2 18 32 4 18 33 2 18 35 2 18 36 4 18 37 2 18 38 2 18 40 2 18 41 2 18 42 3 18 43 2 18 44 2 18 45 1 19 1 3 19 2 2 19 3 2 19 6 2 19 8 2 19 10 2 19 13 4 19 14 2 19 18 2 19 21 2 19 22 2 19 23 2 19 24 2 19 25 2 19 26 2 19 27 2 19 31 2 19 32 2 19 33 2 19 35 2 19 36 1 19 37 2 19 38 2 19 40 2 19 41 2 19 42 2 19 44 3 19 45 3 19 46 2 20 1 2 20 3 1 20 6 2 20 11 1 20 13 1 20 18 3 20 22 2 20 23 1 20 24 1 20 27 2 20 31 2 20 32 3 20 33 2 20 35 1 20 36 1 20 37 1 20 38 2 20 41 1 20 43 1 20 44 2 20 45 2 20 46 2 21 1 3 21 2 3 21 3 1 21 6 2 21 8 1 21 11 3 21 13 3 21 14 2 21 18 1 21 19 2 21 22 1 21 23 1 21 24 2 21 26 2 21 27 2 21 31 1 21 32 1 21 33 2 21 35 2 21 36 1 21 39 2 21 40 4 21 41 2 21 42 2 21 43 2 21 44 3 21 45 3 22 1 3 22 2 2 22 3 4 22 6 3 22 8 3 22 13 3 22 18 2 22 19 2 22 20 3 22 21 2 22 23 3 22 24 4 22 25 4 22 26 2 22 27 3 22 31 3 22 32 3 22 33 3 22 35 4 22 36 3 22 37 3 22 38 3 22 39 2 22 40 2 22 41 3 22 42 4 22 43 3 22 44 3 22 45 2 22 46 2 23 1 3 23 2 2 23 3 2 23 6 3 23 8 1 23 13 2 23 14 2 23 18 2 23 19 2 23 20 2 23 22 3 23 24 2 23 25 2 23 27 2 23 31 4 23 32 1 23 33 2 23 35 2 23 36 2 23 37 2 23 38 2 23 40 1 23 42 3 23 44 3 23 45 1 24 1 2 24 2 2 24 3 2 24 6 2 24 8 3 24 10 2 24 13 3 24 14 1 24 18 2 24 19 2 24 22 3 24 23 2 24 25 2 24 27 2 24 31 2 24 32 2 24 33 4 24 35 3 24 37 2 24 38 2 24 39 2 24 40 1 24 41 1 24 42 2 24 44 2 24 45 2 24 46 2 25 1 3 25 2 2 25 3 3 25 6 1 25 8 2 25 13 3 25 14 2 25 18 1 25 19 3 25 20 1 25 21 1 25 22 3 25 23 2 25 24 3 25 26 1 25 27 1 25 32 3 25 33 3 25 35 3 25 37 2 25 39 1 25 40 2 25 41 1 25 42 2 25 43 2 25 44 2 25 45 2 25 46 1 26 1 4 26 2 2 26 3 2 26 11 1 26 13 2 26 19 2 26 21 1 26 22 2 26 39 2 26 40 2 26 42 2 26 43 2 26 44 4 26 45 1 26 46 2 27 1 2 27 3 2 27 6 4 27 8 1 27 13 2 27 18 2 27 20 2 27 22 2 27 23 2 27 24 1 27 32 2 27 33 2 27 35 3 27 36 2 27 37 2 27 38 2 27 39 2 27 41 2 27 42 2 27 43 1 27 44 2 27 46 2 31 1 1 31 3 2 31 6 1 31 8 1 31 18 2 31 19 2 31 20 2 31 22 2 31 23 2 31 24 2 31 32 1 31 35 3 31 36 1 31 37 3 31 38 2 31 42 1 32 1 2 32 2 2 32 3 2 32 6 2 32 8 2 32 13 2 32 18 3 32 19 2 32 20 2 32 22 3 32 23 1 32 24 2 32 25 2 32 27 2 32 31 1 32 33 3 32 35 4 32 36 2 32 37 3 32 38 3 32 41 2 32 42 3 32 43 1 33 1 3 33 2 3 33 3 2 33 6 2 33 8 2 33 13 3 33 14 1 33 18 2 33 19 3 33 20 2 33 22 2 33 23 3 33 24 4 33 25 3 33 27 2 33 31 2 33 32 2 33 35 3 33 36 2 33 37 2 33 38 3 33 40 1 33 41 2 33 42 2 33 43 1 33 45 1 35 1 2 35 2 2 35 3 2 35 6 3 35 13 2 35 14 3 35 18 2 35 19 2 35 22 3 35 23 2 35 24 3 35 25 2 35 27 3 35 32 3 35 33 3 35 37 4 35 38 2 35 41 2 35 42 4 35 45 2 36 1 3 36 2 2 36 3 4 36 6 3 36 13 2 36 18 4 36 20 1 36 22 3 36 23 1 36 24 1 36 27 3 36 31 1 36 32 2 36 33 1 36 35 1 36 37 2 36 38 2 36 41 2 36 42 3 36 43 2 36 44 2 36 46 2 37 1 3 37 2 2 37 3 2 37 6 2 37 8 3 37 10 2 37 13 3 37 14 2 37 18 2 37 19 3 37 20 2 37 21 2 37 22 3 37 23 2 37 24 3 37 25 2 37 26 2 37 27 2 37 31 4 37 32 3 37 33 3 37 35 4 37 36 2 37 38 3 37 40 2 37 41 3 37 42 3 37 43 2 37 44 2 37 45 2 37 46 2 38 1 2 38 2 2 38 3 2 38 6 3 38 8 1 38 13 3 38 18 3 38 19 2 38 20 2 38 22 3 38 23 2 38 24 3 38 27 2 38 31 3 38 32 3 38 33 3 38 35 3 38 36 3 38 37 3 38 41 1 38 42 2 39 1 4 39 2 1 39 3 2 39 6 1 39 8 1 39 11 1 39 13 1 39 18 1 39 19 1 39 20 1 39 21 2 39 22 2 39 23 1 39 24 1 39 26 3 39 27 2 39 32 1 39 33 1 39 35 2 39 36 1 39 37 2 39 38 1 39 41 2 39 42 2 39 44 3 39 46 1 40 1 3 40 2 2 40 3 2 40 6 2 40 8 2 40 10 2 40 13 3 40 14 3 40 18 2 40 19 2 40 21 4 40 22 1 40 23 2 40 24 2 40 25 2 40 26 2 40 27 2 40 32 1 40 33 2 40 35 2 40 36 1 40 37 2 40 42 2 40 43 2 40 44 2 40 45 2 40 46 2 41 1 3 41 2 2 41 6 3 41 13 2 41 18 1 41 19 1 41 21 2 41 22 2 41 23 2 41 24 2 41 27 3 41 32 2 41 33 2 41 35 3 41 37 2 41 38 1 41 39 2 41 40 1 41 42 2 41 44 2 41 45 2 41 46 2 42 1 3 42 2 2 42 3 2 42 6 3 42 8 2 42 10 2 42 13 3 42 18 3 42 19 2 42 20 3 42 21 2 42 22 4 42 23 3 42 24 2 42 25 2 42 26 2 42 27 2 42 32 3 42 33 2 42 35 4 42 36 2 42 37 4 42 39 2 42 40 2 42 41 2 42 43 2 42 44 3 42 45 2 42 46 4 43 1 3 43 2 3 43 3 1 43 6 2 43 10 2 43 13 3 43 18 2 43 19 2 43 21 2 43 22 2 43 24 2 43 25 2 43 26 2 43 27 2 43 32 2 43 33 2 43 35 2 43 37 2 43 38 2 43 40 3 43 41 2 43 42 3 43 44 3 43 45 3 43 46 2 44 1 4 44 2 4 44 3 2 44 6 2 44 8 2 44 10 3 44 11 2 44 13 2 44 14 2 44 18 2 44 19 3 44 20 2 44 21 3 44 22 2 44 23 3 44 24 2 44 25 2 44 26 3 44 27 2 44 31 2 44 32 2 44 33 2 44 35 2 44 36 2 44 37 2 44 38 2 44 39 2 44 40 2 44 41 2 44 42 3 44 43 2 44 45 4 44 46 3 45 1 4 45 2 4 45 6 2 45 8 2 45 10 2 45 13 4 45 14 4 45 18 2 45 19 3 45 21 2 45 22 1 45 24 3 45 25 2 45 32 1 45 33 2 45 35 3 45 36 1 45 37 1 45 39 2 45 40 2 45 41 1 45 42 3 45 43 2 45 44 4 45 46 3 46 1 3 46 2 2 46 6 1 46 8 1 46 10 2 46 13 3 46 14 2 46 18 2 46 19 2 46 24 2 46 25 2 46 27 1 46 37 2 46 42 4 46 44 3 46 45 3SocNetV-3.7/src/data/Freeman_EIES_network_48actors_Messages.dl000066400000000000000000000072011524402210300242270ustar00rootroot00000000000000dl N=32 format=edgelist1 data: 1 1 24 1 2 488 1 3 28 1 4 65 1 5 20 1 6 65 1 7 45 1 8 346 1 9 82 1 10 52 1 11 177 1 12 28 1 13 24 1 14 49 1 15 81 1 16 77 1 17 77 1 18 73 1 19 33 1 20 31 1 21 22 1 22 46 1 23 31 1 24 128 1 25 38 1 26 89 1 27 95 1 28 25 1 29 388 1 30 71 1 31 212 1 32 185 2 1 364 2 2 6 2 3 17 2 4 17 2 5 15 2 7 30 2 8 20 2 9 35 2 10 20 2 11 22 2 12 15 2 13 15 2 14 15 2 15 15 2 16 50 2 17 25 2 18 8 2 20 15 2 21 15 2 22 15 2 23 15 2 25 15 2 26 15 2 27 10 2 28 24 2 29 89 2 30 23 2 31 163 2 32 39 3 1 4 3 2 5 3 8 5 4 1 52 4 2 30 4 4 4 4 6 2 4 8 32 4 9 21 4 10 34 4 11 9 4 16 5 4 17 4 4 18 2 4 19 35 4 24 12 4 27 12 4 28 5 4 29 20 4 30 4 4 31 19 4 32 33 5 1 26 5 2 4 5 3 4 5 4 4 5 6 4 5 7 8 5 8 4 5 9 4 5 10 4 5 11 4 5 12 4 5 13 4 5 14 4 5 15 4 5 16 4 5 17 4 5 18 4 5 19 4 5 21 4 5 22 8 5 23 4 5 24 14 5 25 4 5 27 4 5 29 4 5 30 7 5 31 4 5 32 4 6 1 72 6 2 23 6 4 2 6 6 34 6 8 16 6 10 7 6 11 15 6 15 8 6 16 7 6 17 6 6 24 14 6 27 7 6 28 3 6 29 34 6 30 3 6 31 22 7 1 14 7 31 6 8 1 239 8 2 82 8 3 5 8 4 37 8 5 3 8 6 34 8 7 5 8 8 10 8 9 12 8 10 18 8 11 164 8 12 18 8 16 30 8 17 53 8 18 27 8 19 20 8 20 4 8 22 5 8 23 4 8 24 55 8 26 9 8 27 34 8 29 146 8 30 216 8 31 88 8 32 288 9 1 24 9 2 25 9 4 2 9 8 8 9 9 16 9 11 15 9 13 10 9 17 5 9 27 15 9 29 10 9 31 30 9 32 44 10 1 43 10 2 15 10 4 32 10 6 12 10 8 14 10 10 5 10 11 25 10 12 2 10 16 10 10 17 10 10 19 20 10 20 15 10 22 5 10 23 20 10 24 29 10 26 4 10 27 10 10 29 47 10 30 6 10 31 22 10 32 19 11 1 178 11 2 36 11 4 11 11 6 19 11 7 10 11 8 172 11 9 39 11 10 28 11 11 29 11 13 4 11 16 23 11 17 15 11 18 24 11 21 8 11 24 29 11 25 10 11 26 11 11 27 22 11 29 46 11 31 119 11 32 34 12 2 5 12 8 5 12 12 3 12 19 5 12 29 53 12 31 5 12 32 9 13 1 5 13 11 5 13 31 5 14 1 12 14 3 9 14 14 2 14 16 12 14 19 5 14 29 35 14 31 8 15 1 120 15 6 4 15 12 5 15 15 78 15 27 8 15 29 58 15 31 32 16 1 58 16 2 25 16 4 10 16 8 20 16 10 5 16 11 10 16 14 5 16 16 15 16 17 10 16 21 5 16 24 5 16 29 35 16 31 10 17 1 63 17 2 18 17 3 9 17 4 7 17 6 6 17 8 36 17 10 5 17 11 9 17 12 5 17 14 5 17 16 5 17 20 5 17 21 2 17 27 15 17 29 10 17 30 9 17 31 15 17 32 9 18 1 58 18 2 8 18 3 5 18 4 4 18 8 4 18 10 5 18 11 18 18 18 4 18 27 20 18 29 8 18 30 10 18 31 48 19 1 5 19 2 5 19 4 25 19 8 10 19 14 5 19 19 5 19 23 5 19 31 10 20 21 4 20 29 4 21 1 9 21 11 3 21 16 5 21 29 5 22 1 10 22 24 40 22 29 15 22 32 5 23 1 5 23 2 5 23 3 5 23 10 19 23 19 5 23 29 14 23 31 5 24 1 89 24 2 17 24 3 4 24 4 14 24 5 14 24 6 18 24 7 8 24 8 41 24 9 4 24 10 19 24 11 31 24 12 4 24 13 4 24 14 9 24 15 4 24 16 14 24 17 4 24 18 9 24 19 4 24 20 4 24 21 4 24 22 58 24 23 4 24 24 5 24 25 18 24 26 14 24 27 9 24 28 4 24 29 156 24 30 4 24 31 56 24 32 10 25 1 32 25 2 5 25 14 15 25 22 10 25 24 23 25 25 10 25 30 9 25 31 15 26 1 35 26 2 5 26 10 5 26 29 10 26 31 13 27 1 50 27 2 28 27 4 13 27 8 19 27 9 29 27 10 5 27 11 8 27 13 33 27 15 4 27 17 10 27 18 15 27 24 10 27 28 3 27 29 32 27 31 13 27 32 33 28 1 9 28 2 6 28 6 3 28 28 3 28 32 6 29 1 559 29 2 132 29 3 5 29 4 24 29 5 21 29 6 29 29 8 155 29 9 15 29 10 98 29 11 69 29 12 89 29 13 37 29 14 76 29 15 80 29 16 63 29 17 15 29 18 4 29 19 9 29 20 18 29 21 43 29 22 108 29 23 29 29 24 218 29 26 15 29 27 66 29 29 6 29 30 14 29 31 91 29 32 126 30 1 39 30 2 21 30 4 6 30 5 3 30 6 3 30 8 140 30 10 7 30 12 2 30 17 9 30 18 5 30 27 2 30 29 18 30 30 2 30 31 20 30 32 8 31 1 82 31 2 125 31 3 10 31 4 22 31 5 10 31 6 15 31 7 18 31 8 70 31 9 35 31 10 23 31 11 114 31 12 20 31 13 16 31 14 15 31 15 24 31 16 30 31 17 28 31 18 49 31 19 30 31 20 5 31 21 5 31 22 15 31 23 8 31 24 53 31 25 25 31 26 8 31 27 21 31 28 8 31 29 65 31 30 28 31 32 67 32 1 239 32 2 99 32 4 27 32 5 3 32 8 268 32 9 101 32 10 18 32 11 35 32 12 4 32 17 7 32 22 14 32 24 5 32 27 50 32 28 6 32 29 71 32 30 7 32 31 107 32 32 219SocNetV-3.7/src/data/Freeman_EIES_networks_32actors.dl000066400000000000000000000206331524402210300225600ustar00rootroot00000000000000DL N=32 NM=3 FORMAT = FULLMATRIX DIAGONAL PRESENT ROW LABELS: "1" "2" "3" "6" "8" "10" "11" "13" "14" "18" "19" "20" "21" "22" "23" "24" "25" "26" "27" "32" "33" "35" "36" "37" "38" "39" "40" "41" "42" "43" "44" "45" COLUMN LABELS: "1" "2" "3" "6" "8" "10" "11" "13" "14" "18" "19" "20" "21" "22" "23" "24" "25" "26" "27" "32" "33" "35" "36" "37" "38" "39" "40" "41" "42" "43" "44" "45" LEVEL LABELS: "TIME_1" "TIME_2" "NUMBER_OF_MESSAGES" DATA: 0 4 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 2 2 2 2 2 2 2 3 2 2 2 2 4 2 4 0 2 0 1 0 3 3 4 1 3 0 2 2 2 3 2 0 1 2 3 2 0 2 0 0 2 1 2 3 4 4 3 1 0 4 1 0 0 2 0 2 4 4 0 4 1 2 2 2 1 2 2 2 4 2 0 2 0 1 1 1 0 0 2 0 2 0 2 0 0 2 2 2 2 2 2 2 2 1 0 0 4 2 2 2 2 2 2 0 2 2 2 0 2 0 3 0 0 2 0 0 0 2 3 2 2 1 0 2 1 2 2 0 1 2 2 2 0 2 1 0 1 2 2 0 2 2 3 0 0 0 0 0 0 2 0 0 0 0 0 2 0 1 0 0 2 0 1 0 0 0 0 0 2 0 2 0 2 0 3 2 1 0 0 0 0 2 2 0 1 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 2 2 2 2 2 0 0 0 1 0 2 0 2 2 2 2 2 0 1 2 2 1 1 2 2 0 2 0 2 2 0 0 3 4 0 0 2 0 0 2 0 0 1 0 2 1 0 0 0 0 0 0 1 3 0 0 0 0 3 0 0 0 0 4 2 1 3 3 2 0 1 2 2 0 2 3 0 1 2 2 2 0 2 3 2 2 4 2 2 0 0 2 2 2 0 0 1 3 2 1 1 0 0 3 1 1 0 0 0 2 1 2 2 0 1 2 2 2 1 2 2 0 2 1 1 0 1 0 1 0 1 2 0 0 0 1 0 3 0 0 0 2 0 1 0 0 2 2 2 2 0 0 2 0 0 0 2 2 0 0 3 3 1 2 1 0 3 3 2 1 1 0 0 1 1 1 0 0 2 1 1 1 1 0 0 2 4 2 2 2 3 3 3 2 4 2 3 0 0 3 2 1 2 3 1 0 3 4 3 2 3 3 3 4 3 3 3 2 1 2 4 3 2 0 3 2 2 3 1 0 1 2 2 2 2 1 0 3 0 2 2 0 2 1 2 1 2 2 2 0 0 0 3 0 2 0 2 2 2 1 3 0 0 3 1 0 2 0 0 3 2 0 3 0 1 2 4 3 0 3 2 0 0 0 2 0 0 0 3 2 3 0 2 0 0 3 2 1 2 0 0 3 2 2 0 0 1 3 3 3 0 2 0 0 0 1 1 0 2 0 4 1 2 0 0 0 0 0 0 0 2 0 0 2 1 0 0 0 1 0 0 0 0 1 0 2 2 1 2 2 4 0 2 0 2 4 1 0 0 2 0 2 0 2 0 2 2 1 0 0 0 1 2 3 2 2 2 2 0 2 2 1 2 0 2 2 2 2 2 0 0 2 0 3 2 2 0 3 1 2 2 0 2 0 3 4 2 3 3 0 0 2 3 1 0 0 3 3 2 2 2 0 0 3 1 2 3 2 0 2 3 4 3 0 2 2 0 3 2 2 3 0 1 2 2 1 0 1 2 2 2 3 0 0 0 2 3 2 2 0 0 3 0 3 2 0 3 3 3 0 0 4 2 0 0 2 4 0 0 0 2 0 4 3 0 0 0 0 0 4 0 1 0 2 1 1 0 0 2 2 2 1 0 1 2 0 0 1 2 0 0 0 2 2 2 2 2 0 0 3 2 2 2 2 0 3 2 3 2 0 3 3 3 4 2 0 3 0 2 2 4 0 0 0 2 2 2 2 1 0 0 2 0 3 2 2 0 3 2 3 0 0 2 4 3 3 3 4 0 0 0 1 0 0 0 0 4 1 2 1 1 0 1 1 0 1 1 1 2 2 1 1 0 3 2 1 1 2 1 2 1 0 0 2 2 0 3 0 2 2 1 2 1 0 0 2 2 1 1 0 4 1 1 1 1 0 1 1 1 0 0 0 0 0 0 0 0 2 0 0 3 2 0 3 0 0 0 0 0 1 1 0 1 2 2 2 0 0 3 2 2 3 0 2 1 2 1 0 2 0 2 0 2 2 2 2 2 0 0 2 0 2 0 2 0 3 2 2 0 2 2 2 2 4 2 3 0 2 2 2 0 2 2 0 3 4 1 0 0 0 0 4 0 2 0 0 2 2 0 2 2 2 2 2 2 2 0 0 0 0 2 1 2 0 0 2 4 4 2 2 2 2 1 2 2 0 2 0 2 2 2 1 2 3 2 0 1 2 2 2 0 2 2 2 2 2 0 0 3 3 0 1 2 0 0 3 4 0 1 0 2 1 0 1 0 0 1 1 1 0 0 0 0 0 2 0 0 3 3 0 0 4 2 2 2 2 2 3 3 2 3 2 3 2 2 2 2 3 2 2 2 2 2 2 2 3 2 2 3 2 4 3 4 0 2 2 1 2 2 3 4 2 3 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 4 4 3 1 0 4 1 0 0 2 0 2 4 4 0 4 1 2 2 2 1 2 2 2 4 2 0 2 0 1 1 1 0 0 2 2 2 0 2 2 0 2 2 3 2 2 1 2 2 2 0 2 4 2 2 2 2 2 2 2 2 2 2 2 2 0 3 0 0 2 0 0 0 2 3 2 2 1 0 2 1 2 2 0 1 2 2 2 0 2 1 0 1 2 2 0 2 2 4 2 0 0 0 0 0 3 0 2 2 0 0 2 2 2 0 0 2 0 2 0 0 3 0 2 2 2 3 0 4 2 3 2 1 0 0 0 0 2 2 0 1 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 3 2 2 2 2 2 1 0 1 2 4 1 2 2 2 2 2 2 2 2 2 2 2 2 2 0 2 1 2 2 2 4 3 4 0 0 2 0 0 2 0 0 2 0 2 1 0 1 2 0 0 0 2 2 0 0 0 0 3 0 1 0 2 4 3 0 2 3 2 0 1 2 1 0 2 3 2 1 2 2 2 2 2 4 2 2 4 2 2 0 2 2 3 2 2 1 3 2 2 2 2 2 0 4 2 2 0 0 2 2 2 2 2 2 2 2 2 2 1 2 2 0 2 2 2 0 3 3 2 0 1 2 0 0 1 1 0 3 0 0 0 2 1 1 0 0 2 3 2 1 1 1 2 0 0 1 0 1 2 2 3 3 1 2 1 0 3 3 2 1 2 0 0 1 1 2 0 2 2 1 2 2 1 0 0 2 4 2 2 2 3 3 3 2 4 3 3 0 0 3 0 2 2 3 2 0 3 4 4 2 3 3 3 4 3 3 3 2 2 3 4 3 3 2 3 2 2 3 1 0 0 2 2 2 2 2 0 3 0 2 2 0 2 1 2 2 2 2 2 0 1 0 3 0 3 1 2 2 2 2 3 2 0 3 1 2 2 0 0 3 2 0 2 0 2 2 4 3 0 2 2 2 1 1 2 0 2 2 3 2 3 1 2 0 0 3 2 1 3 1 1 3 2 3 0 1 1 3 3 3 0 2 0 1 2 1 2 2 2 2 4 2 2 0 0 0 1 2 0 0 2 0 1 2 0 0 0 0 0 0 0 0 0 0 0 2 2 0 2 2 4 1 2 0 2 4 1 0 0 2 0 2 0 2 0 2 2 1 0 0 0 2 2 3 2 2 2 2 0 2 2 1 2 0 2 2 2 2 2 0 0 2 0 3 2 2 0 3 1 2 2 0 2 0 3 4 2 3 3 0 0 2 3 1 0 0 3 3 2 2 2 0 0 3 1 2 3 2 0 2 3 4 3 0 2 2 0 3 2 2 3 0 1 2 2 1 0 1 2 2 2 3 0 0 0 2 3 2 2 0 0 3 2 3 2 0 3 3 3 0 0 4 2 0 0 2 4 0 0 2 3 2 4 3 0 0 0 2 0 4 0 1 0 3 1 1 0 0 3 2 1 1 0 2 2 0 0 2 3 2 2 0 3 2 2 2 3 2 0 3 2 2 3 2 2 3 2 3 2 2 2 3 3 4 2 0 3 0 2 3 3 2 2 2 2 2 2 3 1 0 0 3 0 3 2 2 0 3 2 3 0 0 2 3 3 3 3 3 0 0 0 1 2 0 0 0 4 1 2 1 1 0 1 1 0 1 1 1 2 2 1 1 0 3 2 1 1 2 1 2 1 0 0 2 2 0 3 0 3 2 2 2 2 2 0 3 3 2 2 0 4 1 2 2 2 2 2 1 2 2 1 2 0 0 0 0 2 2 2 2 3 2 0 3 0 0 0 2 0 1 1 0 2 2 2 2 0 0 3 2 2 3 0 2 1 2 1 0 2 0 2 2 3 2 2 3 2 2 0 3 0 3 2 3 2 4 3 2 2 2 2 3 2 4 2 4 0 2 2 2 0 2 3 2 3 3 1 2 0 2 0 3 0 2 2 0 2 2 0 2 2 2 2 2 2 2 0 2 2 0 3 2 3 0 3 3 4 4 2 2 2 3 2 2 2 2 3 2 3 2 3 2 2 3 2 2 2 2 2 2 2 2 2 2 3 2 0 4 4 4 0 2 2 2 0 4 4 2 3 0 2 1 0 3 2 0 0 1 2 3 1 1 0 2 2 1 3 2 4 0 24 488 28 65 20 65 45 346 82 52 177 28 24 49 81 77 77 73 33 31 22 46 31 128 38 89 95 25 388 71 212 185 364 6 17 17 15 0 30 20 35 20 22 15 15 15 15 50 25 8 0 15 15 15 15 0 15 15 10 24 89 23 163 39 4 5 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 52 30 0 4 0 2 0 32 21 34 9 0 0 0 0 5 4 2 35 0 0 0 0 12 0 0 12 5 20 4 19 33 26 4 4 4 0 4 8 4 4 4 4 4 4 4 4 4 4 4 4 0 4 8 4 14 4 0 4 0 4 7 4 4 72 23 0 2 0 34 0 16 0 7 15 0 0 0 8 7 6 0 0 0 0 0 0 14 0 0 7 3 34 3 22 0 14 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 239 82 5 37 3 34 5 10 12 18 164 18 0 0 0 30 53 27 20 4 0 5 4 55 0 9 34 0 146 216 88 288 24 25 0 2 0 0 0 8 16 0 15 0 10 0 0 0 5 0 0 0 0 0 0 0 0 0 15 0 10 0 30 44 43 15 0 32 0 12 0 14 0 5 25 2 0 0 0 10 10 0 20 15 0 5 20 29 0 4 10 0 47 6 22 19 178 36 0 11 0 19 10 172 39 28 29 0 4 0 0 23 15 24 0 0 8 0 0 29 10 11 22 0 46 0 119 34 0 5 0 0 0 0 0 5 0 0 0 3 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 53 0 5 9 5 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 0 12 0 9 0 0 0 0 0 0 0 0 0 0 2 0 12 0 0 5 0 0 0 0 0 0 0 0 0 35 0 8 0 120 0 0 0 0 4 0 0 0 0 0 5 0 0 78 0 0 0 0 0 0 0 0 0 0 0 8 0 58 0 32 0 58 25 0 10 0 0 0 20 0 5 10 0 0 5 0 15 10 0 0 0 5 0 0 5 0 0 0 0 35 0 10 0 63 18 9 7 0 6 0 36 0 5 9 5 0 5 0 5 0 0 0 5 2 0 0 0 0 0 15 0 10 9 15 9 58 8 5 4 0 0 0 4 0 5 18 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 20 0 8 10 48 0 5 5 0 25 0 0 0 10 0 0 0 0 0 5 0 0 0 0 5 0 0 0 5 0 0 0 0 0 0 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 4 0 0 0 9 0 0 0 0 0 0 0 0 0 3 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 5 0 0 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 40 0 0 0 0 15 0 0 5 5 5 5 0 0 0 0 0 0 19 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 14 0 5 0 89 17 4 14 14 18 8 41 4 19 31 4 4 9 4 14 4 9 4 4 4 58 4 5 18 14 9 4 156 4 56 10 32 5 0 0 0 0 0 0 0 0 0 0 0 15 0 0 0 0 0 0 0 10 0 23 10 0 0 0 0 9 15 0 35 5 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 0 13 0 50 28 0 13 0 0 0 19 29 5 8 0 33 0 4 0 10 15 0 0 0 0 0 10 0 0 0 3 32 0 13 33 9 6 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 6 559 132 5 24 21 29 0 155 15 98 69 89 37 76 80 63 15 4 9 18 43 108 29 218 0 15 66 0 6 14 91 126 39 21 0 6 3 3 0 140 0 7 0 2 0 0 0 0 9 5 0 0 0 0 0 0 0 0 2 0 18 2 20 8 82 125 10 22 10 15 18 70 35 23 114 20 16 15 24 30 28 49 30 5 5 15 8 53 25 8 21 8 65 28 0 67 239 99 0 27 3 0 0 268 101 18 35 4 0 0 0 0 7 0 0 0 0 14 0 5 0 0 50 6 71 7 107 219SocNetV-3.7/src/data/Galaskiewicz_CEOs_and_clubs_affiliation_network_data.2sm000066400000000000000000000014131524402210300274260ustar00rootroot000000000000000 0 1 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 0 0 0 0 1 0 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 1 1 1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0 1 0 0 1 1 0 1 0 0 0 0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 1 0 0 1 1 0 1 1 0 1 1 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0 0 1 1 0 1 1 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 0 0 0 1 0 0 0SocNetV-3.7/src/data/Herschel_Graph.paj000066400000000000000000000013731524402210300177530ustar00rootroot00000000000000*Network Herschel_Graph *Vertices 11 1 "1" ic red 0.48225 0.411308 circle 2 "2" ic red 0.652297 0.591389 circle 3 "3" ic red 0.479571 0.762504 circle 4 "4" ic red 0.849224 0.41395 circle 5 "5" ic red 0.48196 0.06 circle 6 "6" ic red 0.148625 0.413208 circle 7 "7" ic red 0.654193 0.198133 circle 8 "8" ic red 0.268771 0.593206 circle 9 "9" ic red 0.272785 0.19606 circle 10 "10" ic red 0.834746 0.0533333 circle 11 "11" ic red 0.134137 0.761837 circle *Arcs *Edges 1 3 1 c #616161 1 4 1 c #616161 1 5 1 c #616161 1 6 1 c #616161 2 3 1 c #616161 2 4 1 c #616161 2 7 1 c #616161 2 8 1 c #616161 3 11 1 c #616161 4 10 1 c #616161 5 9 1 c #616161 5 10 1 c #616161 6 9 1 c #616161 6 11 1 c #616161 7 9 1 c #616161 7 10 1 c #616161 8 9 1 c #616161 8 11 1 c #616161SocNetV-3.7/src/data/Knoke_Bureaucracies_Network.pajek000066400000000000000000000015511524402210300230300ustar00rootroot00000000000000*Network knokbur *Vertices 10 1 "COUN" 0.1000 0.5000 0.5000 2 "COMM" 0.1764 0.2649 0.5000 3 "EDUC" 0.3764 0.1196 0.5000 4 "INDU" 0.6236 0.1196 0.5000 5 "MAYR" 0.8236 0.2649 0.5000 6 "WRO " 0.9000 0.5000 0.5000 7 "NEWS" 0.8236 0.7351 0.5000 8 "UWAY" 0.6236 0.8804 0.5000 9 "WELF" 0.3764 0.8804 0.5000 10 "WEST" 0.1764 0.7351 0.5000 *Matrix :1 "Information exchange" 0 1 0 0 1 0 1 0 1 0 1 0 1 1 1 0 1 1 1 0 0 1 0 1 1 1 1 0 0 1 1 1 0 0 1 0 1 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 0 0 0 1 0 1 0 0 1 0 1 1 0 0 0 0 0 1 1 0 1 1 0 1 0 1 0 0 1 0 0 1 0 1 0 0 0 1 1 1 0 1 0 1 0 0 0 *Matrix :2 "Money exchange" 0 0 1 0 1 0 0 1 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 1 0 0 0 1 1 1 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 SocNetV-3.7/src/data/Krackhardt_High-tech_managers.paj000066400000000000000000000065341524402210300227140ustar00rootroot00000000000000*Network Krackhardt's High-tech managers *Vertices 21 1 "v1" 0.6226 0.7207 2 "v2" 0.6000 0.5533 3 "v3" 0.6722 0.3928 4 "v4" 0.7646 0.6000 5 "v5" 0.3518 0.4775 6 "v6" 0.7583 0.0784 7 "v7" 0.6692 0.2475 8 "v8" 0.7349 0.5030 9 "v9" 0.5325 0.3892 10 "v10" 0.5846 0.6311 11 "v11" 0.4600 0.4733 12 "v12" 0.8855 0.2566 13 "v13" 0.1145 0.4786 14 "v14" 0.3838 0.3270 15 "v15" 0.5349 0.4455 16 "v16" 0.6117 0.9216 17 "v17" 0.7041 0.4144 18 "v18" 0.4864 0.5808 19 "v19" 0.5728 0.4802 20 "v20" 0.6640 0.5041 21 "v21" 0.7846 0.3329 *Matrix :1 gives_advice_to 0 1 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 1 1 1 1 1 1 1 0 1 0 0 1 1 0 1 1 1 1 0 0 0 1 0 1 0 1 1 1 0 0 0 1 1 1 0 1 1 1 1 0 0 0 1 1 1 0 1 1 0 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 0 0 0 1 1 0 1 0 0 1 1 0 0 1 0 1 0 1 0 1 1 0 0 1 1 0 0 0 0 0 0 1 0 0 1 1 1 0 0 0 1 1 1 0 1 1 1 0 1 0 1 1 1 0 0 1 1 1 1 1 1 0 0 1 0 0 1 0 1 0 1 1 1 1 1 1 0 1 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 1 1 1 0 1 0 1 0 0 1 1 0 0 1 1 0 0 1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1 0 1 1 1 1 1 0 0 1 0 1 1 1 0 1 1 1 0 0 0 1 0 1 0 0 1 1 0 1 0 *Matrix :2 is_friend_of 0 1 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 1 1 0 0 0 0 0 1 0 0 0 1 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 0 1 0 1 0 0 1 0 0 1 0 1 0 1 0 1 0 0 0 0 1 0 1 0 0 1 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 1 1 0 0 1 0 0 0 1 0 0 0 1 0 1 1 1 1 1 0 0 1 1 0 0 1 1 0 1 0 1 1 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0 0 0 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 0 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 0 0 0 0 1 1 0 1 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 0 0 *Matrix :3 reports_to 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0SocNetV-3.7/src/data/Krackhardt_Kite_N10.paj000066400000000000000000000003531524402210300205420ustar00rootroot00000000000000*Vertices 10 1 "Andre" 2 "Beverley" 3 "Carol" 4 "Diane" 5 "Ed" 6 "Fernando" 7 "Garth" 8 "Heather" 9 "Ike" 10 "Jane" *Edges 18 1 2 1 1 3 1 1 4 1 1 6 1 2 4 1 2 5 1 2 7 1 3 4 1 3 6 1 4 5 1 4 6 1 4 7 1 5 7 1 6 7 1 6 8 1 7 8 1 8 9 1 9 10 1 SocNetV-3.7/src/data/Mexican_Power_Network_1940s.lst000066400000000000000000000004771524402210300222420ustar00rootroot0000000000000018 8 10 23 21 19 11 21 29 5 9 10 23 8 9 18 11 4 7 6 8 20 5 21 5 4 29 20 7 6 8 9 26 21 6 5 7 4 20 21 8 7 4 6 5 8 20 21 9 5 8 23 29 20 21 11 10 8 18 23 4 5 6 7 21 24 26 25 9 10 37 20 10 18 29 8 11 9 20 25 26 11 19 23 9 10 25 21 36 20 4 5 6 7 8 9 10 24 8 26 26 5 8 24 10 21 19 4 5 6 7 8 9 11 18 36 37 11 37 8 36 25 10 11 8SocNetV-3.7/src/data/Padgett_Florentine_Families.paj000066400000000000000000000033521524402210300224620ustar00rootroot00000000000000*Network Padgett's Florentine Families *Vertices 16 1 "Acciaiuoli" 0.2024 0.1006 2 "Albizzi" 0.3882 0.4754 3 "Barbadori" 0.1633 0.7413 4 "Bischeri" 0.6521 0.5605 5 "Castellani" 0.6178 0.9114 6 "Ginori" 0.3018 0.5976 7 "Guadagni" 0.5219 0.5006 8 "Lamberteschi" 0.4533 0.6299 9 "Medici" 0.2876 0.3521 10 "Pazzi" 0.0793 0.2587 11 "Peruzzi" 0.6509 0.7365 12 "Pucci" 0.4083 0.1186 13 "Ridolfi" 0.6308 0.2060 14 "Salviati" 0.0734 0.4455 15 "Strozzi" 0.8639 0.5832 16 "Tornabuoni" 0.5633 0.3713 *Matrix :1 "Marital" 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 0 0 *Matrix :2 "Business" 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0SocNetV-3.7/src/data/Petersen_Graph.paj000066400000000000000000000014361524402210300200030ustar00rootroot00000000000000*Network petersen *Vertices 10 1 "blue" ic RGB5555FF 0.301331 0.398259 circle 2 "red" ic red 0.474335 0.238302 circle 3 "blue" ic RGB5555FF 0.652082 0.407722 circle 4 "green" ic RGB00FF00 0.601418 0.681758 circle 5 "red" ic red 0.348936 0.677763 circle 6 "green" ic RGB00FF00 0.410646 0.581066 circle 7 "red" ic red 0.534221 0.583243 circle 8 "red" ic red 0.561787 0.437432 circle 9 "blue" ic RGB5555FF 0.475285 0.351469 circle 10 "green" ic RGB00FF00 0.38308 0.436344 circle *Arcs *Edges 1 2 1 c black 1 5 1 c black 1 10 1 c black 2 3 1 c black 2 9 1 c black 3 4 1 c black 3 8 1 c black 4 5 1 c black 4 7 1 c black 5 6 1 c black 6 8 1 c black 6 9 1 c black 7 9 1 c black 7 10 1 c black 8 10 1 c blackSocNetV-3.7/src/data/Sampson_Monks_N18.net000066400000000000000000000010211524402210300203140ustar00rootroot00000000000000*Vertices 18 1 "JohnBosco" 2 "Gregory" 3 "Basil" 4 "Peter" 5 "Bonaventure" 6 "Berthold" 7 "Mark" 8 "Victor" 9 "Ambrose" 10 "Romuald" 11 "Louis" 12 "Winifrid" 13 "Amand" 14 "Hugh" 15 "Boniface" 16 "Albert" 17 "Elias" 18 "Simplicius" *Arcs 2 1 1 3 13 1 4 11 1 6 5 1 6 9 1 8 4 1 8 6 1 9 12 1 10 4 1 10 5 1 10 9 1 10 13 1 11 8 1 11 14 1 13 5 1 13 7 1 13 18 1 14 15 1 15 2 1 15 7 1 15 12 1 16 2 1 16 15 1 17 2 1 18 2 1 *Edges 1 3 1 1 12 1 2 7 1 2 12 1 3 17 1 4 5 1 4 6 1 5 9 1 5 11 1 7 12 1 8 9 1 12 14 1 1 14 1 7 16 1 3 18 1 17 18 1 SocNetV-3.7/src/data/SmallWorld_N10_E12.graphml000066400000000000000000000114171524402210300210620ustar00rootroot00000000000000 0.0 0.0 10 red circle #8d8d8d 8 1.0 #666666 1 0.803097 0.499179 12 circle 2 0.745575 0.763547 12 circle 3 0.59292 0.92775 15 circle 4 0.405973 0.92775 12 circle 5 0.253319 0.763547 12 circle 6 0.195796 0.499179 9 circle 7 0.253319 0.234811 9 circle 8 0.405973 0.0706076 12 circle 9 0.59292 0.0706076 15 circle 10 0.745575 0.234811 12 circle 1 1 1 1 1 1 1 1 1 1 1 1 SocNetV-3.7/src/data/Stephenson_Zelen_40_AIDS_patients_sex_contact.paj000066400000000000000000000044651524402210300257710ustar00rootroot00000000000000*Network Stephenson&Zelen_40_AIDS_patients *Vertices 40 1 "1" ic red 0.15899 0.150442 circle 2 "2" ic red 0.178306 0.210914 circle 3 "3" ic red 0.242199 0.181416 circle 4 "4" ic red 0.31055 0.182891 circle 5 "5" ic red 0.20951 0.253687 circle 6 "6" ic red 0.132244 0.29351 circle 7 "7" ic red 0.0846954 0.327434 circle 8 "8" ic red 0.200594 0.351032 circle 9 "9" ic red 0.170877 0.412979 circle 10 "10" ic red 0.120357 0.458702 circle 11 "11" ic red 0.283804 0.292035 circle 12 "12" ic red 0.329866 0.244838 circle 13 "13" ic red 0.389302 0.210914 circle 14 "14" ic red 0.459138 0.238938 circle 15 "15" ic red 0.497771 0.294985 circle 16 "16" ic red 0.401189 0.351032 circle 17 "17" ic red 0.280832 0.349558 circle 18 "18" ic red 0.251114 0.482301 circle 19 "19" ic red 0.344725 0.547198 circle 20 "20" ic red 0.317979 0.463127 circle 21 "21" ic red 0.401189 0.449852 circle 22 "22" ic red 0.536404 0.418879 circle 23 "23" ic red 0.63893 0.355457 circle 24 "24" ic red 0.658247 0.268437 circle 25 "25" ic red 0.676077 0.443953 circle 26 "26" ic red 0.576523 0.516224 circle 27 "27" ic red 0.468053 0.511799 circle 28 "28" ic red 0.482912 0.600295 circle 29 "29" ic red 0.482912 0.675516 circle 30 "30" ic red 0.423477 0.728614 circle 31 "31" ic red 0.592868 0.646018 circle 32 "32" ic red 0.59584 0.728614 circle 33 "33" ic red 0.594354 0.792035 circle 34 "34" ic red 0.69688 0.839233 circle 35 "35" ic red 0.805349 0.889381 circle 36 "36" ic red 0.710253 0.669617 circle 37 "37" ic red 0.787519 0.70944 circle 38 "38" ic red 0.698366 0.539823 circle 39 "39" ic red 0.808321 0.466077 circle 40 "40" ic red 0.817236 0.564897 circle *Edges 1 2 1 c black 2 5 1 c black 3 5 1 c black 4 5 1 c black 5 6 1 c black 5 11 1 c black 7 8 1 c black 8 9 1 c black 8 11 1 c black 9 10 1 c black 11 16 1 c black 12 16 1 c black 13 14 1 c black 14 16 1 c black 15 16 1 c black 16 17 1 c black 16 20 1 c black 16 21 1 c black 16 22 1 c black 18 20 1 c black 19 20 1 c black 19 28 1 c black 22 23 1 c black 22 25 1 c black 22 26 1 c black 23 24 1 c black 26 27 1 c black 26 28 1 c black 26 31 1 c black 26 38 1 c black 28 29 1 c black 29 30 1 c black 31 32 1 c black 31 36 1 c black 32 33 1 c black 32 34 1 c black 33 34 1 c black 34 35 1 c black 36 37 1 c black 38 39 1 c black 38 40 1 c blackSocNetV-3.7/src/data/Stephenson_Zelen_5actors_6edges_IC_test_dataset.paj000066400000000000000000000006331524402210300263720ustar00rootroot00000000000000*Network Stephenson_and_Zelen_5_actors_6edges *Vertices 5 1 "1" ic red 0.226804 0.365782 circle 2 "2" ic red 0.745214 0.365782 circle 3 "3" ic red 0.758468 0.724189 circle 4 "4" ic red 0.226804 0.724189 circle 5 "5" ic red 0.480118 0.10472 circle *Matrix :1 non-weighted 0 1 0 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 0 1 1 0 0 0 *Matrix :2 weighted 0 2 0 1 5 2 0 1 0 5 0 1 0 10 0 1 0 10 0 0 5 5 0 0 0 SocNetV-3.7/src/data/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj000066400000000000000000000022131524402210300304270ustar00rootroot00000000000000*Network Dunbar&Dunbar_Gelada_baboon_colony_H22a *Vertices 12 1 "Adult Female" ic RGB729FCF 0.223061 0.329258 circle 2 "2 years Male" ic RGB729FCF 0.212487 0.530562 circle 3 "Adult Female" ic RGB729FCF 0.426989 0.427873 circle 4 "Adult Female" ic RGB729FCF 0.341893 0.414018 circle 5 "Adult Male" ic RGB729FCF 0.348943 0.243684 circle 6 "3 years Female" ic RGB729FCF 0.475327 0.271394 circle 7 "3 years Male" ic RGB729FCF 0.632931 0.323553 circle 8 "Adult Female" ic RGB729FCF 0.63142 0.444988 circle 9 "1 year Male" ic RGB729FCF 0.571501 0.554197 circle 10 "3 years Female" ic RGB729FCF 0.486908 0.604727 circle 11 "2 years Female" ic RGB729FCF 0.405337 0.581092 circle 12 "1 year Male" ic RGB729FCF 0.331319 0.550937 circle *Arcs *Edges 1 2 11 c #666666 1 4 5 c #666666 1 5 9 c #666666 1 11 2 c #666666 1 12 1 c #666666 3 4 30 c #666666 3 5 4 c #666666 3 6 3 c #666666 3 7 1 c #666666 3 10 8 c #666666 3 11 6 c #666666 3 12 3 c #666666 4 5 10 c #666666 4 6 2 c #666666 4 7 3 c #666666 4 10 1 c #666666 4 12 3 c #666666 5 6 20 c #666666 5 8 3 c #666666 6 10 5 c #666666 6 12 1 c #666666 7 8 17 c #666666 8 9 2 c #666666 8 10 7 c #666666SocNetV-3.7/src/data/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl000066400000000000000000000013631524402210300265600ustar00rootroot00000000000000DL N=16 FORMAT = FULLMATRIX DIAGONAL PRESENT ROW LABELS: ABN AMRO ENNIA NS BUHRT AGO AKZO NB SHV FGH HEINK PHLPS NATND OGEM RSV NSU COLUMN LABELS: ABN AMRO ENNIA NS BUHRT AGO AKZO NB SHV FGH HEINK PHLPS NATND OGEM RSV NSU DATA: 0 0 0 1 2 1 2 1 1 1 2 1 4 0 0 0 0 0 3 2 1 2 1 2 2 0 3 1 2 1 2 0 0 3 0 3 1 0 1 0 1 0 0 0 0 1 1 0 1 2 3 0 0 0 1 1 2 0 0 0 1 0 2 0 2 1 1 0 0 0 1 0 0 1 0 0 0 1 0 0 1 2 0 0 0 0 0 2 1 0 1 1 0 0 0 0 2 1 1 1 1 0 0 1 2 1 0 1 1 0 2 0 1 2 0 1 0 2 1 0 1 0 1 1 1 0 0 0 1 2 1 2 0 1 2 1 0 0 0 0 1 0 1 0 1 0 0 0 1 0 1 0 0 0 0 1 0 1 0 0 2 3 0 0 0 1 0 1 0 0 0 1 0 1 1 0 1 1 0 0 0 1 1 1 0 1 1 0 1 0 1 0 4 2 0 1 0 0 1 1 1 0 0 1 0 0 0 0 0 1 1 0 1 0 0 0 0 1 1 0 0 0 1 0 0 2 1 2 0 0 2 0 1 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0SocNetV-3.7/src/data/Stokman_Ziegler_Corporate_Interlocks_West_Germany.dl000066400000000000000000000013101524402210300267050ustar00rootroot00000000000000DL N=15 FORMAT = FULLMATRIX DIAGONAL PRESENT ROW LABELS: VAG DEUBK ALINZ SIEMN RUHRK DIMLR HAPAG KRUPP RWE KREDT THYSN MANES DRESB KARST VEBA COLUMN LABELS: VAG DEUBK ALINZ SIEMN RUHRK DIMLR HAPAG KRUPP RWE KREDT THYSN MANES DRESB KARST VEBA DATA: 0 2 1 0 2 0 0 2 2 2 2 1 1 1 0 2 0 3 3 1 4 2 0 2 1 1 2 0 2 0 1 3 0 6 1 2 2 1 2 0 2 2 1 1 0 0 3 6 0 2 2 1 0 0 0 4 3 1 0 0 2 1 1 2 0 1 1 2 1 1 2 1 1 0 0 0 4 2 2 1 0 1 2 2 0 2 0 1 0 0 0 2 2 1 1 1 0 1 1 0 1 0 2 1 0 2 0 1 0 2 2 1 0 2 1 2 0 2 0 0 2 2 2 0 1 2 1 2 0 3 3 0 1 1 0 2 1 0 0 1 0 0 1 3 0 3 1 0 1 0 2 1 2 4 2 2 1 2 3 3 0 0 1 0 0 1 2 2 3 1 0 0 0 0 1 0 0 0 0 0 1 0 1 1 1 1 2 2 1 0 1 0 0 1 0 1 2 1 0 0 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0SocNetV-3.7/src/data/Thurman_Office_Networks_Coalitions.dl000066400000000000000000000022521524402210300236700ustar00rootroot00000000000000DL N=15 NM=2 FORMAT = FULLMATRIX DIAGONAL PRESENT ROW LABELS: ANN AMY KATY BILL PETE TINA ANDY LISA PRESIDENT MINNA MARY EMMA ROSE MIKE PEG COLUMN LABELS: ANN AMY KATY BILL PETE TINA ANDY LISA PRESIDENT MINNA MARY EMMA ROSE MIKE PEG LEVEL LABELS: THURA THURM DATA: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 1 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 1 0 1 1 0 1 0 1 0 0 1 0 1 1 1 1 0 1 0 0 0 0 0 0 0 1 1 0 0 1 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 0 0 1 1 1 0 0 1 1 1 1 0 0 1 0 0 0 1 1 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 0 0 0 0 1 1 1 0 1 1 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 1 1 1 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0SocNetV-3.7/src/data/TinyAdj_Dir_N3_E4_clucof.adj000066400000000000000000000000221524402210300214320ustar00rootroot000000000000000 0 1 1 0 1 1 1 0 SocNetV-3.7/src/data/TinyAdj_Undir_N3.adj000066400000000000000000000001061524402210300201150ustar00rootroot00000000000000# unweighted, undirected, symmetric, diagonal zeros 0 1 0 1 0 1 0 1 0 SocNetV-3.7/src/data/TinyAdj_Weighted_Dir_N3.adj000066400000000000000000000001171524402210300213740ustar00rootroot00000000000000# weighted, non-symmetric β†’ directed, diagonal zeros 0 2.5 0 0 0 1.5 3.0 0 0 SocNetV-3.7/src/data/TinyArc_Dir_N2_E1.paj000066400000000000000000000000441524402210300201220ustar00rootroot00000000000000*Vertices 2 1 "A" 2 "B" *Arcs 1 2 1 SocNetV-3.7/src/data/TinyComplete_Undir_N4_E6.paj000066400000000000000000000001171524402210300215400ustar00rootroot00000000000000*Vertices 4 1 "A" 2 "B" 3 "C" 4 "D" *Edges 1 2 1 1 3 1 1 4 1 2 3 1 2 4 1 3 4 1 SocNetV-3.7/src/data/TinyDL_TwoMode_3x3_labeled.dl000066400000000000000000000001741524402210300216660ustar00rootroot00000000000000DL NR=3 NC=3 FORMAT=FULLMATRIX ROW LABELS: person1 person2 person3 COL LABELS: event1 event2 event3 DATA: 1 1 0 1 0 1 0 1 1 SocNetV-3.7/src/data/TinyDL_TwoMode_3x3_nolabels.dl000066400000000000000000000000671524402210300220760ustar00rootroot00000000000000DL NR=3 NC=3 FORMAT=FULLMATRIX DATA: 1 1 0 1 0 1 0 1 1 SocNetV-3.7/src/data/TinyDirChain_N3.paj000066400000000000000000000000601524402210300177520ustar00rootroot00000000000000*Vertices 3 1 "A" 2 "B" 3 "C" *Arcs 1 2 1 2 3 1 SocNetV-3.7/src/data/TinyDirWeighted_N3.paj000066400000000000000000000000601524402210300204700ustar00rootroot00000000000000*Vertices 3 1 "A" 2 "B" 3 "C" *Arcs 1 2 2 2 3 3 SocNetV-3.7/src/data/TinyDir_N2_E1_Attributes.graphml000066400000000000000000000045151524402210300224320ustar00rootroot00000000000000 0.0 0.0 10 red circle #8d8d8d 8 1.0 #666666 0.472727 0.480601 #ff0000 circle 40 Left 0.269421 0.301627 #ff0000 circle 35 1 SocNetV-3.7/src/data/TinyDir_N3_REL2.graphml000066400000000000000000000057231524402210300204660ustar00rootroot00000000000000 0.0 0.0 10 red circle #8d8d8d 8 1.0 #666666 0.285861 0.469761 circle 0.484631 0.261603 circle 0.628074 0.447257 circle 1 1 0.285861 0.469761 circle 0.484631 0.261603 circle 0.628074 0.447257 circle 1 1 SocNetV-3.7/src/data/TinyDisconnected_Dir_N5_E3.paj000066400000000000000000000001021524402210300220170ustar00rootroot00000000000000*Vertices 5 1 "A" 2 "B" 3 "C" 4 "D" 5 "E" *Arcs 1 2 1 2 3 1 4 5 1 SocNetV-3.7/src/data/TinyDisconnected_Undir_N6_E4.paj000066400000000000000000000001111524402210300223640ustar00rootroot00000000000000*Vertices 6 1 "A" 2 "B" 3 "C" 4 "D" 5 "E" 6 "F" *Edges 1 2 1 2 3 1 4 5 1 SocNetV-3.7/src/data/TinyEdgeList_Simple_Dir_N3.lst000066400000000000000000000000611524402210300221310ustar00rootroot00000000000000# unweighted Directed chain of length 2. 1 2 2 3 SocNetV-3.7/src/data/TinyEdgeList_Symmetric_Undir_N3.lst000066400000000000000000000000241524402210300232160ustar00rootroot00000000000000# 1 2 2 1 2 3 3 2 SocNetV-3.7/src/data/TinyEdgeList_Weighted_Dir_N3.wlst000066400000000000000000000000671524402210300226350ustar00rootroot00000000000000# Format: source target weight 1 2 2.5 2 3 1.5 3 1 3.0 SocNetV-3.7/src/data/TinyGML_Dir_N3.gml000066400000000000000000000003231524402210300175150ustar00rootroot00000000000000graph [ directed 1 node [ id 1 label "1" ] node [ id 2 label "2" ] node [ id 3 label "3" ] edge [ source 1 target 2 ] edge [ source 2 target 3 ] ] SocNetV-3.7/src/data/TinyGML_Weighted_Dir_N3.gml000066400000000000000000000004061524402210300213370ustar00rootroot00000000000000graph [ directed 1 node [ id 1 label "1" ] node [ id 2 label "2" ] node [ id 3 label "3" ] edge [ source 1 target 2 value 2.5 ] edge [ source 2 target 3 value 1.5 ] edge [ source 3 target 1 value 3.0 ] ] SocNetV-3.7/src/data/TinyGraphML_Dir_N3.graphml000066400000000000000000000046431524402210300212540ustar00rootroot00000000000000 0.0 0.0 10 red circle #8d8d8d 8 1.0 #666666 0.311475 0.272855 circle 0.568648 0.264416 circle 0.330943 0.639944 circle 1 1 1 1 SocNetV-3.7/src/data/TinyGraphML_Undir_N3.graphml000066400000000000000000000043371524402210300216170ustar00rootroot00000000000000 0.0 0.0 10 red circle #8d8d8d 8 1.0 #666666 0.311475 0.272855 circle 0.568648 0.264416 circle 0.330943 0.639944 circle 1 1 SocNetV-3.7/src/data/TinyGraphML_Weighted_Dir_N3.graphml000066400000000000000000000013361524402210300230700ustar00rootroot00000000000000 2.5 1.5 3.0 SocNetV-3.7/src/data/TinyGraphviz_Dir_N3.dot000066400000000000000000000001361524402210300207010ustar00rootroot00000000000000# Directed, No weights, no attributes digraph TinyGraphviz_Dir_N3 { 1 -> 2; 2 -> 3; } SocNetV-3.7/src/data/TinyGraphviz_Undir_N3.dot000066400000000000000000000000701524402210300212410ustar00rootroot00000000000000graph TinyGraphviz_Undir_N3 { 1 -- 2; 2 -- 3; } SocNetV-3.7/src/data/TinyGraphviz_Weighted_Dir_N3.dot000066400000000000000000000001641524402210300225220ustar00rootroot00000000000000digraph TinyGraphviz_Weighted_Dir_N3 { 1 -> 2 [weight=2.5]; 2 -> 3 [weight=1.5]; 3 -> 1 [weight=3.0]; } SocNetV-3.7/src/data/TinyIsolated_Dir_N2_E0.paj000066400000000000000000000000361524402210300211610ustar00rootroot00000000000000*Vertices 2 1 "A" 2 "B" *Arcs SocNetV-3.7/src/data/TinyIsolated_Undir_N3_E0.paj000066400000000000000000000000451524402210300215250ustar00rootroot00000000000000*Vertices 3 1 "A" 2 "B" 3 "C" *Edges SocNetV-3.7/src/data/TinyPajek_Comma_Label_Quoted_N2.net000066400000000000000000000003451524402210300231000ustar00rootroot00000000000000% Test fixture for issue #233: quoted node labels with spaces/commas/special chars *Network TinyPajek_Comma_Label_Quoted_N2 *Vertices 2 1 "Bureau of Alcohol, Tobacco, & Firearms" 2 "Federal Bureau of Investigation" *Arcs 1 2 1 SocNetV-3.7/src/data/TinyPath_N3_E2.paj000066400000000000000000000000621524402210300175150ustar00rootroot00000000000000*Vertices 3 1 "A" 2 "B" 3 "C" *Edges 1 2 1 2 3 1 SocNetV-3.7/src/data/TinyWeaklyConn_Dir_N3_E2.paj000066400000000000000000000000601524402210300214670ustar00rootroot00000000000000*Vertices 3 1 "A" 2 "B" 3 "C" *Arcs 1 2 1 3 2 1 SocNetV-3.7/src/data/TinyWeightedIsolate_Undir_N4_E2.paj000066400000000000000000000000731524402210300230460ustar00rootroot00000000000000*Vertices 4 1 "A" 2 "B" 3 "C" 4 "D" *Edges 1 2 2.5 2 3 1.5 SocNetV-3.7/src/data/Wasserman_Faust_7actors_star_circle_line_graphs.paj000066400000000000000000000014621524402210300265650ustar00rootroot00000000000000*Network 7actors-wasserman-test-net-all *Vertices 7 1 "1" ic red 0.441826 0.426254 circle 2 "2" ic red 0.584683 0.19469 circle 3 "3" ic red 0.71134 0.417404 circle 4 "4" ic red 0.664212 0.687316 circle 5 "5" ic red 0.310751 0.70944 circle 6 "6" ic red 0.157585 0.427729 circle 7 "7" ic red 0.248895 0.193215 circle *Matrix :1 star 0 1 1 1 1 1 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 *Matrix :2 circle 0 1 0 0 0 0 1 1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 1 0 0 0 0 1 0 *Matrix :3 line 0 1 1 0 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0SocNetV-3.7/src/data/Wasserman_Faust_Countries_Trade_Data_Basic_Manufactured_Goods.pajek000066400000000000000000000035561524402210300315610ustar00rootroot00000000000000*Network Countries_Trade_Basic_Manufactured_Goods *Vertices 24 1 "ALG" 0.5408 0.0347 2 "ARG" 0.9195 0.1080 3 "BRA" 0.7626 0.4348 4 "CHI" 0.5190 0.2900 5 "CZE" 0.4734 0.5176 6 "ECU" 0.9669 0.3401 7 "EGY" 0.1749 0.9478 8 "ETH" 0.4757 0.9701 9 "FIN" 0.6789 0.5941 10 "HON" 0.9499 0.6624 11 "IND" 0.0638 0.2404 12 "ISR" 0.6606 0.1142 13 "JAP" 0.4718 0.4038 14 "LIB" 0.9210 0.9313 15 "MAD" 0.7077 0.9150 16 "NZ" 0.0501 0.6893 17 "PAK" 0.3653 0.3211 18 "SPA" 0.6454 0.3687 19 "SWI" 0.5480 0.7162 20 "SYR" 0.2465 0.0501 21 "TAI" 0.3805 0.6520 22 "UK" 0.5921 0.4555 23 "US" 0.5464 0.5983 24 "YUG" 0.3576 0.4845 *Matrix :3 "ws6 - Basic manufactured goods" 0 0 0 1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 1 0 1 1 0 1 0 0 1 0 1 1 1 0 0 0 1 1 1 0 1 0 1 0 1 1 0 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 1 0 0 0 1 0 0 0 0 1 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 1 1 0 1 0 1 0 0 0 1 0 0 1 1 1 1 0 1 1 1 1 0 1 0 0 0 0 0 1 1 0 0 0 1 0 0 1 0 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 0 1 0 0 0 1 0 1 0 0 0 1 1 0 0 1 1 1 1 0 0 0 1 1 0 0 0 1 0 1 0 1 1 0 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 1 1 1 0 0 1 1 1 1 1 0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 1 1 0 1 1 1 0 1 1 1 0 0 1 1 1 1 1 1 1 1 0SocNetV-3.7/src/data/Zachary_Karate_Club.dl000066400000000000000000000111411524402210300205510ustar00rootroot00000000000000DL N=34 NM=2 FORMAT = FULLMATRIX DIAGONAL PRESENT LEVEL LABELS: ZACHE ZACHC DATA: 0 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 1 0 1 1 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0 1 1 0 1 0 0 0 1 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1 0 1 1 1 0 0 0 0 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 1 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 1 0 0 1 0 1 0 1 1 0 0 0 0 0 1 1 1 0 1 0 0 0 0 0 0 0 0 1 1 0 0 0 1 1 1 0 0 1 1 1 0 1 1 0 0 1 1 1 1 1 1 1 0 0 4 5 3 3 3 3 2 2 0 2 3 1 3 0 0 0 2 0 2 0 2 0 0 0 0 0 0 0 0 0 2 0 0 4 0 6 3 0 0 0 4 0 0 0 0 0 5 0 0 0 1 0 2 0 2 0 0 0 0 0 0 0 0 2 0 0 0 5 6 0 3 0 0 0 4 5 1 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 0 0 0 2 0 3 3 3 0 0 0 0 3 0 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 2 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 5 0 0 0 3 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 2 5 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 4 4 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 3 4 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 5 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 4 0 0 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 1 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 0 4 0 3 0 0 5 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 3 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 2 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 2 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 3 0 0 0 0 0 0 0 0 4 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 4 0 0 0 0 0 4 2 0 2 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 7 0 0 2 0 0 0 4 4 0 0 2 0 0 0 0 0 3 0 0 0 0 0 3 3 0 0 1 0 3 0 2 5 0 0 0 0 0 4 3 4 0 5 0 0 0 0 0 0 0 0 4 2 0 0 0 3 2 4 0 0 2 1 1 0 3 4 0 0 2 4 2 2 3 4 5 0SocNetV-3.7/src/engine/000077500000000000000000000000001524402210300147315ustar00rootroot00000000000000SocNetV-3.7/src/engine/distance_engine.cpp000066400000000000000000001626421524402210300205670ustar00rootroot00000000000000/** * @file distance_engine.cpp * @brief Implements the DistanceEngine class for computing geodesic distances and centralities in the graph. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2025 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "engine/distance_engine.h" #include "graph.h" #include "engine/graph_distance_progress_sink.h" #include "engine/thread_local_state.h" #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(lcEngine, "socnetv.engine") /** * @brief Per-run scratch state for DistanceEngine::compute(), scoped to one compute() call. * * Not per-source (see PerSourceScratch/ThreadLocalState in per_source_scratch.h / * thread_local_state.h for that) β€” this is the state threaded through compute()'s three * phases (initRun / runAllSources / finalize) that used to be a long list of local variables * before WS1's Phase C extraction. One instance lives on compute()'s stack for the whole call. */ struct DistanceScratch { // Iterators (kept exactly as locals were) VList::const_iterator it; VList::const_iterator it1; QList::const_iterator it2; // Indices / counters int w = 0, u = 0, s = 0, si = 0, ui = 0, wi = 0; int progressCounter = 0; // Graph size snapshot int N = 0; int E = 0; // UI message QString pMsg; // Scratch scalars used across phases qreal distances_sum_for_s = 0; qreal maxEdgeWeightInNetwork = 0; qreal tempEdgeWeight = 0; // Used during finalize/connectivity scan qreal pairDistance = 0; }; /** * @brief Per-run scratch for centrality values computed once per SSSP source, before the * parallel per-source loop existed to own its own copy (see ThreadLocalState::pss for the * per-thread equivalent used inside runAllSources()'s blockingMap lambda). * * Retained as a distinct type from CentralityScratchFinalize because these fields are * meaningful only during the single-source SSSP pass itself, not the finalize/aggregation * phase that runs once after all sources are done. */ struct CentralityScratchSSSP { // Per-source values computed inside the SSSP loop qreal CC = 0; qreal PC = 0; qreal SPC = 0; // Brandes / dependency scratch qreal sigma_u = 0; qreal sigma_w = 0; qreal delta_u = 0; qreal delta_w = 0; qreal d_sw = 0; qreal d_su = 0; // Power Centrality iterator H_f_i::const_iterator hfi; }; /** * @brief Scratch for the finalize() phase β€” the single-threaded pass that runs once after * runAllSources() completes, scanning every vertex to compute connectivity, group-level * aggregates, and normalised (standardised) centrality scores from the raw per-vertex values * runAllSources() already wrote. Distinct from CentralityScratchSSSP because these values are * only meaningful for this one post-loop scan, not during the per-source SSSP pass itself. */ struct CentralityScratchFinalize { // Values used while scanning vertices and aggregating qreal CC = 0, BC = 0, SC = 0, eccentricity = 0, EC = 0; qreal SCC = 0, SBC = 0, SSC = 0, SEC = 0, SPC = 0; // Variance temps used in the final aggregation loop(s) qreal tempVarianceBC = 0, tempVarianceSC = 0, tempVarianceEC = 0; qreal tempVarianceCC = 0, tempVariancePC = 0; }; DistanceEngine::DistanceEngine(Graph &g) : graph(g) { } /** * @brief Runs the full geodesic distance (and optionally centrality) computation pipeline. * * Orchestrates three phases: * - Phase 0 (Init): initialises scratch structures, resets aggregates, * handles the degenerate E==0 case. * - Phase 1+2 (SSSP loop): runs BFS or Dijkstra from every source vertex, * accumulating per-source distance and centrality data. * - Phase 3 (Finalize): connectivity scan, group-level aggregation, * normalisation of centrality scores. * * If the user cancels via the progress dialog, the function returns early * after Phase 1+2 without finalising, and @c calculatedDistances is left * @c false so the next call recomputes from scratch. * * @param computeCentralities If true, also computes BC, CC, SC, EC, PC. * @param considerWeights If true, uses edge weights (Dijkstra); otherwise BFS. * @param inverseWeights If true, uses 1/weight as the distance metric. * @param dropIsolates If true, excludes isolated vertices from all calculations. */ void DistanceEngine::compute(const bool computeCentralities, const bool considerWeights, const bool inverseWeights, const bool dropIsolates) { qCDebug(lcEngine) << "DistanceEngine::compute() - " << "centralities" << computeCentralities << "considerWeights:" << considerWeights << "inverseWeights:" << inverseWeights << "dropIsolates:" << dropIsolates; if (computeCentralities) { if (graph.calculatedCentralities) { return; } } else if (graph.calculatedDistances) { return; } DistanceScratch ds; CentralityScratchSSSP csssp; CentralityScratchFinalize csfin; // Routes progress/status/cancel notifications back to graph's Qt signals (see // graph_distance_progress_sink.h) - keeps this file free of any direct Qt-signal // dependency of its own. GraphDistanceProgressSink sink(graph); // ---- Phase 0/Init (includes E==0 handling) ---- initRun(computeCentralities, considerWeights, inverseWeights, dropIsolates, ds, csssp, csfin, sink); if (ds.E != 0) { // ---- Phase 1+2: SSSP loop + per-source accumulation ---- runAllSources(computeCentralities, considerWeights, inverseWeights, dropIsolates, ds, sink); if (sink.progressCanceled()) { qCDebug(lcEngine) << "DistanceEngine::compute() - canceled. Skipping finalize."; return; } // ---- Finalization: connectivity scan + aggregation ---- finalize(computeCentralities, dropIsolates, ds, csfin, sink); } graph.calculatedDistances = true; qCDebug(lcEngine) << "Graph::graphDistancesGeodesic()- FINISHED computing distances"; } void DistanceEngine::initRun(const bool computeCentralities, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, DistanceScratch &ds, CentralityScratchSSSP &csssp, CentralityScratchFinalize &csfin, IDistanceProgressSink &sink) { // drop isolated vertices from calculations (i.e. std C and group C). ds.N = graph.vertices(dropIsolates, false, true); ds.E = graph.edgesEnabled(); ds.pMsg = QObject::tr("Computing geodesic distances. \nPlease wait..."); sink.statusMessage(ds.pMsg); sink.resetCancellation(); graph.setSymmetricCached(graph.isSymmetric()); if (ds.E == 0) { // All pairs unreachable. E==0 means no source loop runs (compute() skips // runAllSources() entirely for E==0), so this is the only place that needs to // populate m_apspDist/m_apspSigma for this case. int totalV = 0; for (auto it = graph.verticesBegin(); it != graph.verticesEnd(); ++it) ++totalV; const int relation = graph.relationCurrent(); graph.m_apspDist[relation].resize(totalV, totalV); graph.m_apspSigma[relation].resize(totalV, totalV); graph.m_apspDist[relation].fillMatrix(RAND_MAX); // Sigma stays at 0 - Matrix::resize() zero-initializes every cell already. if (ds.N < 2) { // singleton graph consisting of a single isolated node // is considered connected graph.setConnectedCached(true); } else { // any non-empty and non-singleton graph with zero edges is disconnected graph.setConnectedCached(false); } } else { ds.distances_sum_for_s = 0; ds.maxEdgeWeightInNetwork = 0; ds.tempEdgeWeight = 0; // ---- SSSP scratch ---- csssp.CC = 0; csssp.PC = 0; csssp.SPC = 0; csssp.sigma_u = 0; csssp.sigma_w = 0; csssp.delta_u = 0; csssp.delta_w = 0; csssp.d_sw = 0; csssp.d_su = 0; // ---- Finalize scratch ---- csfin.CC = 0; csfin.BC = 0; csfin.SC = 0; csfin.eccentricity = 0; csfin.EC = 0; csfin.SCC = 0; csfin.SBC = 0; csfin.SSC = 0; csfin.SEC = 0; csfin.SPC = 0; csfin.tempVarianceBC = 0; csfin.tempVarianceSC = 0; csfin.tempVarianceEC = 0; csfin.tempVarianceCC = 0; csfin.tempVariancePC = 0; ds.pairDistance = 0; graph.setConnectedCached(true); graph.maxSCC = 0; graph.minSCC = RAND_MAX; graph.nomSCC = 0; graph.denomSCC = 0; graph.groupCC = 0; graph.maxNodeSCC = 0; graph.minNodeSCC = 0; graph.sumSCC = 0; graph.sumCC = 0; graph.discreteCCs.clear(); graph.classesSCC = 0; graph.maxSBC = 0; graph.minSBC = RAND_MAX; graph.nomSBC = 0; graph.denomSBC = 0; graph.groupSBC = 0; graph.maxNodeSBC = 0; graph.minNodeSBC = 0; graph.sumBC = 0; graph.sumSBC = 0; graph.discreteBCs.clear(); graph.classesSBC = 0; graph.maxSSC = 0; graph.minSSC = RAND_MAX; graph.groupSC = 0; graph.maxNodeSSC = 0; graph.minNodeSSC = 0; graph.sumSC = 0; graph.sumSSC = 0; graph.discreteSCs.clear(); graph.classesSSC = 0; graph.maxSPC = 0; graph.minSPC = RAND_MAX; graph.nomSPC = 0; graph.denomSPC = 0; graph.groupSPC = 0; graph.maxNodeSPC = 0; graph.minNodeSPC = 0; graph.sumSPC = 0; graph.sumPC = 0; graph.discretePCs.clear(); graph.classesSPC = 0; graph.maxEccentricity = 0; graph.minEccentricity = RAND_MAX; graph.maxNodeEccentricity = 0; graph.minNodeEccentricity = 0; graph.discreteEccentricities.clear(); graph.classesEccentricity = 0; graph.maxEC = 0; graph.minEC = RAND_MAX; graph.nomEC = 0; graph.denomEC = 0; graph.groupEC = 0; graph.maxNodeEC = 0; graph.minNodeEC = 0; graph.sumEC = 0; graph.discreteECs.clear(); graph.classesEC = 0; graph.calculatedDistances = false; graph.resetDistanceAggregates(); // Stores vertex pairs not connected // Vertices in keys have // Infinite Eccentricity // Zero Eccentricity Centrality // Zero Closeness Centrality graph.notConnectedPairsClear(); for (ds.it = graph.verticesBegin(); ds.it != graph.verticesEnd(); ++ds.it) { for (ds.it1 = graph.verticesBegin(); ds.it1 != graph.verticesEnd(); ++ds.it1) { if (considerWeights && inverseWeights) { // find the max weight in the network. // it will be used for maxCC below ds.tempEdgeWeight = (*ds.it)->hasEdgeTo((*ds.it1)->number()); if (ds.tempEdgeWeight > ds.maxEdgeWeightInNetwork) { ds.maxEdgeWeightInNetwork = ds.tempEdgeWeight; } } } // Zero centrality scores for each vertex if (computeCentralities) { (*ds.it)->setBC(0.0); (*ds.it)->setSC(0.0); (*ds.it)->setEccentricity(0.0); (*ds.it)->setEC(0.0); (*ds.it)->setCC(0.0); (*ds.it)->setIRCC(0.0); (*ds.it)->setPC(0.0); } } if (graph.symmetricCached()) { graph.maxIndexBC = (ds.N == 2) ? 1 : (ds.N - 1.0) * (ds.N - 2.0) / 2.0; graph.maxIndexSC = (ds.N == 2) ? 1 : (ds.N - 1.0) * (ds.N - 2.0) / 2.0; graph.maxIndexCC = ds.N - 1.0; graph.maxIndexPC = ds.N - 1.0; } else { graph.maxIndexBC = (ds.N == 2) ? 1 : (ds.N - 1.0) * (ds.N - 2.0); // fix N=2 case where maxIndex becomes zero graph.maxIndexSC = (ds.N == 2) ? 1 : (ds.N - 1.0) * (ds.N - 2.0); graph.maxIndexPC = ds.N - 1.0; graph.maxIndexCC = ds.N - 1.0; } if (considerWeights && inverseWeights) { graph.maxIndexCC = graph.maxIndexCC * (1.0 / ds.maxEdgeWeightInNetwork); } } } void DistanceEngine::runAllSources(const bool computeCentralities, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, DistanceScratch &ds, IDistanceProgressSink &sink) { qCDebug(lcEngine) << "*********** MAIN LOOP (parallel): " "solving SSSP from every source vertex across CPU cores..."; // Count total VList positions (includes disabled vertices) for scratch-array sizing. // Scratch indices come from vertexIndexByNumber(), which returns VList positions, // so arrays must be sized by totalV (not ds.N which counts enabled-only). int totalV = 0; for (auto it = graph.verticesBegin(); it != graph.verticesEnd(); ++it) ++totalV; // WS5 A2: relation-keyed flat-matrix APSP storage, resized fresh for this run (same // always-reconstruct convention every other Graph matrix field already follows - see // createMatrixAdjacency()/graphMatrixDistanceGeodesicCreate()). Written alongside the // per-vertex QHash writes below; nothing reads from it yet. const int relation = graph.relationCurrent(); graph.m_apspDist[relation].resize(totalV, totalV); graph.m_apspSigma[relation].resize(totalV, totalV); // Over-allocate the state vector generously. The pool has at most // idealThreadCount() workers; doubling + 4 guards against unexpected // pool growth (e.g., other Qt internals adding threads mid-run) without // wasting meaningful memory on the per-thread scratch arrays. int threadCount = QThread::idealThreadCount(); QVector allStates(threadCount * 2 + 4); for (auto &tls : allStates) tls.allocate(totalV); // Collect enabled source vertices upfront so the lambda receives a plain value // (QPair) instead of iterating a shared container from multiple threads. // Element: (vertex_number, VList_position). QVector> sources; sources.reserve(ds.N); for (auto it = graph.verticesBegin(); it != graph.verticesEnd(); ++it) { if ((*it)->isEnabled()) sources.append({(*it)->number(), graph.vertexIndexByNumber((*it)->number())}); } // Thread-ID β†’ slot map: guarantees each distinct OS thread gets a unique, stable // slot index into allStates across all lambda invocations within this call. // Using a per-call map (not thread_local) avoids the "stale slot" bug that occurs // when runAllSources is called multiple times (e.g., warmup + bench runs): // thread_local would carry over a slot index from the previous call's allStates, // and any newly created pool thread would receive the same index β€” data race. QMutex slotMutex; QHash threadSlots; QAtomicInt nextSlot{0}; // ---- Parallel SSSP source loop ---- // Safety analysis: // - Source-vertex writes (APSP, CC, eccentricity, PC, SPC): safe β€” si is unique. // - Intermediate-vertex writes (BC, SC): unsafe β†’ go to tls.partialBC / tls.partialSC. // - Graph-wide aggregates (distanceSum, geodesicsCount, diameter, sumPC, sumSPC): // unsafe β†’ go to tls accumulators; reduced into graph state after the map. // - All graph reads (edges, vertex numbers, relation) are read-only during SSSP. // - Cancel signals cannot be delivered while graphThread's event loop is blocked // here, so we skip the cancel check inside the lambda; UX shows 0%β†’100% jump. QtConcurrent::blockingMap(sources, [&](const QPair &src) { // Map this OS thread to a unique slot on first entry; reuse the slot on // subsequent invocations (same thread processes multiple sources). // slotMutex is held only for the brief hash lookup β€” never during SSSP. int mySlot; { QMutexLocker lock(&slotMutex); Qt::HANDLE me = QThread::currentThreadId(); auto it = threadSlots.find(me); if (it != threadSlots.end()) { mySlot = it.value(); } else { mySlot = nextSlot.fetchAndAddOrdered(1); threadSlots[me] = mySlot; } } ThreadLocalState &tls = allStates[mySlot]; const int s = src.first; const int si = src.second; qCDebug(lcEngine) << "***** PHASE 1 (SSSP) [thread slot" << mySlot << "]: source s" << s << "vpos" << si; // Reset per-source scratch (dist, sigma, and optionally Stack/Ps/nthOrder). // Also resets pss.sourceDistanceSum / sourceGeodesicsCount / sourceDiameter. tls.pss.resetPerSource(computeCentralities); // Run BFS or Dijkstra; unsafe graph calls go to tls.pss scratch fields / tls.partialSC. if (!considerWeights) bfsSSSP(s, si, computeCentralities, dropIsolates, tls.pss, tls.partialSC); else dijkstraSSSP(s, si, computeCentralities, inverseWeights, dropIsolates, tls.pss, tls.partialSC); // Accumulate per-source aggregates into thread-local running totals. // These will be reduced into graph-global state after the parallel loop. tls.totalDistanceSum += tls.pss.sourceDistanceSum; tls.totalGeodesicsCount += tls.pss.sourceGeodesicsCount; if (tls.pss.sourceDiameter > tls.maxDiameter) tls.maxDiameter = tls.pss.sourceDiameter; qCDebug(lcEngine) << "***** PHASE 1 (SSSP): FINISHED BFS/DIJKSTRA for s" << s << "β€” writing APSP results back to vertex" << si; // APSP write-back: persist tls.pss.dist / sigma into row si of the flat matrices. // Safe: si is unique across all concurrent lambda invocations, so no two sources ever // write the same row. Unconditional (every column vi, not just reached ones) - // tls.pss.dist[vi] already holds RAND_MAX for every unreached vi // (PerSourceScratch::resetPerSource() fills it before every source, unconditionally), // so this isn't new work - it reuses a reset that was already happening. for (int vi = 0; vi < totalV; ++vi) { graph.m_apspDist[relation].setItem(si, vi, tls.pss.dist[vi]); graph.m_apspSigma[relation].setItem(si, vi, (qreal)tls.pss.sigma[vi]); } if (computeCentralities) { // ---- Power Centrality ---- // PC(s) = [1/(N-1)] * sum_i( nthOrder[i] / i ) // where nthOrder[i] = number of nodes at distance i from s. tls.pss.componentSize = 1; qreal pc = 0; for (auto hfi = tls.pss.nthOrder.constBegin(); hfi != tls.pss.nthOrder.constEnd(); ++hfi) { pc += (1.0 / hfi.key()) * hfi.value(); tls.pss.componentSize += hfi.value(); } graph.vertexAtIndex(si)->setPC(pc); // safe: source vertex // Accumulate into thread-local sum; graph.sumPC updated in reduction. tls.totalSumPC += pc; qreal spc = (tls.pss.componentSize != 1) ? (1.0 / (tls.pss.componentSize - 1.0)) * pc : 0; graph.vertexAtIndex(si)->setSPC(spc); // safe: source vertex tls.totalSumSPC += spc; qCDebug(lcEngine) << "***** PHASE 2 (CENTRALITIES): s" << s << "vpos" << si << "PC" << pc; // ---- Closeness Centrality + delta reset ---- // Walk every vertex position to zero delta[] (needed for BC back-propagation) // and simultaneously sum distances for CC. RAND_MAX propagates the // disconnected-graph sentinel so CC becomes 0 when s cannot reach all others. qreal distances_sum_for_s = 0; for (int vi1 = 0; vi1 < totalV; ++vi1) { tls.pss.delta[vi1] = 0.0; distances_sum_for_s += tls.pss.dist[vi1]; } // Accumulate into thread-local total; graph.addToDistanceSum() in reduction. tls.totalCCDistanceSum += distances_sum_for_s; qreal cc = (distances_sum_for_s != 0 && distances_sum_for_s < RAND_MAX) ? 1.0 / distances_sum_for_s : 0; graph.vertexAtIndex(si)->setCC(cc); // safe: source vertex qCDebug(lcEngine) << "***** PHASE 2 (CENTRALITIES): s" << s << "vpos" << si << "CC" << cc; // ---- Brandes BC back-propagation ---- // Visit vertices in reverse BFS/Dijkstra order (deepest first) and propagate // dependency deltas up the shortest-path DAG. Instead of writing to // vertex->BC() directly (which would race across threads for intermediate // vertices), accumulate into tls.partialBC[wi]; the reduction step merges all. qCDebug(lcEngine) << "***** PHASE 2 (BC/ACCUMULATION): back-propagating from s" << s << "Stack size" << (int)tls.pss.Stack.size(); while (!tls.pss.Stack.empty()) { int w = tls.pss.Stack.top(); int wi = graph.vertexIndexByNumber(w); tls.pss.Stack.pop(); const QList &lst = tls.pss.Ps[wi]; for (int u : lst) { int ui = graph.vertexIndexByNumber(u); if (tls.pss.sigma[wi] > 0) { // delta[u] += (1 + delta[w]) * sigma[u] / sigma[w] tls.pss.delta[ui] += (1.0 + tls.pss.delta[wi]) * ((qreal)tls.pss.sigma[ui] / (qreal)tls.pss.sigma[wi]); } } if (w != s) { // Accumulate into per-thread partial BC instead of vertex->setBC(). // Intermediate vertex w may be processed by multiple source threads; // partialBC[wi] is private to this thread, so no mutex needed. tls.partialBC[wi] += tls.pss.delta[wi]; } } // END BC back-propagation } // END if computeCentralities }); // END QtConcurrent::blockingMap qCDebug(lcEngine) << "*********** MAIN LOOP (parallel SSSP): FINISHED. Starting reduction."; // ---- Sequential reduction ---- // Merge per-thread accumulators into graph-global state. This runs on the calling // thread after blockingMap returns; no concurrent access, no mutexes needed. for (auto &tls : allStates) { // Distance sum from BFS inner-loop discoveries (0 for Dijkstra). graph.addToDistanceSum(tls.totalDistanceSum); if (computeCentralities) // Distance sum from the CC-denominator accumulation in the centralities block. graph.addToDistanceSum(tls.totalCCDistanceSum); // Geodesics count (reachable source-target pairs found by BFS / Dijkstra). graph.addGeodesicsCount(tls.totalGeodesicsCount); // Diameter: keep the overall maximum across all threads. if (tls.maxDiameter > graph.graphDiameterCached()) graph.setDiameterCached(tls.maxDiameter); if (computeCentralities) { graph.sumPC += tls.totalSumPC; graph.sumSPC += tls.totalSumSPC; } } // Reduce partial BC and SC arrays into per-vertex scores. // BC/SC were initialised to 0 in initRun; we accumulate all thread contributions here. if (computeCentralities) { for (int wi = 0; wi < totalV; ++wi) { qreal totalBC = 0, totalSC = 0; for (auto &tls : allStates) { totalBC += tls.partialBC[wi]; totalSC += tls.partialSC[wi]; } if (totalBC != 0.0) graph.vertexAtIndex(wi)->setBC(totalBC); if (totalSC != 0.0) graph.vertexAtIndex(wi)->setSC(totalSC); } } qCDebug(lcEngine) << "*********** MAIN LOOP (SSSP problem): FINISHED."; } void DistanceEngine::finalize(const bool computeCentralities, const bool dropIsolates, DistanceScratch &ds, CentralityScratchFinalize &csf, IDistanceProgressSink &sink) { Q_UNUSED(sink); // progress dialog is managed by compute(); finalize() has no steps to report // check if there are disconnected nodes // and get the distance sums qCDebug(lcEngine) << "Checking if there are disconnected nodes"; graph.setConnectedCached(true); // WS5 A2: this pair loop is O(N^2) - the actual hot path A2 targets. i is looked up once per // outer iteration (O(N) total, negligible); the inner loop iterates positions directly // (vertexAtIndex(j), not an iterator + a per-pair position lookup), so each pair read is a // genuine O(1) matrix access with no hash lookup involved. int totalV = 0; for (auto it = graph.verticesBegin(); it != graph.verticesEnd(); ++it) ++totalV; const int relation = graph.relationCurrent(); for (ds.it = graph.verticesBegin(); ds.it != graph.verticesEnd(); ++ds.it) { if (!(*ds.it)->isEnabled()) { qCDebug(lcEngine) << "actor i" << (*ds.it)->number() << "disabled. SKIP/CONTINUE"; continue; } ds.pairDistance = 0; const int i = graph.vertexIndexByNumber((*ds.it)->number()); for (int j = 0; j < totalV; ++j) { GraphVertex *v1 = graph.vertexAtIndex(j); if (!v1->isEnabled()) { qCDebug(lcEngine) << " actor j" << v1->number() << "disabled. SKIP/CONTINUE"; continue; } if (v1->number() == (*ds.it)->number()) { qCDebug(lcEngine) << " == actor j" << v1->number() << "SKIP/CONTINUE"; continue; } ds.pairDistance = graph.m_apspDist[relation].item(i, j); if (ds.pairDistance == RAND_MAX) { graph.notConnectedPairsInsert((*ds.it)->number(), v1->number()); (*ds.it)->setEccentricity(RAND_MAX); graph.setConnectedCached(false); qCDebug(lcEngine) << "actor i" << (*ds.it)->number() << "has infinite eccentricity. " "There is no path from it to actor j" << v1->number(); } else { qCDebug(lcEngine) << "actor i" << (*ds.it)->number() << "distanceSum" << (*ds.it)->distanceSum(); (*ds.it)->setDistanceSum((*ds.it)->distanceSum() + ds.pairDistance); } } // end for qCDebug(lcEngine) << "actor i" << (*ds.it)->number() << "Final distanceSum" << (*ds.it)->distanceSum(); if (computeCentralities) { // Compute Eccentricity (max geodesic distance) csf.eccentricity = (*ds.it)->eccentricity(); qCDebug(lcEngine) << "actor" << (*ds.it)->number() << "eccentricity" << csf.eccentricity; if (csf.eccentricity != RAND_MAX) { // Find min/max Eccentricity graph.minmax(csf.eccentricity, (*ds.it), graph.maxEccentricity, graph.minEccentricity, graph.maxNodeEccentricity, graph.minNodeEccentricity); graph.resolveClasses(csf.eccentricity, graph.discreteEccentricities, graph.classesEccentricity, (*ds.it)->number()); // Eccentricity Centrality is the inverted Eccentricity csf.EC = 1.0 / csf.eccentricity; (*ds.it)->setEC(csf.EC); // Set Eccentricity Centrality (*ds.it)->setSEC(csf.EC); // Set std EC = EC graph.sumEC += csf.EC; // set sum EC qCDebug(lcEngine) << "actor i" << (*ds.it)->number() << "EC" << csf.EC; } else { csf.EC = 0; (*ds.it)->setEC(csf.EC); // Set Eccentricity Centrality (*ds.it)->setSEC(csf.EC); // Set std EC = EC graph.sumEC += csf.EC; // set sum EC qCDebug(lcEngine) << "actor i" << (*ds.it)->number() << "EC=0 (disconnected graph)"; } } // end if compute centralities } // end for disconnected checking // Compute average path length... if (graph.notConnectedPairsSize() == 0) { graph.setAverageDistanceCached(graph.graphSumDistanceCached() / (ds.N * (ds.N - 1.0))); qCDebug(lcEngine) << "Graph::graphDistancesGeodesic() - Average distance:" << graph.graphDistanceGeodesicAverageCached(); } else { // TODO In not connected nets, it would be nice to ask the user what to do // with unconnected pairs (make M or drop (default?) qCDebug(lcEngine) << "Graph::graphDistancesGeodesic() - Average distance:" << graph.graphDistanceGeodesicAverageCached(); graph.setAverageDistanceCached(graph.graphSumDistanceCached() / graph.graphGeodesicsCountCached()); } if (computeCentralities) { qCDebug(lcEngine) << "Graph: graphDistancesGeodesic() - " "Computing centralities..."; for (ds.it = graph.verticesBegin(); ds.it != graph.verticesEnd(); ++ds.it) { if (dropIsolates && (*ds.it)->isIsolated()) { qCDebug(lcEngine) << "vertex " << (*ds.it)->number() << " isolated, continue. "; continue; } // Compute classes and min/maxEC csf.SEC = (*ds.it)->SEC(); graph.resolveClasses(csf.SEC, graph.discreteECs, graph.classesEC, (*ds.it)->number()); graph.minmax(csf.SEC, (*ds.it), graph.maxEC, graph.minEC, graph.maxNodeEC, graph.minNodeEC); // Compute classes and min/maxSPC csf.SPC = (*ds.it)->SPC(); // same as PC graph.resolveClasses(csf.SPC, graph.discretePCs, graph.classesSPC, (*ds.it)->number()); graph.minmax(csf.SPC, (*ds.it), graph.maxSPC, graph.minSPC, graph.maxNodeSPC, graph.minNodeSPC); // Compute std BC, classes and min/maxSBC if (graph.symmetricCached()) { qCDebug(lcEngine) << "Betweenness centrality must be divided by" << " two if the graph is undirected"; (*ds.it)->setBC((*ds.it)->BC() / 2.0); } csf.BC = (*ds.it)->BC(); graph.sumBC += csf.BC; csf.SBC = csf.BC / graph.maxIndexBC; (*ds.it)->setSBC(csf.SBC); graph.resolveClasses(csf.SBC, graph.discreteBCs, graph.classesSBC); graph.sumSBC += csf.SBC; graph.minmax(csf.SBC, (*ds.it), graph.maxSBC, graph.minSBC, graph.maxNodeSBC, graph.minNodeSBC); // Compute std CC, classes and min/maxSCC csf.CC = (*ds.it)->CC(); graph.sumCC += csf.CC; csf.SCC = graph.maxIndexCC * csf.CC; (*ds.it)->setSCC(csf.SCC); graph.resolveClasses(csf.SCC, graph.discreteCCs, graph.classesSCC, (*ds.it)->number()); graph.sumSCC += csf.SCC; graph.minmax(csf.SCC, (*ds.it), graph.maxSCC, graph.minSCC, graph.maxNodeSCC, graph.minNodeSCC); // prepare to compute stdSC csf.SC = (*ds.it)->SC(); if (graph.symmetricCached()) { (*ds.it)->setSC(csf.SC / 2.0); csf.SC = (*ds.it)->SC(); qCDebug(lcEngine) << "SC of " << (*ds.it)->number() << " divided by 2 (because the graph is symmetric) " << (*ds.it)->SC(); } graph.sumSC += csf.SC; qCDebug(lcEngine) << "vertex " << (*ds.it)->number() << " - " << " EC: " << (*ds.it)->EC() << " CC: " << (*ds.it)->CC() << " BC: " << (*ds.it)->BC() << " SC: " << (*ds.it)->SC() << " PC: " << (*ds.it)->PC(); } // end for qCDebug(lcEngine) << "Graph: graphDistancesGeodesic() -" "Computing mean centrality values..."; // Compute mean values and prepare to compute variances graph.meanSBC = graph.sumSBC / (qreal)ds.N; graph.varianceSBC = 0; csf.tempVarianceBC = 0; graph.meanSCC = graph.sumSCC / (qreal)ds.N; graph.varianceSCC = 0; csf.tempVarianceCC = 0; graph.meanSPC = graph.sumSPC / (qreal)ds.N; graph.varianceSPC = 0; csf.tempVariancePC = 0; graph.meanEC = graph.sumEC / (qreal)ds.N; graph.varianceEC = 0; csf.tempVarianceEC = 0; qCDebug(lcEngine) << "Graph: graphDistancesGeodesic() - " "Computing std centralities ..."; for (ds.it = graph.verticesBegin(); ds.it != graph.verticesEnd(); ++ds.it) { if (dropIsolates && (*ds.it)->isIsolated()) { continue; } // Compute std SC, classes and min/maxSSC csf.SC = (*ds.it)->SC(); csf.SSC = csf.SC / graph.sumSC; (*ds.it)->setSSC(csf.SSC); graph.resolveClasses(csf.SSC, graph.discreteSCs, graph.classesSSC); graph.sumSSC += csf.SSC; graph.minmax(csf.SSC, (*ds.it), graph.maxSSC, graph.minSSC, graph.maxNodeSSC, graph.minNodeSSC); // Compute numerator of groupSBC csf.SBC = (*ds.it)->SBC(); graph.nomSBC += (graph.maxSBC - csf.SBC); // calculate BC variance csf.tempVarianceBC = (csf.SBC - graph.meanSBC); csf.tempVarianceBC *= csf.tempVarianceBC; graph.varianceSBC += csf.tempVarianceBC; // Compute numerator of groupCC graph.nomSCC += graph.maxSCC - (*ds.it)->SCC(); // calculate CC variance csf.tempVarianceCC = ((*ds.it)->SCC() - graph.meanSCC); csf.tempVarianceCC *= csf.tempVarianceCC; graph.varianceSCC += csf.tempVarianceCC; // Compute numerator of groupSPC csf.SPC = (*ds.it)->SPC(); graph.nomSPC += (graph.maxSPC - csf.SPC); // calculate PC variance csf.tempVariancePC = ((*ds.it)->SPC() - graph.meanSPC); csf.tempVariancePC *= csf.tempVariancePC; graph.varianceSPC += csf.tempVariancePC; // calculate EC variance csf.tempVarianceEC = ((*ds.it)->EC() - graph.meanEC); csf.tempVarianceEC *= csf.tempVarianceEC; graph.varianceEC += csf.tempVarianceEC; } // end for // compute final variances graph.varianceSBC /= (qreal)ds.N; graph.varianceSCC /= (qreal)ds.N; graph.varianceSPC /= (qreal)ds.N; graph.varianceEC /= (qreal)ds.N; // calculate SC mean value and prepare to compute variance graph.meanSSC = graph.sumSSC / (qreal)ds.N; graph.varianceSSC = 0; csf.tempVarianceSC = 0; for (ds.it = graph.verticesBegin(); ds.it != graph.verticesEnd(); ++ds.it) { if (dropIsolates && (*ds.it)->isIsolated()) { continue; } csf.tempVarianceSC = ((*ds.it)->SSC() - graph.meanSSC); csf.tempVarianceSC *= csf.tempVarianceSC; graph.varianceSSC += csf.tempVarianceSC; } // calculate final SC variance graph.varianceSSC /= (qreal)ds.N; graph.denomSPC = ((ds.N - 2.0)) / (2.0); // only for connected nets if (ds.N < 3) graph.denomSPC = ds.N - 1.0; // what if the net is disconnected (isolates exist) ? graph.groupSPC = graph.nomSPC / graph.denomSPC; graph.denomSCC = ((ds.N - 1.0) * (ds.N - 2.0)) / (2.0 * ds.N - 3.0); if (ds.N < 3) graph.denomSCC = ds.N - 1.0; graph.groupCC = graph.nomSCC / graph.denomSCC; // Calculate group Closeness centrality // nomSBC*=2.0; // denomSBC = (N-1.0) * (N-1.0) * (N-2.0); graph.denomSBC = (ds.N - 1.0); // Wasserman&Faust - formula 5.14 graph.groupSBC = graph.nomSBC / graph.denomSBC; // Calculate group Betweenness centrality graph.calculatedCentralities = true; } // END if computeCentralities } /** * Breadth-First Search (BFS) method for unweighted graphs (directed or not) INPUT: a 'source' vertex with vpos s and a boolean computeCentralities. (Implicitly, BFS uses the m_graph structure) OUTPUT: For every vertex t: pss.dist[ti] is set to the distance of each t from s For every vertex t: pss.sigma[ti] is set to the number of shortest paths between s and t Also, if computeCentralities is true then BFS does extra operations: a) For source vertex s: it calculates CC(s) as the sum of its distances from every other vertex. it calculates eccentricity(s) as the maximum distance from all other vertices. it increases pss.nthOrder[ N ] by one, to store the number of nodes at distance n from source s b) For every vertex u: it increases SC(u) by one, when it finds a new shor. path from s to t through u. appends each neighbor y of u to pss.Ps[y], thus Ps stores all predecessors of y on all shortest paths from s c) Each vertex u popped from Q is pushed to pss.Stack */ void DistanceEngine::bfsSSSP(const int &s, const int &si, const bool &computeCentralities, const bool &dropIsolates, PerSourceScratch &pss, QVector &partialSC) { Q_UNUSED(dropIsolates); qCDebug(lcEngine) << "BFS:"; int u = 0, ui = 0, w = 0, wi = 0; int dist_u = 0, temp = 0, dist_w = 0; int relation = 0; qreal weight = 0; // Fix #30: needed to skip zero-weight edges bool edgeStatus = false; H_edges::const_iterator it1; // set distance of s from s equal to 0 pss.dist[si] = 0; // set sigma of s from s equal to 1 pss.sigma[si] = 1; std::queue Q; Q.push(s); qCDebug(lcEngine) << "BFS: LOOP: While Q not empty "; while (!Q.empty()) { u = Q.front(); Q.pop(); ui = graph.vertexIndexByNumber(u); qCDebug(lcEngine) << "BFS: Dequeue: first element of Q is u" << u << "graph.vertexIndexByNumber" << ui; if (!graph.vertexAtIndex(ui)->isEnabled()) { continue; } if (computeCentralities) { qCDebug(lcEngine) << "BFS: Compute centralities: Pushing u" << u << "to Stack "; pss.Stack.push(u); } qCDebug(lcEngine) << "BFS: LOOP over every edge (u,w) e E, that is all neighbors w of vertex u"; it1 = graph.vertexAtIndex(ui)->outEdges().cbegin(); while (it1 != graph.vertexAtIndex(ui)->outEdges().cend()) { relation = it1.value().first; if (relation != graph.relationCurrent()) { ++it1; continue; } edgeStatus = it1.value().second.second; if (edgeStatus != true) { ++it1; continue; } // Fix #30: zero-weight edges are visual-only and must not be // traversed as structural connections. Without this guard, BFS // would report incorrect geodesic distances and reachability. weight = it1.value().second.first; if (weight == 0) { ++it1; continue; } w = it1.key(); wi = graph.vertexIndexByNumber(w); qCDebug(lcEngine, "BFS: u=%i is connected with node w=%i of graph.vertexIndexByNumber wi=%i. ", u, w, wi); qCDebug(lcEngine, "BFS: Start path discovery"); // if dist[wi] is RAND_MAX, w is found for the first time. if (pss.dist[wi] == (qreal)RAND_MAX) { qCDebug(lcEngine, "BFS: First time visiting w=%i. Enqueuing w to the end of Q", w); Q.push(w); dist_u = (int)pss.dist[ui]; dist_w = dist_u + 1; qCDebug(lcEngine) << "BFS: Setting dist_w = d ( s" << s << ", w" << w << ") equal to dist_u=d(s,u) plus 1. New dist_w" << dist_w; pss.dist[wi] = (qreal)dist_w; // Accumulate into scratch instead of calling graph methods directly. // Multiple threads run bfsSSSP concurrently; these graph methods // are not thread-safe. The owning thread reduces the scratch totals // into graph state after QtConcurrent::blockingMap returns. pss.sourceDistanceSum += dist_w; ++pss.sourceGeodesicsCount; if (dist_w > pss.sourceDiameter) pss.sourceDiameter = dist_w; qCDebug(lcEngine) << "== BFS - d(" << s << "," << w << ")=" << pss.dist[wi]; if (computeCentralities) { qCDebug(lcEngine) << "BFS: Calculate PC: store the number of nodes at distance " << dist_w << "from s"; pss.nthOrderIncrement((qreal)dist_w); qCDebug(lcEngine) << "BFS: Calculate CC: the sum of distances (will invert it l8r)"; // Source-vertex writes (si is unique per thread): safe for parallelism. graph.vertexAtIndex(si)->setCC(graph.vertexAtIndex(si)->CC() + dist_w); qCDebug(lcEngine) << "BFS: Calculate Eccentricity: the maximum distance "; if (graph.vertexAtIndex(si)->eccentricity() < dist_w) graph.vertexAtIndex(si)->setEccentricity(dist_w); } } qCDebug(lcEngine) << "BFS: Start path counting"; // Is edge (u,w) on a shortest path from s to w via u? if ((int)pss.dist[wi] == (int)pss.dist[ui] + 1) { temp = pss.sigma[wi] + pss.sigma[ui]; qCDebug(lcEngine) << "BFS: Found a NEW SHORTEST PATH from s" << s << "to w" << w << "via u" << u << "Setting Sigma(s, w)" << temp; if (s != w) { pss.sigma[wi] = temp; } if (computeCentralities) { qCDebug(lcEngine) << "BFS/SC: Computing centralities: Computing SC "; if (s != w && s != u && u != w) { qCDebug(lcEngine) << "BFS: partialSC[ui=" << ui << "] += 1"; // Intermediate vertex ui may be processed by concurrent threads // (other sources pass through the same u). Write to partialSC[ui] // β€” a per-thread array β€” instead of vertex->setSC() to avoid races. partialSC[ui] += 1.0; } qCDebug(lcEngine) << "BFS: appending u" << u << " to list Ps[w=" << w << "] with the predecessors of w on all shortest paths from s "; pss.Ps[wi].append(u); } } ++it1; } // end while (it1...) } // end while (!Q.empty()) } // end bfsSSSP() /** * Dijkstra's algorithm for solving the SSSP problem in weighted graphs (directed or not). * It uses a min-priority queue prQ to provide constant time lookup of the minimum * distance. The priority queue is implemented with std::priority_queue INPUT: a 'source' vertex with vpos s and a boolean computeCentralities. (Implicitly, the algorithm uses the m_graph structure) OUTPUT: For every vertex t: pss.dist[ti] is set to the distance of each t from s For every vertex t: pss.sigma[ti] is set to the number of shortest paths between s and t Also, if computeCentralities is true then it does extra operations: a) For source vertex s: it calculates CC(s) as the sum of its distances from every other vertex. it calculates eccentricity(s) as the maximum distance from all other vertices. it increases pss.nthOrder[ N ] by one, to store the number of nodes at distance n from source s b) For every vertex u: it increases SC(u) by one, when it finds a new shor. path from s to t through u. appends each neighbor y of u to pss.Ps[y], thus Ps stores all predecessors of y on all shortest paths from s c) Each vertex u popped from prQ is pushed to pss.Stack */ void DistanceEngine::dijkstraSSSP(const int &s, const int &si, const bool &computeCentralities, const bool &inverseWeights, const bool &dropIsolates, PerSourceScratch &pss, QVector &partialSC) { Q_UNUSED(dropIsolates); int u = 0, ui = 0, w = 0, wi = 0, v = 0, sp_w = 0; int relation = 0; qreal weight = 0, dist_u = 0, dist_w = 0, cur_dist_w = 0; bool edgeStatus = false; H_edges::const_iterator it1; VList::const_iterator it; // Construct a priority queue where we will store discovered vertices along with their distances from source qCDebug(lcEngine) << "### dijkstra: Construct a priority queue prQ to store discovered vertices-distances from source"; // TODO: Check prQ functionality in weighted graphs, where edge weight denotes value (not cost) priority_queue, GraphDistancesCompare> prQ; // This is used to not allow duplicates in the priority queue (@see issue #123) QSet visited_vertices; // set d( s, s ) = 0 pss.dist[si] = 0; // set sp ( s , s ) = 1 pss.sigma[si] = 1; for (it = graph.verticesBegin(); it != graph.verticesEnd(); ++it) { v = graph.vertexIndexByNumber((*it)->number()); if (v != s) { // NOTE: d(i,j) init to RAND_MAX already done via pss.dist.fill(RAND_MAX) // prQ.push(GraphDistance(v,RAND_MAX)); // TODO // Previous node in optimal path from source // previous[v] := undefined } } qCDebug(lcEngine) << "### dijkstra: push s" << s << "to prQ with 0 distance from s"; // Note: without it the priority prQ would pop arbitrary node at first loop prQ.push(GraphDistance(s, 0)); qCDebug(lcEngine) << "### dijkstra: LOOP: While prQ not empty "; while (!prQ.empty()) { qCDebug(lcEngine) << " *** dijkstra: prQ size: " << prQ.size(); // Get the first vertex in the priority queue u = prQ.top().target; // Get the vertex index ui = graph.vertexIndexByNumber(u); // Pop it qCDebug(lcEngine) << " *** dijkstra: first vertex in prQ is u" << u << "graph.vertexIndexByNumber" << ui << ". It has minimum distance from s " << s << "=" << prQ.top().distance << " Popping it from the queue."; prQ.pop(); if (visited_vertices.contains(u)) { qCDebug(lcEngine) << " *** dijkstra: vertex already visited. Skipping!"; continue; } // Add it to visited visited_vertices.insert(u); // Skip if that vertex is disabled if (!graph.vertexAtIndex(ui)->isEnabled()) { qCDebug(lcEngine) << " *** dijkstra: vertex disabled. Skipping!"; continue; } // Check if we need to compute centralities if (computeCentralities) { qCDebug(lcEngine) << " *** dijkstra: Compute centralities: pushing u =" << u << " to Stack "; pss.Stack.push(u); } // LOOP over every edge of u qCDebug(lcEngine) << " --- dijkstra: LOOP over every edge of u (" << u << ", w ) e E... "; it1 = graph.vertexAtIndex(ui)->outEdges().cbegin(); while (it1 != graph.vertexAtIndex(ui)->outEdges().cend()) { // Skip if the edge is not of the current relation relation = it1.value().first; if (relation != graph.relationCurrent()) { ++it1; continue; } // Skip if the edge is disabled edgeStatus = it1.value().second.second; if (edgeStatus != true) { ++it1; continue; } // Get the target vertex of this edge and its index w = it1.key(); wi = graph.vertexIndexByNumber(w); // Get the edge weight weight = it1.value().second.first; // Fix #30: zero-weight edges are visual-only and must not be // traversed. Normal weights: a zero-weight path would be "free" // (dist_w = dist_u + 0), collapsing distances incorrectly. // Inverse weights: 1/0 is undefined β€” potential crash or +inf. if (weight == 0) { ++it1; continue; } qCDebug(lcEngine) << " --- dijkstra: edge (u, w) = (" << u << "," << w << ") =" << weight; // Invert edge weight if the user told us to do so if (inverseWeights) { weight = 1.0 / weight; qCDebug(lcEngine) << " --- dijkstra: inverting weight to " << weight; } // Start path discovery qCDebug(lcEngine) << " --- dijkstra: Start path discovery"; // Get the distance of u from source dist_u = pss.dist[ui]; // If dist_u not finite, this means that dist_w also not finite if (dist_u == RAND_MAX || dist_u < 0) { dist_w = RAND_MAX; qCDebug(lcEngine) << " --- dijkstra: dist_w = RAND_MAX " << RAND_MAX; } else { // dist_u finite, therefore dist_w is (dist_u + edge weight) dist_w = dist_u + weight; qCDebug(lcEngine) << " --- dijkstra: dist_w = dist_u + weight = " << dist_u << "+" << weight << "=" << dist_w; } // Get the currently computed distance of w from source cur_dist_w = pss.dist[wi]; qCDebug(lcEngine) << " --- dijkstra: RELAXATION: check if dist_w =" << dist_w << " shorter than current d(s=" << s << ",w=" << w << ")=" << cur_dist_w; if ((dist_w == cur_dist_w) && dist_w < RAND_MAX) { qCDebug(lcEngine) << " --- dijkstra: dist_w : " << dist_w << " == current d(s,w) : " << cur_dist_w; sp_w = pss.sigma[wi] + pss.sigma[ui]; // WRONG! We do not know for sure that we are in a shortest path!!! qCDebug(lcEngine) << " --- dijkstra: (POSSIBLE BUG?) Found ANOTHER SP from s =" << s << " to w=" << w << " via u=" << u << " - Setting Sigma(s, w) = " << sp_w; if (s != w) { pss.sigma[wi] = sp_w; } if (computeCentralities) { if (s != w && s != u && u != w) { qCDebug(lcEngine) << " --- dijkstra: Compute Centralities: partialSC[ui=" << ui << "] += 1"; // Intermediate vertex: use partialSC to avoid race with other threads. partialSC[ui] += 1.0; } else { qCDebug(lcEngine) << " --- dijkstra: Compute Centralities: " "Skipping SC of u, because s=" << s << " w=" << w << " u=" << u; } qCDebug(lcEngine) << " --- dijkstra: Compute Centralities: " "Appending u=" << u << " to list Ps[w =" << w << "] with the predecessors of w on all shortest paths from s "; pss.Ps[wi].append(u); } } else if (dist_w > 0 && dist_w < cur_dist_w) { qCDebug(lcEngine) << " --- dijkstra: dist_w " << dist_w << " < current d(s,w) =" << cur_dist_w << " Pushing w" << w << "to prQ with distance" << dist_w << "from s" << s; // FIXME: w might have been already visited? // If so, we might use QMap which is sorted (minimum) // and also provides contain() prQ.push(GraphDistance(w, dist_w)); pss.dist[wi] = dist_w; // Accumulate into scratch instead of calling graph.incGeodesicsCount() / // graph.setDiameterCached() directly β€” those are not thread-safe. ++pss.sourceGeodesicsCount; qCDebug(lcEngine) << " --- dijkstra: " "Set d ( s=" << s << ", w=" << w << " ) = " << dist_w << "=" << pss.dist[wi]; if (dist_w > (qreal)pss.sourceDiameter) { pss.sourceDiameter = (int)dist_w; qCDebug(lcEngine) << " --- dijkstra: " "New thread-local diameter =" << pss.sourceDiameter; } if (s != w) { qCDebug(lcEngine) << " --- dijkstra: " "Found NEW shortest path from s =" << s << " to w =" << w << " via u =" << u << " - Setting Sigma(s, w) = 1 "; pss.sigma[wi] = 1; } if (computeCentralities) { pss.nthOrderIncrement(dist_w); qCDebug(lcEngine) << " --- dijkstra: Compute Centralities: " "For PC: nthOrder: number of nodes at distance " << dist_w << "from s is " << pss.nthOrder.value(dist_w, 0); if (graph.vertexAtIndex(si)->eccentricity() < dist_w) { graph.vertexAtIndex(si)->setEccentricity(dist_w); qCDebug(lcEngine) << " --- dijkstra: Compute Centralities: " "For EC: max distance =" << graph.vertexAtIndex(si)->eccentricity(); } qCDebug(lcEngine) << " --- dijkstra: Compute Centralities: " "Appending u=" << u << " to list Ps[w =" << w << "] with the predecessors of w on all shortest paths from s "; pss.Ps[wi].append(u); } } else { qCDebug(lcEngine) << " --- dijkstra: " "NOT a new SP"; } ++it1; } // END loop for every outEdge of u qCDebug(lcEngine) << " --- dijkstra: LOOP END over every edge (" << u << ", w ) e E... "; } // END loop while prQ not empty qCDebug(lcEngine) << "### dijkstra: LOOP END. prQ is empty - Returning."; } // END dijkstraSSSP() SocNetV-3.7/src/engine/distance_engine.h000066400000000000000000000071201524402210300202210ustar00rootroot00000000000000/** * @file distance_engine.h * @brief Declares the DistanceEngine class for computing geodesic distances and centralities in the graph. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2025 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef SOCNETV_DISTANCE_ENGINE_H #define SOCNETV_DISTANCE_ENGINE_H #include "engine/graph_distance_progress_sink.h" #include "engine/per_source_scratch.h" #include class Graph; class DistanceEngine { public: explicit DistanceEngine(Graph &g); void compute(const bool computeCentralities, const bool considerWeights, const bool inverseWeights, const bool dropIsolates); private: Graph &graph; void initRun(const bool computeCentralities, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, struct DistanceScratch &ds, struct CentralityScratchSSSP &csssp, struct CentralityScratchFinalize &csfin, IDistanceProgressSink &sink); // Parallel SSSP source loop (Phase 2). // Distributes source vertices across CPU cores via QtConcurrent::blockingMap. // Each thread owns a ThreadLocalState; unsafe graph writes (BC, SC, distance // sum, geodesics count, diameter) are accumulated into per-thread state and // reduced into graph-global state in a single-threaded step after the map. void runAllSources(const bool computeCentralities, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, struct DistanceScratch &ds, IDistanceProgressSink &sink); void finalize(const bool computeCentralities, const bool dropIsolates, struct DistanceScratch &ds, struct CentralityScratchFinalize &csfin, IDistanceProgressSink &sink); // Breadth-First Search SSSP for unweighted graphs. // Writes distances and sigma to pss; accumulates unsafe graph-wide values into // pss.sourceDistanceSum / sourceGeodesicsCount / sourceDiameter instead of calling // graph methods directly (safe for parallel execution from multiple threads). // SC increments go into partialSC[ui] rather than vertex->setSC() to avoid races // on intermediate vertices that may be visited by concurrent source threads. void bfsSSSP(const int &s, const int &si, const bool &computeCentralities, const bool &dropIsolates, PerSourceScratch &pss, QVector &partialSC); // Dijkstra SSSP for weighted graphs (directed or not). // Same thread-safety contract as bfsSSSP: unsafe graph-wide writes go to // pss scratch fields and partialSC instead of touching graph/vertex state directly. void dijkstraSSSP(const int &s, const int &si, const bool &computeCentralities, const bool &inverseWeights, const bool &dropIsolates, PerSourceScratch &pss, QVector &partialSC); }; #endif // SOCNETV_DISTANCE_ENGINE_H SocNetV-3.7/src/engine/distance_progress_sink.h000066400000000000000000000016241524402210300216470ustar00rootroot00000000000000/** * @file distance_progress_sink.h * @brief Declares the IDistanceProgressSink interface for status messages and cancellation during distance and centrality computations. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2025 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #pragma once #include class IDistanceProgressSink { public: virtual ~IDistanceProgressSink() = default; virtual void statusMessage(const QString &msg) = 0; virtual void resetCancellation() = 0; virtual bool progressCanceled() const = 0; }; SocNetV-3.7/src/engine/graph_distance_progress_sink.cpp000066400000000000000000000023411524402210300233600ustar00rootroot00000000000000/** * @file graph_distance_progress_sink.cpp * @brief Implements the GraphDistanceProgressSink class, which forwards DistanceEngine's status messages and cancellation state to Graph. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2025 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph_distance_progress_sink.h" #include "../graph.h" // adjust include if you prefer "graph.h" from include paths GraphDistanceProgressSink::GraphDistanceProgressSink(Graph &g) : graph(g) { } void GraphDistanceProgressSink::statusMessage(const QString &msg) { emit graph.statusMessage(msg); } /** * @brief Resets the cancellation flag at the start of a distance computation. */ void GraphDistanceProgressSink::resetCancellation() { graph.resetProgressCanceled(); } bool GraphDistanceProgressSink::progressCanceled() const { return graph.progressCanceled(); } SocNetV-3.7/src/engine/graph_distance_progress_sink.h000066400000000000000000000020161524402210300230240ustar00rootroot00000000000000/** * @file graph_distance_progress_sink.h * @brief Declares the GraphDistanceProgressSink class, which forwards DistanceEngine's status messages and cancellation state to Graph. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2025 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #pragma once #include "distance_progress_sink.h" class Graph; // forward declaration class GraphDistanceProgressSink final : public IDistanceProgressSink { public: explicit GraphDistanceProgressSink(Graph &g); void statusMessage(const QString &msg) override; void resetCancellation() override; bool progressCanceled() const override; private: Graph &graph; }; SocNetV-3.7/src/engine/null_distance_progress_sink.h000066400000000000000000000017441524402210300227040ustar00rootroot00000000000000/** * @file null_distance_progress_sink.h * @brief Declares the NullDistanceProgressSink class that implements IDistanceProgressSink with empty methods, for use when no UI updates are needed during distance and centrality computations. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2025 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #pragma once #include "distance_progress_sink.h" class NullDistanceProgressSink final : public IDistanceProgressSink { public: void statusMessage(const QString &) override {} void resetCancellation() override {} bool progressCanceled() const override { return false; } }; SocNetV-3.7/src/engine/per_source_scratch.h000066400000000000000000000074431524402210300207670ustar00rootroot00000000000000/** * @file per_source_scratch.h * @brief Per-source scratch state for the Brandes SSSP / centrality computation. * * All mutable state that is local to a single source-vertex iteration of the * DistanceEngine source loop lives here. Moving it out of Graph and GraphVertex * is WS3 Phase 1; making it thread-local enables Phase 2 parallelism. * * The corresponding transitional Graph/GraphVertex members (Stack, * sizeOfNthOrderNeighborhood, sizeOfComponent, myPs, m_delta) were removed * in the WS3 Phase 2 dead-code cleanup after the regression harness passed. */ #ifndef SOCNETV_PER_SOURCE_SCRATCH_H #define SOCNETV_PER_SOURCE_SCRATCH_H #include #include #include #include #include #include // RAND_MAX struct PerSourceScratch { // Brandes traversal stack β€” vertices ordered by non-increasing distance from s std::stack Stack; // Predecessor lists: Ps[vi] = list of vertices that precede vertex vi // on shortest paths from the current source s. Indexed by vertex position. QVector> Ps; // Dependency accumulators for Brandes BC back-propagation. // Indexed by vertex position. QVector delta; // BFS / Dijkstra distances from current source s. // dist[vi] == RAND_MAX means vertex vi has not been reached. // Indexed by vertex position. QVector dist; // Shortest-path counts (sigma) from current source s. // Indexed by vertex position. QVector sigma; // Number of vertices at each distance order from s (used for Power Centrality). // Key = distance, Value = count. QHash nthOrder; // Size of the connected component reachable from s (used for SPC normalisation). int componentSize = 0; // Per-source graph-aggregate accumulators. // Phase 2: SSSP functions write here instead of calling graph.addToDistanceSum() / // graph.incGeodesicsCount() / graph.setDiameterCached() directly, which would be // unsafe when multiple threads process different sources concurrently. // The owning thread accumulates these into ThreadLocalState totals after each source, // and the post-loop reduction merges them into the graph-level aggregates. qreal sourceDistanceSum = 0; // replaces graph.addToDistanceSum(dist_w) in BFS int sourceGeodesicsCount = 0; // replaces graph.incGeodesicsCount() in BFS / Dijkstra int sourceDiameter = 0; // replaces graph.setDiameterCached() in BFS / Dijkstra // Allocate all containers once for totalVertices positions. // Call this once before the source loop. void allocate(int totalVertices) { Ps.resize(totalVertices); delta.resize(totalVertices); dist.resize(totalVertices); sigma.resize(totalVertices); } // Reset per-source state before each new source vertex. // computeCentralities controls whether the Brandes-only structures are reset. void resetPerSource(bool computeCentralities) { dist.fill((qreal)RAND_MAX); sigma.fill(0); // Reset per-source graph-aggregate accumulators so they reflect only this source. sourceDistanceSum = 0; sourceGeodesicsCount = 0; sourceDiameter = 0; if (computeCentralities) { while (!Stack.empty()) Stack.pop(); for (auto &ps : Ps) ps.clear(); nthOrder.clear(); } // delta is zeroed in the per-source back-propagation setup loop, // not here, to preserve the existing guard on computeCentralities. } // Increment the nth-order neighbourhood count for distance d. void nthOrderIncrement(qreal d) { nthOrder.insert(d, nthOrder.value(d, 0) + 1); } }; #endif // SOCNETV_PER_SOURCE_SCRATCH_H SocNetV-3.7/src/engine/thread_local_state.h000066400000000000000000000073661524402210300207370ustar00rootroot00000000000000/** * @file thread_local_state.h * @brief Per-thread state for the parallel SSSP source loop in DistanceEngine. * * During Phase 2 parallelisation (QtConcurrent::blockingMap over source vertices), * each worker thread owns one ThreadLocalState. It holds: * - A PerSourceScratch (reused across all sources the thread processes). * - Partial BC and SC accumulator arrays β€” intermediate vertices are shared across * sources, so BC/SC are NOT written to GraphVertex directly inside the loop. * Instead each thread accumulates into partialBC / partialSC, and a single-threaded * reduction step at the end writes the totals to the vertex objects. * - Running totals for graph-wide aggregates (distance sum, geodesics count, diameter, * PC/SPC sums) that would otherwise require a mutex around every graph call. * * Lifecycle: * allocate(totalV) β€” called once per thread before the parallel loop * (partialBC / partialSC are zeroed at allocation; pss is reset per source) * Post-loop reduction reads totalDistanceSum, totalGeodesicsCount, maxDiameter, * totalSumPC, totalSumSPC, partialBC[*], partialSC[*] and merges into graph state. */ #ifndef SOCNETV_THREAD_LOCAL_STATE_H #define SOCNETV_THREAD_LOCAL_STATE_H #include "engine/per_source_scratch.h" #include #include struct ThreadLocalState { // Reused SSSP scratch β€” reset once per source via pss.resetPerSource(). PerSourceScratch pss; // Partial betweenness centrality sums, indexed by vertex position. // Each source contributes pss.delta[wi] for every intermediate vertex w != s // instead of calling vertex->setBC() directly (which would race across threads). // Post-loop: vertex[wi]->setBC( sum over all threads of partialBC[wi] ) QVector partialBC; // Partial stress centrality sums, indexed by vertex position. // Each new shortest path through vertex ui increments partialSC[ui] by 1 // instead of calling vertex->setSC() directly. // Post-loop: vertex[ui]->setSC( sum over all threads of partialSC[ui] ) QVector partialSC; // Sum of pss.sourceDistanceSum across all sources this thread has processed. // Originates from BFS inner-loop "dist_w" accumulation (not used by Dijkstra). // Reduced into graph.addToDistanceSum() after the parallel loop. qreal totalDistanceSum = 0; // Sum of per-source distances_sum_for_s (computeCentralities path only). // This is the CC-denominator sum that also goes into graphSumDistance. // Reduced into graph.addToDistanceSum() after the parallel loop. qreal totalCCDistanceSum = 0; // Total geodesic-path count across all sources this thread has processed. // Replaces repeated graph.incGeodesicsCount() calls inside BFS / Dijkstra. // Reduced via graph.addGeodesicsCount() after the parallel loop. int totalGeodesicsCount = 0; // Maximum geodesic distance (diameter) seen by this thread. // Replaces graph.setDiameterCached() inside BFS / Dijkstra. // Post-loop: graph.setDiameterCached( max over all threads of maxDiameter ) int maxDiameter = 0; // Accumulated Power Centrality and Standardised Power Centrality sums. // Replaces direct graph.sumPC += and graph.sumSPC += inside the source loop // (those graph members are public but would race across threads). // Reduced into graph.sumPC and graph.sumSPC after the parallel loop. qreal totalSumPC = 0; qreal totalSumSPC = 0; // Initialise all per-vertex arrays to totalV slots. // Must be called once per thread before the parallel source loop starts. void allocate(int totalV) { pss.allocate(totalV); partialBC.fill(0.0, totalV); partialSC.fill(0.0, totalV); } }; #endif // SOCNETV_THREAD_LOCAL_STATE_H SocNetV-3.7/src/forms/000077500000000000000000000000001524402210300146125ustar00rootroot00000000000000SocNetV-3.7/src/forms/dialogbulkedit.cpp000066400000000000000000000141031524402210300203000ustar00rootroot00000000000000/** * @file dialogbulkedit.cpp * @brief Implements DialogBulkEdit for applying a single property value to multiple nodes or edges. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogbulkedit.h" #include "ui_dialogbulkedit.h" #include "global.h" #include #include "forms_logging.h" #include #include #include SOCNETV_USE_NAMESPACE DialogBulkEdit::DialogBulkEdit(Scope scope, const QStringList &existingKeys, const QStringList &nodeShapeList, const QStringList &iconPathList, int targetCount, bool isFilterScope, QWidget *parent) : QDialog(parent) , ui(new Ui::DialogBulkEdit) , m_nodeShapeList(nodeShapeList) , m_iconPathList(iconPathList) , m_color(Qt::gray) { ui->setupUi(this); // Description label const QString scopeStr = (scope == Scope::Nodes) ? tr("nodes") : tr("edges"); const QString filterStr = isFilterScope ? tr("visible") : tr("selected"); ui->descLabel->setText( tr("Apply to %1 %2 %3:").arg(targetCount).arg(filterStr).arg(scopeStr)); // Helper: append one property entry auto addProp = [this](const QString &displayName, const QString &key, int page) { ui->propertyCombo->addItem(displayName); m_propertyKeys << key; m_propertyPages << page; }; // Built-in properties β€” scope-dependent if (scope == Scope::Nodes) { addProp(tr("Label"), QStringLiteral("Label"), PAGE_TEXT); addProp(tr("Size"), QStringLiteral("Size"), PAGE_INT); addProp(tr("Color"), QStringLiteral("Color"), PAGE_COLOR); addProp(tr("Shape"), QStringLiteral("Shape"), PAGE_SHAPE); } else { addProp(tr("Label"), QStringLiteral("Label"), PAGE_TEXT); addProp(tr("Weight"), QStringLiteral("Weight"), PAGE_DOUBLE); addProp(tr("Color"), QStringLiteral("Color"), PAGE_COLOR); } // Custom attribute keys separated from built-ins if (!existingKeys.isEmpty()) { ui->propertyCombo->insertSeparator(ui->propertyCombo->count()); m_propertyKeys << QString(); // placeholder for separator m_propertyPages << -1; for (const QString &key : existingKeys) addProp(key, key, PAGE_TEXT); } // Populate shape combo (nodes only) if (scope == Scope::Nodes && !m_nodeShapeList.isEmpty()) { ui->shapeCombo->addItems(m_nodeShapeList); for (int i = 0; i < m_nodeShapeList.size() && i < m_iconPathList.size(); ++i) ui->shapeCombo->setItemIcon(i, QIcon(m_iconPathList[i])); } // Colour button: show a filled swatch as the icon (same pattern as DialogNodeEdit) m_pixmap = QPixmap(60, 20); m_pixmap.fill(m_color); ui->colorButton->setIcon(QIcon(m_pixmap)); // Start on the page matching the first combo item ui->valueStack->setCurrentIndex( m_propertyPages.isEmpty() ? PAGE_TEXT : m_propertyPages.first()); connect(ui->propertyCombo, qOverload(&QComboBox::currentIndexChanged), this, &DialogBulkEdit::onPropertyChanged); connect(ui->colorButton, &QToolButton::clicked, this, &DialogBulkEdit::onColorButtonClicked); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &DialogBulkEdit::getUserChoices); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); ui->buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); ui->textEdit->setFocus(); } DialogBulkEdit::~DialogBulkEdit() { delete ui; } void DialogBulkEdit::onPropertyChanged(int index) { if (index < 0 || index >= m_propertyPages.size()) return; const int page = m_propertyPages[index]; if (page == -1) { // Landed on separator β€” advance to the next valid item const int next = index + 1; if (next < m_propertyPages.size() && m_propertyPages[next] != -1) ui->propertyCombo->setCurrentIndex(next); return; } ui->valueStack->setCurrentIndex(page); switch (page) { case PAGE_TEXT: ui->textEdit->setFocus(); break; case PAGE_INT: ui->intSpin->setFocus(); break; case PAGE_DOUBLE: ui->doubleSpin->setFocus(); break; case PAGE_COLOR: ui->colorButton->setFocus(); break; case PAGE_SHAPE: ui->shapeCombo->setFocus(); break; default: break; } } void DialogBulkEdit::onColorButtonClicked() { const QColor chosen = QColorDialog::getColor(m_color, this, tr("Select color")); if (chosen.isValid()) { m_color = chosen; m_pixmap.fill(m_color); ui->colorButton->setIcon(QIcon(m_pixmap)); } } void DialogBulkEdit::getUserChoices() { const int idx = ui->propertyCombo->currentIndex(); if (idx < 0 || idx >= m_propertyKeys.size()) return; const QString &property = m_propertyKeys[idx]; if (property.isEmpty()) return; // separator β€” shouldn't be reachable QString value; switch (m_propertyPages[idx]) { case PAGE_TEXT: value = ui->textEdit->text(); break; case PAGE_INT: value = QString::number(ui->intSpin->value()); break; case PAGE_DOUBLE: value = QString::number(ui->doubleSpin->value(), 'f', 2); break; case PAGE_COLOR: value = m_color.name(); break; case PAGE_SHAPE: value = ui->shapeCombo->currentText(); break; default: return; } qCDebug(lcForms) << "DialogBulkEdit: property" << property << "value" << value; emit userChoices(property, value); accept(); } SocNetV-3.7/src/forms/dialogbulkedit.h000066400000000000000000000041551524402210300177530ustar00rootroot00000000000000/** * @file dialogbulkedit.h * @brief Declares DialogBulkEdit for applying a single property value to multiple nodes or edges. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGBULKEDIT_H #define DIALOGBULKEDIT_H #include #include #include #include #include namespace Ui { class DialogBulkEdit; } class DialogBulkEdit : public QDialog { Q_OBJECT public: enum class Scope { Nodes, Edges }; explicit DialogBulkEdit(Scope scope, const QStringList &existingKeys, const QStringList &nodeShapeList = QStringList(), const QStringList &iconPathList = QStringList(), int targetCount = 0, bool isFilterScope = false, QWidget *parent = nullptr); ~DialogBulkEdit(); signals: void userChoices(const QString &property, const QString &value); private slots: void onPropertyChanged(int index); void onColorButtonClicked(); void getUserChoices(); private: Ui::DialogBulkEdit *ui; QStringList m_nodeShapeList; QStringList m_iconPathList; QColor m_color; QPixmap m_pixmap; QStringList m_propertyKeys; // internal key per combo item (empty = separator) QVector m_propertyPages; // valueStack page index per combo item (-1 = separator) static constexpr int PAGE_TEXT = 0; static constexpr int PAGE_INT = 1; static constexpr int PAGE_DOUBLE = 2; static constexpr int PAGE_COLOR = 3; static constexpr int PAGE_SHAPE = 4; }; #endif // DIALOGBULKEDIT_H SocNetV-3.7/src/forms/dialogbulkedit.ui000066400000000000000000000145211524402210300201370ustar00rootroot00000000000000 DialogBulkEdit 0 0 420 200 360 180 Set Property for Selection 12 8 Apply to selected items. true 8 Property: 1 0 Value: 1 0 0 0 Enter value... 0 1 200 8 0 0.000000 99999.990000 0.100000 2 1.000000 0 Select color... Qt::ToolButtonTextBesideIcon 0 0 0 0 0 Qt::Vertical 20 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok SocNetV-3.7/src/forms/dialogcentralitybonacich.cpp000066400000000000000000000050031524402210300223410ustar00rootroot00000000000000/** * @file dialogcentralitybonacich.cpp * @brief Implements the DialogCentralityBonacich class for collecting the Bonacich Power Centrality parameters (alpha, beta) in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogcentralitybonacich.h" #include #include #include "forms_logging.h" DialogCentralityBonacich::DialogCentralityBonacich(QWidget *parent, const qreal alpha, const qreal beta) : QDialog(parent) { ui.setupUi(this); ui.alphaDoubleSpinBox->setValue(alpha); ui.betaDoubleSpinBox->setValue(beta); connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(getUserChoices())); (ui.buttonBox)->button(QDialogButtonBox::Ok)->setDefault(true); ui.alphaDoubleSpinBox->setFocus(); } void DialogCentralityBonacich::setBetaBound(qreal bound) { if (bound > 0) { const qreal maxBeta = bound * 0.999; ui.betaDoubleSpinBox->setMinimum(-maxBeta); ui.betaDoubleSpinBox->setMaximum(maxBeta); if (ui.betaDoubleSpinBox->value() > maxBeta) { ui.betaDoubleSpinBox->setValue(maxBeta); } else if (ui.betaDoubleSpinBox->value() < -maxBeta) { ui.betaDoubleSpinBox->setValue(-maxBeta); } ui.hintLabel->setText( tr("Beta must be smaller in absolute value than %1 (1 / this network's largest " "eigenvalue) - the spinbox above already enforces this. With negative beta, " "scores can come out negative - that is expected, not an error.").arg(bound)); } else { ui.hintLabel->setText( tr("This network has no such bound on beta - any value converges. With negative " "beta, scores can come out negative - that is expected, not an error.")); } } void DialogCentralityBonacich::getUserChoices() { const qreal alpha = ui.alphaDoubleSpinBox->value(); const qreal beta = ui.betaDoubleSpinBox->value(); qCDebug(lcForms) << "DialogCentralityBonacich: emitting userChoices, alpha =" << alpha << "beta =" << beta; emit userChoices(alpha, beta); } SocNetV-3.7/src/forms/dialogcentralitybonacich.h000066400000000000000000000032031524402210300220060ustar00rootroot00000000000000/** * @file dialogcentralitybonacich.h * @brief Declares the DialogCentralityBonacich class for collecting the Bonacich Power Centrality parameters (alpha, beta) in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGCENTRALITYBONACICH_H #define DIALOGCENTRALITYBONACICH_H #include #include "ui_dialogcentralitybonacich.h" class DialogCentralityBonacich : public QDialog { Q_OBJECT public: explicit DialogCentralityBonacich(QWidget *parent = Q_NULLPTR, const qreal alpha = 1.0, const qreal beta = 0.1); // Call before exec(). bound > 0 clamps the beta spinbox to +-(bound * 0.999) (the // convergence condition is a strict "<", not "<=") and shows the actual number in the hint // label. bound == 0 means the network has no such bound (e.g. nilpotent/DAG-like) - the // spinbox keeps its default generous range and the hint label says so. alpha is never // bounded - it's a free outer scale factor. void setBetaBound(qreal bound); public slots: void getUserChoices(); signals: void userChoices(const qreal alpha, const qreal beta); private: Ui::DialogCentralityBonacich ui; }; #endif SocNetV-3.7/src/forms/dialogcentralitybonacich.ui000066400000000000000000000144741524402210300222100ustar00rootroot00000000000000 DialogCentralityBonacich 0 0 500 420 500 420 Bonacich Power Centrality Bonacich Power Centrality gives each actor credit for indirect connections, discounted by distance like Katz Centrality - but beta below can be negative, in which case being tied to well-connected others can hurt rather than help a node's score. alpha is a free overall scale factor and does not change the ranking, only the numbers' size. Qt::TextFormat::RichText true Alpha: Qt::Orientation::Horizontal 40 20 80 0 <html><head/><body><p>A free overall scale factor - it does not affect convergence, only the size of the resulting scores.</p></body></html> Overall scale factor. Does not change the ranking, only the numbers' size. Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 6 0.000001000000000 10.000000000000000 0.010000000000000 1.000000000000000 Beta: Qt::Orientation::Horizontal 40 20 80 0 <html><head/><body><p>Must satisfy |beta| &lt; 1 / (the network's largest eigenvalue), or the computation will not converge - the report will explain if your value is too large. May be negative.</p></body></html> Attenuation factor. May be negative, flipping whether connections to well-connected others help or hurt. Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 6 -10.000000000000000 10.000000000000000 0.010000000000000 0.100000000000000 Beta must be smaller in absolute value than 1 / (the network's largest eigenvalue). If it's too large, the report will show all-zero scores and explain why in the status bar. With negative beta, scores can come out negative - that is expected, not an error. Qt::TextFormat::RichText true true Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBox accepted() DialogCentralityBonacich accept() buttonBox rejected() DialogCentralityBonacich reject() SocNetV-3.7/src/forms/dialogcentralitykatz.cpp000066400000000000000000000036421524402210300215530ustar00rootroot00000000000000/** * @file dialogcentralitykatz.cpp * @brief Implements the DialogCentralityKatz class for collecting the Katz Centrality attenuation factor (alpha) in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogcentralitykatz.h" #include #include #include "forms_logging.h" DialogCentralityKatz::DialogCentralityKatz(QWidget *parent, const qreal alpha) : QDialog(parent) { ui.setupUi(this); ui.alphaDoubleSpinBox->setValue(alpha); connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(getUserChoices())); (ui.buttonBox)->button(QDialogButtonBox::Ok)->setDefault(true); ui.alphaDoubleSpinBox->setFocus(); } void DialogCentralityKatz::setAlphaBound(qreal bound) { if (bound > 0) { const qreal maxAlpha = bound * 0.999; ui.alphaDoubleSpinBox->setMaximum(maxAlpha); if (ui.alphaDoubleSpinBox->value() > maxAlpha) { ui.alphaDoubleSpinBox->setValue(maxAlpha); } ui.hintLabel->setText( tr("Must be smaller than %1 (1 / this network's largest eigenvalue) - " "the spinbox above already enforces this.").arg(bound)); } else { ui.hintLabel->setText( tr("This network has no such bound - any alpha value converges.")); } } void DialogCentralityKatz::getUserChoices() { const qreal alpha = ui.alphaDoubleSpinBox->value(); qCDebug(lcForms) << "DialogCentralityKatz: emitting userChoices, alpha =" << alpha; emit userChoices(alpha); } SocNetV-3.7/src/forms/dialogcentralitykatz.h000066400000000000000000000026411524402210300212160ustar00rootroot00000000000000/** * @file dialogcentralitykatz.h * @brief Declares the DialogCentralityKatz class for collecting the Katz Centrality attenuation factor (alpha) in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGCENTRALITYKATZ_H #define DIALOGCENTRALITYKATZ_H #include #include "ui_dialogcentralitykatz.h" class DialogCentralityKatz : public QDialog { Q_OBJECT public: explicit DialogCentralityKatz(QWidget *parent = Q_NULLPTR, const qreal alpha = 0.1); // Call before exec(). bound > 0 clamps the spinbox to +-(bound * 0.999) (the convergence // condition is a strict "<", not "<=") and shows the actual number in the hint label. // bound == 0 means the network has no such bound (e.g. nilpotent/DAG-like) - the spinbox // keeps its default generous range and the hint label says so. void setAlphaBound(qreal bound); public slots: void getUserChoices(); signals: void userChoices(const qreal alpha); private: Ui::DialogCentralityKatz ui; }; #endif SocNetV-3.7/src/forms/dialogcentralitykatz.ui000066400000000000000000000103051524402210300214000ustar00rootroot00000000000000 DialogCentralityKatz 0 0 480 320 480 320 Katz Centrality Katz Centrality gives each actor credit for indirect connections, discounted the further away they are by the attenuation factor alpha below (a direct tie counts full price, a 2-hop tie counts alpha times as much, and so on). Qt::TextFormat::RichText true Alpha: Qt::Orientation::Horizontal 40 20 80 0 <html><head/><body><p>Must be smaller than 1 / (the network's largest eigenvalue), or the computation will not converge - the report will explain if your value is too large.</p></body></html> Attenuation factor. Larger values give more weight to distant connections. Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 6 0.000001000000000 10.000000000000000 0.010000000000000 0.100000000000000 Must be smaller than 1 / (the network's largest eigenvalue). If it's too large, the report will show all-zero scores and explain why in the status bar. Qt::TextFormat::RichText true true Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBox accepted() DialogCentralityKatz accept() buttonBox rejected() DialogCentralityKatz reject() SocNetV-3.7/src/forms/dialogclusteringhierarchical.cpp000077500000000000000000000067341524402210300232310ustar00rootroot00000000000000/** * @file dialogclusteringhierarchical.cpp * @brief Implements hierarchical clustering dialog for social network visualization and analysis. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogclusteringhierarchical.h" #include #include "forms_logging.h" #include DialogClusteringHierarchical::DialogClusteringHierarchical (QWidget *parent, QString preselectMatrix) : QDialog (parent) { ui.setupUi(this); (ui.buttonBox)->button (QDialogButtonBox::Ok)->setDefault(true); matrixList << "Adjacency" << "Distances"; measureList << "None, use raw input matrix" << "Jaccard distance" << "Hamming distance" << "Euclidean distance" << "Manhattan distance"; linkageList << "Single-linkage (minimum)" << "Complete-linkage (maximum)" << "Average-linkage (UPGMA)"; variablesLocationList << "Rows" << "Columns" << "Both"; ui.variablesLocationSelect->insertItems( 1, variablesLocationList ); ui.variablesLocationSelect->setCurrentIndex(2); ui.matrixSelect->insertItems( 1, matrixList ); if (preselectMatrix == "Distances") { ui.matrixSelect->setCurrentIndex(1); } ui.metricSelect->insertItems(1, measureList); ui.metricSelect->setCurrentIndex(3); ui.linkageSelect->insertItems( 1, linkageList ); ui.linkageSelect->setCurrentIndex(2); ui.diagonalCheckBox->setChecked(false); ui.diagramCheckBox->setChecked(true); connect ( ui.matrixSelect, SIGNAL(textHighlighted(QString)), this, SLOT(matrixChanged(QString)) ); } void DialogClusteringHierarchical::matrixChanged(const QString &matrix) { qCDebug(lcForms)<< "DialogClusteringHierarchical::matrixChanged()" << matrix; } /** * @brief Gets user choices */ void DialogClusteringHierarchical::getUserChoices(){ qCDebug(lcForms)<< "DialogClusteringHierarchical::getUserChoices!..."; QString matrix = ui.matrixSelect->currentText(); QString varLocation = ui.variablesLocationSelect->currentText(); QString metric= (( ui.metricSelect->isEnabled() ) ? ui.metricSelect->currentText() : "-" ); QString linkage = ui.linkageSelect->currentText(); bool diagonal = ui.diagonalCheckBox->isChecked(); bool diagram = ui.diagramCheckBox->isChecked(); qCDebug(lcForms)<< "DialogClusteringHierarchical: user selected: " << matrix << metric << linkage; emit userChoices( matrix, varLocation, metric, linkage,diagonal, diagram ); } void DialogClusteringHierarchical::on_buttonBox_accepted() { this->getUserChoices(); this->accept(); } void DialogClusteringHierarchical::on_buttonBox_rejected() { this->reject(); } DialogClusteringHierarchical::~DialogClusteringHierarchical(){ matrixList.clear(); measureList.clear(); linkageList.clear(); } SocNetV-3.7/src/forms/dialogclusteringhierarchical.h000077500000000000000000000031031524402210300226610ustar00rootroot00000000000000/** * @file dialogclusteringhierarchical.h * @brief Defines the DialogClusteringHierarchical class for hierarchical clustering settings in the social network visualizer. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGCLUSTERINGHIERARCHICAL_H #define DIALOGCLUSTERINGHIERARCHICAL_H #include #include "ui_dialogclusteringhierarchical.h" class DialogClusteringHierarchical: public QDialog { Q_OBJECT public: DialogClusteringHierarchical (QWidget *parent = Q_NULLPTR, QString preselectMatrix = ""); ~DialogClusteringHierarchical(); public slots: void getUserChoices(); signals: void userChoices(const QString &matrix, const QString &varLocation, const QString &similarityMeasure, const QString &linkageCriterion, const bool &diagonal, const bool &diagram); private slots: void on_buttonBox_accepted(); void on_buttonBox_rejected(); void matrixChanged(const QString &matrix); private: Ui::DialogClusteringHierarchical ui; QStringList matrixList, measureList, linkageList, variablesLocationList; }; #endif SocNetV-3.7/src/forms/dialogclusteringhierarchical.ui000066400000000000000000000251751524402210300230610ustar00rootroot00000000000000 DialogClusteringHierarchical true 0 0 800 500 0 0 700 500 800 600 Hierarchical Clustering 170 0 Variables in: 200 0 Qt::StrongFocus <html><head/><body><p><span style=" font-weight:600;">Rows: </span>Correlate outbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Columns: </span>Correlate inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Both: </span>Correlate both outbound and inbound ties (or distances, depending on the selected matrix above) between pairs of actors. In this case, the input matrix is expanded by listing row vectors followed by column vectors. This is useful only when you have directed data.</p><p><br/></p></body></html> Enable to include matrix diagonal in calculations Print dendrogram (avoid in large nets) Qt::Vertical 20 40 0 0 400 200 <html><head/><body><p>Agglomerative hierarchical clustering builds a hierarchy of actor clusters, based on their tie/distance dissimilarity, starting with single elements and aggregating them into clusters.</p><p>It takes a matrix (adjacency or geodesic distances) and a distance metric between actors as input and constructs a pair-wise dissimilarity matrix. </p><p>Initially, each actor starts in its own cluster (Level 0). In each subsequent Level, the pair of clusters with minimum distance are merged into a larger cluster. Then, the distance between the new cluster and the old ones is computed, using the specified clustering method (i.e. single-linkage clustering). The process is repeated until all actors end up in the same cluster. </p><p>Select an input matrix, a distance/dissimilarity metric and a clustering method (criterion) for the hierarchical cluster analysis. </p></body></html> Qt::RichText true 170 0 Clustering method (criterion): 200 0 Qt::StrongFocus <html><head/><body><p>Supported linkage criteria for agglomerative hierarchical clustering: </p><p><span style=" font-weight:600;">Single-linkage (minimum)</span>: The distance between two clusters will be determined by a single element pair, namely those two elements (one in each cluster) that have the shortest distance between them. In each step, the clusters that have the shortest distance will be merged. </p><p><span style=" font-weight:600;">Complete-linkage (maximum)</span>: The distance between two clusters will be determined by any two elements (one in each cluster) that have the longest distance between them. </p><p><span style=" font-weight:600;">Average-linkage (UPGMA)</span>: The distance between two clusters A and B is equal to the average of distances between all pairs of elements in A and B. </p><p><br/></p></body></html> Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok 170 0 Input matrix: 200 0 Qt::StrongFocus <html><head/><body><p><span style=" font-weight:600;">Adjacency:</span> Use the active network's adjacency matrix as input to hierarchical clustering.</p><p><span style=" font-weight:600;">Distances:</span> Use the active network's geodedic distances matrix as input. </p></body></html> 170 0 Distance/dissimilarity metric: 200 0 Qt::StrongFocus <html><head/><body><p>Supported distance metrics for hierarchical clustering:</p><p><span style=" font-weight:600;">Euclidean distance</span>: The square root of the sum of squared differences between tie/distance profiles.</p><p><span style=" font-weight:600;">Jaccard distance</span>: The Jaccard index J is the ratio of same ties/distances reported by each pair of actors to the total number of their ties. Does not count absent ties. The Jaccard distance is 1 - J</p><p><span style=" font-weight:600;">Hamming distance</span>: The number of ties/distances to other actors which differ between each pair of actors.</p><p><span style=" font-weight:600;">Manhattan distance</span>: The sum of absolute differences between tie/distance profiles.<br/></p></body></html> Enable to include matrix diagonal in calculations Include input matrix diagonal Qt::Vertical 20 40 SocNetV-3.7/src/forms/dialogdatasetselect.cpp000077500000000000000000000113741524402210300213340ustar00rootroot00000000000000/** * @file dialogdatasetselect.cpp * @brief Implements the DialogDatasetSelect class for selecting datasets in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogdatasetselect.h" #include #include "forms_logging.h" #include DialogDataSetSelect::DialogDataSetSelect (QWidget *parent) : QDialog (parent), ui(new Ui::DialogDataSetSelect) { ui->setupUi(this); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setDefault(true); datasets_list << "Krackhardt: High-tech managers (multirelational), 24 actors" << "Padgett: Florentine Families (marital and business relations), 16 actors" << "Zachary: Karate Club (binary & valued ties), 34 actors" << "Bernard: Killworth Fraternity (multirelational), 58 actors" << "Thurman: In the office: Networks and Coalitions, 15 actors" << "Stokman-Ziegler: Corporate Interlocks in Netherlands, 16 actors" << "Stokman-Ziegler: Corporate Interlocks in West Germany, 15 actors" << "Galaskiewicz: CEOs and clubs (affiliation data)" << "Freeman's EIES networks (multirelational, 32 actors)" << "Freeman: EIES network, at time-1, 48 actors" << "Freeman: EIES network, at time-2, 48 actors" << "Freeman: EIES network, number of messages, 48 actors" << "Freeman: The 34 possible graphs with N=5 (as multirelational), 5 actors" << "Mexican Power Network in the 1940s (list format)" << "Knoke: Bureaucracies Information & Money Exchange Network, 10 actors, 2 relationships" << "Stephenson and Zelen (1989): Network of 40 AIDS patients (sex relationship)" << "Stephenson and Zelen (1989): Information Centrality test dataset, 5 actors" << "Dunbar and Dunbar (1975): Network of Gelada baboon colony (H22a), 12 actors" << "Wasserman and Faust: star, circle and line graphs of 7 actors (multirelational)" << "Wasserman and Faust: Countries Trade (basic manufactured goods), 24 actors" << "Borgatti (1992): Campnet dataset, 18 actors" << "Petersen graph: A non-planar, undirected graph with 10 vertices and 15 edges" << "Herschel graph: The smallest nonhamiltonian polyhedral graph. 11 nodes, 18 edges"; datasets_filenames << "Krackhardt_High-tech_managers.paj" << "Padgett_Florentine_Families.paj" << "Zachary_Karate_Club.dl" << "Bernard_Killworth_Fraternity.dl" << "Thurman_Office_Networks_Coalitions.dl" << "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" << "Stokman_Ziegler_Corporate_Interlocks_West_Germany.dl" << "Galaskiewicz_CEOs_and_clubs_affiliation_network_data.2sm" << "Freeman_EIES_networks_32actors.dl" << "Freeman_EIES_network_48actors_Acquaintanceship_at_time_1.dl" << "Freeman_EIES_network_48actors_Acquaintanceship_at_time_2.dl" << "Freeman_EIES_network_48actors_Messages.dl" << "Freeman_34_possible_graphs_with_N_5_multirelational.paj" << "Mexican_Power_Network_1940s.lst" << "Knoke_Bureaucracies_Network.pajek" << "Stephenson_Zelen_40_AIDS_patients_sex_contact.paj" << "Stephenson_Zelen_5actors_6edges_IC_test_dataset.paj" << "Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" << "Wasserman_Faust_7actors_star_circle_line_graphs.paj" << "Wasserman_Faust_Countries_Trade_Data_Basic_Manufactured_Goods.pajek" << "Campnet.paj" << "Petersen_Graph.paj" << "Herschel_Graph.paj"; (ui->selectBox)->insertItems( 1, datasets_list ); } void DialogDataSetSelect::getUserChoices(){ qCDebug(lcForms)<< "DialogDataSetSelect: gathering Data!..."; int index = (ui->selectBox)->currentIndex(); QString dataset_name = datasets_filenames[index]; qCDebug(lcForms)<< "DialogDataSetSelect: user selected: " << dataset_name; emit userChoices( dataset_name ); } void DialogDataSetSelect::on_buttonBox_accepted() { this->getUserChoices(); this->accept(); } void DialogDataSetSelect::on_buttonBox_rejected() { this->reject(); } DialogDataSetSelect::~DialogDataSetSelect(){ datasets_list.clear(); datasets_filenames.clear(); } SocNetV-3.7/src/forms/dialogdatasetselect.h000077500000000000000000000021511524402210300207720ustar00rootroot00000000000000/** * @file dialogdatasetselect.h * @brief Declares the DialogDatasetSelect class for selecting datasets in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGDATASETSELECT_H #define DIALOGDATASETSELECT_H #include #include "ui_dialogdatasetselect.h" class DialogDataSetSelect: public QDialog { Q_OBJECT public: DialogDataSetSelect (QWidget *parent = Q_NULLPTR); ~DialogDataSetSelect(); public slots: void getUserChoices(); signals: void userChoices(QString); private slots: void on_buttonBox_accepted(); void on_buttonBox_rejected(); private: Ui::DialogDataSetSelect *ui; QStringList datasets_list, datasets_filenames; }; #endif SocNetV-3.7/src/forms/dialogdatasetselect.ui000066400000000000000000000060131524402210300211560ustar00rootroot00000000000000 DialogDataSetSelect true 0 0 600 200 0 0 600 200 800 400 Famous SNA data sets 400 90 <html><head/><body><p>Automatically recreate and visualize known data sets of Social Network Analysis, such as Padgett's Florentine families, Zachary's Karate Club, Knoke's Bureaucracies, etc.</p><p>Select the data set you want to re-create from the list.</p></body></html> Qt::TextFormat::RichText true 300 0 Qt::FocusPolicy::StrongFocus <html><head/><body><p>Click to select a data set</p></body></html> Qt::Orientation::Vertical 20 40 Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok SocNetV-3.7/src/forms/dialogdissimilarities.cpp000077500000000000000000000041621524402210300217020ustar00rootroot00000000000000/** * @file dialogdissimilarities.cpp * @brief Implements the DialogDissimilarities class for managing dissimilarity calculations in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogdissimilarities.h" #include #include "forms_logging.h" #include DialogDissimilarities::DialogDissimilarities (QWidget *parent) : QDialog (parent) { ui.setupUi(this); (ui.buttonBox)->button (QDialogButtonBox::Ok)->setDefault(true); variablesLocationList << "Rows" << "Columns" << "Both"; metricList << tr("Euclidean distance") << tr("Manhattan distance") << tr("Hamming distance") << tr("Jaccard distance") << tr("Chebyshev distance"); (ui.variablesLocationSelect)->insertItems( 1, variablesLocationList ); (ui.metricSelect)->insertItems( 1, metricList ); (ui.diagonalCheckBox)->setChecked(false); } void DialogDissimilarities::getUserChoices(){ qCDebug(lcForms)<< "DialogDissimilarities: gathering Data!..."; QString varLocation = (ui.variablesLocationSelect)->currentText(); QString metric = (ui.metricSelect)->currentText(); bool diagonal = (ui.diagonalCheckBox)->isChecked(); qCDebug(lcForms)<< "DialogDissimilarities: user selected: " << varLocation << metric; emit userChoices( metric, varLocation, diagonal ); } void DialogDissimilarities::on_buttonBox_accepted() { this->getUserChoices(); this->accept(); } void DialogDissimilarities::on_buttonBox_rejected() { this->reject(); } DialogDissimilarities::~DialogDissimilarities(){ metricList.clear(); variablesLocationList.clear(); } SocNetV-3.7/src/forms/dialogdissimilarities.h000077500000000000000000000023601524402210300213450ustar00rootroot00000000000000/** * @file dialogdissimilarities.h * @brief Declares the DialogDissimilarities class for managing dissimilarity calculations in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGDISSIMILARITIES_H #define DIALOGDISSIMILARITIES_H #include #include "ui_dialogdissimilarities.h" class DialogDissimilarities: public QDialog { Q_OBJECT public: DialogDissimilarities (QWidget *parent = Q_NULLPTR); ~DialogDissimilarities(); public slots: void getUserChoices(); signals: void userChoices(const QString &metric, const QString &varLocation, const bool &diagonal); private slots: void on_buttonBox_accepted(); void on_buttonBox_rejected(); private: Ui::DialogDissimilarities ui; QStringList variablesLocationList, metricList; }; #endif SocNetV-3.7/src/forms/dialogdissimilarities.ui000066400000000000000000000155611524402210300215370ustar00rootroot00000000000000 DialogDissimilarities true 0 0 700 350 0 0 700 350 800 600 Tie profile dissimilarities Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok Distance metric: 200 0 Qt::StrongFocus <html><head/><body><p>Select a matching method. </p><p><span style=" font-weight:600;">Exact Matches</span>: Examines pairs of actors for exact tie or distance matches (present or absent) to other actors and returns a proportion to their overall ties. </p><p><span style=" font-weight:600;">Positive Matches (Jaccard/Co-citation)</span>: Looks for same ties/distances reported by both actors. Returns the ratio to the total number of ties reported.</p><p><span style=" font-weight:600;">Hamming distance</span>: Reports the number of ties/distances to other actors which differ between each pair of actors.</p><p><span style=" font-weight:600;">Cosine similarity</span>: Computes the pair-wise similarity of actors as the dot product of their tie/distance vectors divided by the magnitude of these vectors. <br/></p></body></html> 0 0 400 150 <html><head/><body><p>Compute a <span style=" font-weight:600;">dissimilarities matrix</span>, where each element (i,j) is the pair-wise distance / dissimilarity of actors i and j tie profiles to all other actors, according to a selected metric. </p><p>Select a distance metric. For example, the &quot;Euclidean distance&quot; is the square root of the sum of the squared differences of tie values that actors i and j have to other actors. Hover over &quot;Distance Metric&quot; select box for more info on each metric.</p><p>Also, specify where the &quot;variables&quot; are. For instance, select Rows to measure the outbound ties between all pairs of actors. Select Both to measure both inbound and outbound ties. </p></body></html> Qt::RichText true Variables in: 200 0 Qt::StrongFocus <html><head/><body><p><span style=" font-weight:600;">Rows: </span>Correlate outbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Columns: </span>Correlate inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Both: </span> Correlate both outbound and inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><br/></p></body></html> 12 Qt::Vertical 20 20 Enable to include matrix diagonal in calculations Include input matrix diagonal Qt::Vertical 20 15 SocNetV-3.7/src/forms/dialogedgedichotomization.cpp000077500000000000000000000025571524402210300225430ustar00rootroot00000000000000/** * @file dialogedgedichotomization.cpp * @brief Implements the DialogEdgeDichotomization class for managing edge dichotomization in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogedgedichotomization.h" #include #include #include "forms_logging.h" DialogEdgeDichotomization::DialogEdgeDichotomization (QWidget *parent) : QDialog (parent) { ui.setupUi(this); connect ( ui.buttonBox,SIGNAL(accepted()), this, SLOT(getUserChoices()) ); (ui.buttonBox)->button (QDialogButtonBox::Ok)->setDefault(true); } void DialogEdgeDichotomization::getUserChoices(){ qCDebug(lcForms)<< "Dialog: gathering Data!..."; qreal my_threshold = ui.weightThreshold->value() ; qCDebug(lcForms)<< "DialogEdgeDichotomization::getUserChoices() - We will dichotomize edges according to threshold: " << my_threshold; qCDebug(lcForms)<< "Dialog: emitting userChoices" ; emit userChoices( my_threshold ); } SocNetV-3.7/src/forms/dialogedgedichotomization.h000077500000000000000000000020251524402210300221760ustar00rootroot00000000000000/** * @file dialogedgedichotomization.h * @brief Declares the DialogEdgeDichotomization class for managing edge dichotomization in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGEDGEDICHOTOMIZATION_H #define DIALOGEDGEDICHOTOMIZATION_H #include #include "ui_dialogedgedichotomization.h" class DialogEdgeDichotomization : public QDialog { Q_OBJECT public: explicit DialogEdgeDichotomization (QWidget *parent = Q_NULLPTR); public slots: void getUserChoices (); signals: void userChoices( qreal threshold); private: Ui::DialogEdgeDichotomization ui; }; #endif SocNetV-3.7/src/forms/dialogedgedichotomization.ui000066400000000000000000000107631524402210300223710ustar00rootroot00000000000000 DialogEdgeDichotomization 0 0 600 200 600 200 Dichotomize Edges 0 65 <html><head/><body><p>Enter a threshold value to dichotomize the edges of a valued network, in order to create a new binary relation. All ties with equal or higher values will be set to 1, and all lower will be removed. </p></body></html> Qt::TextFormat::RichText true 6 QLayout::SizeConstraint::SetDefaultConstraint 0 0 Weight Threshold Qt::Orientation::Horizontal 40 20 60 0 80 100 1 -100.000000000000000 Qt::Orientation::Vertical 20 15 Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok Qt::Orientation::Vertical 20 12 buttonBox accepted() DialogEdgeDichotomization accept() 248 254 157 274 buttonBox rejected() DialogEdgeDichotomization reject() 316 260 286 274 SocNetV-3.7/src/forms/dialogedgeedit.cpp000066400000000000000000000133421524402210300202530ustar00rootroot00000000000000/** * @file dialogedgeedit.cpp * @brief Implements the DialogEdgeEdit class for editing edge properties. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogedgeedit.h" #include "ui_dialogedgeedit.h" #include #include "forms_logging.h" #include #include #include #include #include DialogEdgeEdit::DialogEdgeEdit(QWidget *parent, const int &v1, const int &v2, const QString &label, const double &weight, const QColor &color, const QHash &customAttributes) : QDialog(parent), ui(new Ui::DialogEdgeEdit), m_v1(v1), m_v2(v2), m_label(label), m_weight(weight), m_color(color), m_customAttributes(customAttributes) { ui->setupUi(this); qCDebug(lcForms) << "DialogEdgeEdit: edge" << v1 << "->" << v2 << "label" << label << "weight" << weight << "color" << color << "attrs" << customAttributes; ui->edgeTitleLabel->setText(tr("Edge %1 \u2192 %2").arg(v1).arg(v2)); ui->labelEdit->setText(m_label); ui->weightSpin->setValue(m_weight); m_pixmap = QPixmap(60, 20); m_pixmap.fill(m_color); ui->colorButton->setIcon(QIcon(m_pixmap)); // Populate custom attributes table ui->customAttributesTable->setRowCount(0); for (auto it = customAttributes.cbegin(); it != customAttributes.cend(); ++it) { int row = ui->customAttributesTable->rowCount(); ui->customAttributesTable->insertRow(row); ui->customAttributesTable->setItem(row, 0, new QTableWidgetItem(it.key())); ui->customAttributesTable->setItem(row, 1, new QTableWidgetItem(it.value())); } connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &DialogEdgeEdit::getUserChoices); connect(ui->colorButton, &QToolButton::clicked, this, &DialogEdgeEdit::selectColor); connect(ui->addPropertyBtn, &QPushButton::clicked, this, &DialogEdgeEdit::on_addPropertyBtn_clicked); connect(ui->customAttributesTable, &QTableWidget::itemSelectionChanged, [this]() { ui->removePropertyBtn->setEnabled( !ui->customAttributesTable->selectedItems().isEmpty()); }); connect(ui->removePropertyBtn, &QPushButton::clicked, this, &DialogEdgeEdit::on_removePropertyBtn_clicked); ui->buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); ui->labelEdit->setFocus(); } DialogEdgeEdit::~DialogEdgeEdit() { delete ui; } /** * @brief Opens a color picker and updates the color button icon. */ void DialogEdgeEdit::selectColor() { QColor chosen = QColorDialog::getColor(m_color, this, tr("Select edge color")); if (chosen.isValid()) { m_color = chosen; m_pixmap.fill(m_color); ui->colorButton->setIcon(QIcon(m_pixmap)); } } /** * @brief Adds a key/value pair from the input fields to the attributes table. */ void DialogEdgeEdit::on_addPropertyBtn_clicked() { QString key = ui->keyLineEdit->text().trimmed(); QString value = ui->valueLineEdit->text().trimmed(); QGraphicsColorizeEffect *effect = nullptr; if (key.isEmpty()) { effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui->keyLineEdit->setGraphicsEffect(effect); } else { ui->keyLineEdit->setGraphicsEffect(nullptr); } if (value.isEmpty()) { effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui->valueLineEdit->setGraphicsEffect(effect); } else { ui->valueLineEdit->setGraphicsEffect(nullptr); } if (key.isEmpty() || value.isEmpty()) return; m_customAttributes.insert(key, value); int row = ui->customAttributesTable->rowCount(); ui->customAttributesTable->insertRow(row); ui->customAttributesTable->setItem(row, 0, new QTableWidgetItem(key)); ui->customAttributesTable->setItem(row, 1, new QTableWidgetItem(value)); ui->keyLineEdit->clear(); ui->valueLineEdit->clear(); } /** * @brief Removes the selected row from the attributes table. */ void DialogEdgeEdit::on_removePropertyBtn_clicked() { int row = ui->customAttributesTable->currentRow(); if (row != -1) { QString key = ui->customAttributesTable->item(row, 0)->text(); m_customAttributes.remove(key); ui->customAttributesTable->removeRow(row); } ui->removePropertyBtn->setEnabled(false); } /** * @brief Collects all field values and emits userChoices(). */ void DialogEdgeEdit::getUserChoices() { qCDebug(lcForms) << "DialogEdgeEdit::getUserChoices()"; m_label = ui->labelEdit->text(); m_weight = ui->weightSpin->value(); // Rebuild custom attributes from table (handles in-place cell edits) m_customAttributes.clear(); for (int i = 0; i < ui->customAttributesTable->rowCount(); ++i) { QTableWidgetItem *keyItem = ui->customAttributesTable->item(i, 0); QTableWidgetItem *valueItem = ui->customAttributesTable->item(i, 1); if (keyItem && valueItem && !keyItem->text().isEmpty()) m_customAttributes.insert(keyItem->text(), valueItem->text()); } emit userChoices(m_label, m_weight, m_color, m_customAttributes); } SocNetV-3.7/src/forms/dialogedgeedit.h000066400000000000000000000033461524402210300177230ustar00rootroot00000000000000/** * @file dialogedgeedit.h * @brief Declares the DialogEdgeEdit class for editing edge properties. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGEDGEEDIT_H #define DIALOGEDGEEDIT_H #include #include #include #include namespace Ui { class DialogEdgeEdit; } class DialogEdgeEdit : public QDialog { Q_OBJECT public: explicit DialogEdgeEdit(QWidget *parent, const int &v1, const int &v2, const QString &label, const double &weight, const QColor &color, const QHash &customAttributes = QHash()); ~DialogEdgeEdit(); private slots: void getUserChoices(); void selectColor(); void on_addPropertyBtn_clicked(); void on_removePropertyBtn_clicked(); signals: void userChoices(const QString &label, const double &weight, const QColor &color, const QHash &customAttributes); private: Ui::DialogEdgeEdit *ui; int m_v1, m_v2; QString m_label; double m_weight; QColor m_color; QHash m_customAttributes; QPixmap m_pixmap; }; #endif SocNetV-3.7/src/forms/dialogedgeedit.ui000066400000000000000000000156551524402210300201170ustar00rootroot00000000000000 DialogEdgeEdit 0 0 480 420 480 380 Edge Properties 11 true Edge Label: Weight: Qt::Horizontal 40 20 -999.000000000000000 999.000000000000000 2 0.100000000000000 1.000000000000000 Color (click to change): Qt::Horizontal 40 20 60 25 ... 60 20 Custom Attributes 0 100 QAbstractItemView::SelectRows Key Value Key Value Add 0 0 Remove selected false Qt::Horizontal 40 20 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() DialogEdgeEdit accept() buttonBox rejected() DialogEdgeEdit reject() SocNetV-3.7/src/forms/dialogexportimage.cpp000066400000000000000000000145751524402210300210360ustar00rootroot00000000000000/** * @file dialogexportimage.cpp * @brief Implements the DialogExportImage class for exporting network visualizations as images in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogexportimage.h" #include "ui_dialogexportimage.h" #include #include #include #include "forms_logging.h" #include #include DialogExportImage::DialogExportImage(QWidget *parent) : QDialog(parent), ui(new Ui::DialogExportImage) { ui->setupUi(this); // Get supported Image formats QStringList imgFormats; QByteArray bytes; foreach (bytes, QImageWriter::supportedImageFormats()) { imgFormats << QString(bytes); } ui->formatSelect->addItems(imgFormats); // Connect dialog signals to slots connect ( ui->fileDirSelectButton, &QToolButton::clicked, this, &DialogExportImage::getFilename); connect ( ui->formatSelect, SIGNAL(currentTextChanged (const QString &)), this, SLOT ( getFormat(const QString &)) ); connect ( ui->buttonBox,SIGNAL(accepted()), this, SLOT(getUserChoices()) ); // Prepare Quality slider and spin box, and connect them changeQualityRange(1,100,1); connect ( ui->qualitySlider, SIGNAL(valueChanged(int)), ui->qualitySpinBox,SLOT(setValue(int)) ); connect ( ui->qualitySpinBox, SIGNAL(valueChanged(int)), ui->qualitySlider,SLOT(setValue(int)) ); ui->qualitySlider->setValue(100); // Prepare Compression slider and spin box, and connect them changeCompressionRange(1,100,1); connect ( ui->compressionSlider, SIGNAL(valueChanged(int)), ui->compressionSpinBox,SLOT(setValue(int)) ); connect ( ui->compressionSpinBox, SIGNAL(valueChanged(int)), ui->compressionSlider,SLOT(setValue(int)) ); ui->compressionSlider->setValue(0); // Set default button // OK button is disabled until user has selected a filename. (ui->buttonBox)->button (QDialogButtonBox::Cancel)->setDefault(true); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); // Set which widget will have focus ui->fileDirSelectButton->setFocus(Qt::OtherFocusReason); } /** * @brief DialogExportImage::~DialogExportImage */ DialogExportImage::~DialogExportImage() { delete ui; } /** * @brief Changes Compression widgets range and stepping * @param min * @param max * @param step */ void DialogExportImage::changeCompressionRange(const int &min, const int &max, const int &step) { ui->compressionSlider->setSingleStep(step); ui->compressionSlider->setTickInterval(step); ui->compressionSpinBox->setSingleStep(step); ui->compressionSpinBox->setRange(min,max); } /** * @brief Changes Quality widgets range and stepping * @param min * @param max * @param step */ void DialogExportImage::changeQualityRange(const int &min, const int &max, const int &step){ ui->qualitySlider->setSingleStep(step); ui->qualitySlider->setTickInterval(step); ui->qualitySpinBox->setSingleStep(step); ui->qualitySpinBox->setRange(min,max); } /** * @brief Opens a dialog to get the filename of the new image to export */ void DialogExportImage::getFilename(){ QString m_format = ui->formatSelect->currentText().toLower(); QString m_filter = m_format.toUpper() + " (*." + m_format + ")"; QString m_fileName = QFileDialog::getSaveFileName(this, tr("Save to image"), "", m_filter); if (!m_fileName.isEmpty() && QFileInfo(m_fileName).absoluteDir().exists() ) { if ( QFileInfo(m_fileName).suffix().isEmpty() ) { m_fileName.append("." +m_format); } else if ( QString::compare( QFileInfo(m_fileName).suffix() , m_format, Qt::CaseInsensitive ) ) { m_fileName.append("." +m_format); } ui->fileEdit->setText(m_fileName); ui->fileEdit->setGraphicsEffect(0); ui->fileDirSelectButton->setGraphicsEffect(0); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(true); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setDefault(true); } else { qCDebug(lcForms) << "Empty filaname or dir does not exist"; QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui->fileEdit->setGraphicsEffect(effect); ui->fileDirSelectButton->setGraphicsEffect(effect); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); (ui->buttonBox)->button (QDialogButtonBox::Cancel)->setDefault(true); } } /** * @brief Gets the file format of the Image */ void DialogExportImage::getFormat(const QString &format){ QString m_format = format.toLower(); qCDebug(lcForms) << "format:" << m_format; QString m_fileName = ui->fileEdit->text(); qCDebug(lcForms) << "filename" << m_fileName; qCDebug(lcForms) << "suffix" << QFileInfo(m_fileName).suffix(); if ( QString::compare(QFileInfo(m_fileName).suffix() , m_format, Qt::CaseInsensitive) != 0 ) { // User filename suffix differs from selected format. Correct the filename suffix. m_fileName = QFileInfo(m_fileName).absolutePath() + QDir::separator() + QFileInfo(m_fileName).completeBaseName().append("."+m_format); qCDebug(lcForms) << "Corrected filename:" << m_fileName; } ui->fileEdit->setText(m_fileName); } void DialogExportImage::getUserChoices(){ QByteArray m_format = ui->formatSelect->currentText().toLower().toUtf8(); QString m_fileName = ui->fileEdit->text(); int m_quality = ui->qualitySpinBox->value(); int m_compression = ui->compressionSpinBox->value(); qCDebug(lcForms)<< "user choices: " << m_fileName << m_format << m_quality << m_compression; emit userChoices(m_fileName, m_format, m_quality, m_compression); } SocNetV-3.7/src/forms/dialogexportimage.h000066400000000000000000000026441524402210300204750ustar00rootroot00000000000000/** * @file dialogexportimage.h * @brief Declares the DialogExportImage class for exporting network visualizations as images in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGEXPORTIMAGE_H #define DIALOGEXPORTIMAGE_H #include namespace Ui { class DialogExportImage; } class DialogExportImage : public QDialog { Q_OBJECT public: explicit DialogExportImage(QWidget *parent = Q_NULLPTR); ~DialogExportImage(); void changeCompressionRange(const int &min, const int &max, const int &step); void changeQualityRange(const int &min, const int &max, const int &step); public slots: void getFilename(); void getFormat(const QString &format); void getUserChoices(); signals: void userChoices( const QString &filename, const QByteArray &format, const int &quality, const int &compression ); private: Ui::DialogExportImage *ui; }; #endif // DIALOGEXPORTIMAGE_H SocNetV-3.7/src/forms/dialogexportimage.ui000066400000000000000000000265411524402210300206650ustar00rootroot00000000000000 DialogExportImage 0 0 600 220 600 220 Export to Image Save to file: Qt::Orientation::Horizontal 48 20 220 0 <html><head/><body><p><span style=" font-weight:600;">Image filename</span></p><p>The path and the filename of the resulting image. </p><p>Click the button on the right to select a new filename.</p><p><br/></p></body></html> 50 0 50 16777215 PointingHandCursor <html><head/><body><p><span style=" font-size:10pt; font-weight:600;">Image filename</span></p><p><span style=" font-size:10pt;">Click this button to select the path and the filename of the resulting image.<br/></span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">PDF filename</span></p><p>Click this button to select the path and the filename of the PDF. </p><p><br/></p><p><br/></p></body></html> ... Format DejaVu Sans Qt::Orientation::Horizontal 98 20 0 0 50 0 50 16777215 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Image format </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Select the format of the resulting image. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">SocNetV automatically supports all image formats supported currently by Qt in your computer platform. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Click on this drop down to see all supported image formats and select a format for your image.</span></p></body></html> Quality Qt::Orientation::Horizontal 40 20 200 0 Qt::Orientation::Horizontal 50 0 50 16777215 100 100 Compression Qt::Orientation::Horizontal 18 20 200 0 Qt::Orientation::Horizontal 50 50 50 20 50 25 100 Qt::Orientation::Vertical QSizePolicy::Policy::Minimum 20 29 Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBox accepted() DialogExportImage accept() 248 254 157 274 buttonBox rejected() DialogExportImage reject() 316 260 286 274 SocNetV-3.7/src/forms/dialogexportpdf.cpp000066400000000000000000000121221524402210300205070ustar00rootroot00000000000000/** * @file dialogexportpdf.cpp * @brief Implements the DialogExportPDF class for exporting network visualizations as PDF files in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogexportpdf.h" #include "graphicswidget.h" #include "ui_dialogexportpdf.h" #include #include #include "forms_logging.h" #include #include DialogExportPDF::DialogExportPDF (QWidget *parent ) : QDialog (parent), ui(new Ui::DialogExportPDF) { ui->setupUi(this); m_fileName = ""; m_dpi = 75; m_printerMode = QPrinter::ScreenResolution; m_orientation = QPageLayout::Portrait; // Populate printer modes QStringList resList; resList << "Screen" << "Print"; ui->qualitySelect->addItems(resList); // Populate dpi (currently only 75dpi is supported) QStringList dpiList; dpiList << "75" << "300" << "600" << "1200"; ui->resolutionSelect->addItems(dpiList); ui->resolutionSelect->setDisabled(true); QStringList orientationList; orientationList << "Portrait" << "Landscape"; ui->orientationSelect->addItems(orientationList); // Connect dialog signals to slots connect (ui->fileDirSelectButton, &QToolButton::clicked, this, &DialogExportPDF::getFilename); connect(ui->qualitySelect, SIGNAL ( currentTextChanged (const QString &)), this, SLOT(getPrinterMode(const QString &)) ); connect ( ui->buttonBox,SIGNAL(accepted()), this, SLOT(getUserChoices()) ); // Set Cancel as default button // The OK button disabled until user selects a file. (ui->buttonBox)->button (QDialogButtonBox::Cancel)->setDefault(true); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); // Set which widget will have focus ui->fileDirSelectButton->setFocus(Qt::OtherFocusReason); } DialogExportPDF::~DialogExportPDF() { delete ui; } void DialogExportPDF::checkFilename(const QString &fileName){ m_fileName = fileName; if (!m_fileName.isEmpty() && QFileInfo(m_fileName).absoluteDir().exists() ) { if ( QFileInfo(m_fileName).suffix().isEmpty() ) { m_fileName.append(".pdf"); } else if ( QString::compare( QFileInfo(m_fileName).suffix() , "pdf", Qt::CaseInsensitive ) ) { qCDebug(lcForms) << "suffix() : " << QFileInfo(m_fileName).suffix(); m_fileName.append(".pdf"); } ui->fileEdit->setText(m_fileName); ui->fileEdit->setGraphicsEffect(0); ui->fileDirSelectButton->setGraphicsEffect(0); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(true); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setDefault(true); } else { qCDebug(lcForms) << "Empty filaname or dir does not exist"; QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui->fileEdit->setGraphicsEffect(effect); ui->fileDirSelectButton->setGraphicsEffect(effect); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); (ui->buttonBox)->button (QDialogButtonBox::Cancel)->setDefault(true); } } /** * @brief Gets the filename of the PDF */ void DialogExportPDF::getFilename(){ QString fileName = QFileDialog::getSaveFileName(this, tr("Save to pdf"), "", tr("PDF (*.pdf)")); checkFilename(fileName); } /** * @brief Gets printer quality mode */ void DialogExportPDF::getPrinterMode(const QString &mode){ if (!mode.isEmpty() ) { // m_appSettings["canvasUpdateMode"] = mode; if ( mode == "screen" ){ m_printerMode = QPrinter::ScreenResolution; } else if ( mode == "print" ) { m_printerMode = QPrinter::PrinterResolution; } } } void DialogExportPDF::getUserChoices(){ qCDebug(lcForms)<< "Dialog: gathering Data!..."; // User might have entered the filename manually! if (m_fileName.isEmpty()) { getFilename(); } if ( ui->qualitySelect->currentText().contains("Screen")){ m_printerMode = QPrinter::ScreenResolution; } else { m_printerMode = QPrinter::PrinterResolution; } m_dpi = ui->resolutionSelect->currentText().toInt(); if ( ui->orientationSelect->currentText().contains("Portrait")) { m_orientation = QPageLayout::Portrait; } else { m_orientation = QPageLayout::Landscape; } qCDebug(lcForms)<< "Dialog: emitting userChoices" ; emit userChoices( m_fileName, m_orientation, m_dpi, m_printerMode ); } SocNetV-3.7/src/forms/dialogexportpdf.h000066400000000000000000000030541524402210300201600ustar00rootroot00000000000000/** * @file dialogexportpdf.h * @brief Declares the DialogExportPDF class for exporting network visualizations as PDF files in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGEXPORTPDF_H #define DIALOGEXPORTPDF_H #include #include #include namespace Ui { class DialogExportPDF; } class DialogExportPDF : public QDialog { Q_OBJECT public: explicit DialogExportPDF (QWidget *parent = Q_NULLPTR ); ~DialogExportPDF(); public slots: void checkFilename(const QString &fileName = QString()); void getFilename(); void getPrinterMode(const QString &mode); void getUserChoices (); signals: void userChoices( QString &filename, const QPageLayout::Orientation &orientation, const int &dpi, const QPrinter::PrinterMode printerMode, const QPageSize &pageSize = QPageSize(QPageSize::A4)); private: QString m_fileName; int m_dpi; QPageLayout::Orientation m_orientation; QPrinter::PrinterMode m_printerMode; Ui::DialogExportPDF *ui; }; #endif // DIALOGEXPORTPDF_H SocNetV-3.7/src/forms/dialogexportpdf.ui000066400000000000000000000364351524402210300203570ustar00rootroot00000000000000 DialogExportPDF 0 0 600 265 600 220 Export to PDF Page Orientation DejaVu Sans Qt::Orientation::Horizontal 98 20 0 0 170 0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Page Orientation</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Select the page orientation in the PDF: Portrait or landscape. </span></p></body></html> Save to file: Qt::Orientation::Horizontal 48 20 220 0 <html><head/><body><p><span style=" font-weight:600;">PDF filename</span></p><p>The path and the filename of the PDF. </p><p>Click the button on the right to select a new filename.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">PDF filename</span></p><p>The path and the filename of the PDF. </p><p>Click the button on the right to select a new filename.</p><p><br/></p></body></html> PointingHandCursor <html><head/><body><p><span style=" font-size:10pt; font-weight:600;">PDF filename</span></p><p><span style=" font-size:10pt;">Click this button to select the path and the filename of the PDF. <br/></span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">PDF filename</span></p><p>Click this button to select the path and the filename of the PDF. </p><p><br/></p><p><br/></p></body></html> ... Quality DejaVu Sans Qt::Orientation::Horizontal 98 20 0 0 170 0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">PDF Quality</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Select the quality of the PDF. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">The default value is Screen, which results in a PDF exactly like what you see on the application canvas.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; text-decoration: underline;">Screen</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt; font-weight:600;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Sets the resolution of the print device to the screen resolution. This has the big advantage that the results obtained when painting on the printer will match more or less exactly the visible output on the screen. It is the easiest to use, as font metrics on the screen and on the printer are the same. This is the default value.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; text-decoration: underline;">Print</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">On Windows, sets the printer resolution to that defined for the printer in use. For PDF printing, sets the resolution of the PDF driver to 1200 dpi.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p></body></html> Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok Resolution (DPI) DejaVu Sans Qt::Orientation::Horizontal 98 20 0 0 170 0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">DPI</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Select the PDF resolution in DPI.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">The default value is 75, to match screen resolution.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p></body></html> buttonBox accepted() DialogExportPDF accept() 248 254 157 274 buttonBox rejected() DialogExportPDF reject() 316 260 286 274 SocNetV-3.7/src/forms/dialogfilterbyattribute.cpp000066400000000000000000000067541524402210300222560ustar00rootroot00000000000000/** * @file dialogfilterbyattribute.cpp * @brief Implements DialogFilterByAttribute. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #include "dialogfilterbyattribute.h" #include "ui_dialogfilterbyattribute.h" #include #include DialogFilterByAttribute::DialogFilterByAttribute(const QStringList &nodeKeys, const QStringList &edgeKeys, QWidget *parent) : QDialog(parent), ui(new Ui::DialogFilterByAttribute), m_nodeKeys(nodeKeys), m_edgeKeys(edgeKeys) { ui->setupUi(this); repopulateKeys(FilterCondition::Scope::Nodes); connect(ui->nodesRadio, &QRadioButton::toggled, this, &DialogFilterByAttribute::onScopeChanged); connect(ui->edgesRadio, &QRadioButton::toggled, this, &DialogFilterByAttribute::onScopeChanged); connect(ui->bothRadio, &QRadioButton::toggled, this, &DialogFilterByAttribute::onScopeChanged); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &DialogFilterByAttribute::getUserChoices); connect(ui->queryBuilderBtn, &QPushButton::clicked, this, [this]() { m_qbRequested = true; reject(); }); ui->valueEdit->setFocus(); } DialogFilterByAttribute::~DialogFilterByAttribute() { delete ui; } void DialogFilterByAttribute::onScopeChanged() { FilterCondition::Scope scope = FilterCondition::Scope::Nodes; if (ui->edgesRadio->isChecked()) scope = FilterCondition::Scope::Edges; else if (ui->bothRadio->isChecked()) scope = FilterCondition::Scope::Both; repopulateKeys(scope); } void DialogFilterByAttribute::repopulateKeys(FilterCondition::Scope scope) { QStringList keys; switch (scope) { case FilterCondition::Scope::Nodes: keys = m_nodeKeys; break; case FilterCondition::Scope::Edges: keys = m_edgeKeys; break; case FilterCondition::Scope::Both: { QSet merged(m_nodeKeys.cbegin(), m_nodeKeys.cend()); merged.unite(QSet(m_edgeKeys.cbegin(), m_edgeKeys.cend())); keys = QStringList(merged.cbegin(), merged.cend()); keys.sort(); break; } } const QString current = ui->keyCombo->currentText(); ui->keyCombo->clear(); ui->keyCombo->addItems(keys); // Restore user's typed key if still relevant if (!current.isEmpty()) ui->keyCombo->setCurrentText(current); } void DialogFilterByAttribute::getUserChoices() { FilterCondition cond; if (ui->edgesRadio->isChecked()) cond.scope = FilterCondition::Scope::Edges; else if (ui->bothRadio->isChecked()) cond.scope = FilterCondition::Scope::Both; else cond.scope = FilterCondition::Scope::Nodes; cond.key = ui->keyCombo->currentText().trimmed(); cond.value = ui->valueEdit->text().trimmed(); static const FilterCondition::Op opMap[] = { FilterCondition::Op::Eq, FilterCondition::Op::Neq, FilterCondition::Op::Gt, FilterCondition::Op::Lt, FilterCondition::Op::Gte, FilterCondition::Op::Lte, FilterCondition::Op::Contains }; const int idx = ui->opCombo->currentIndex(); cond.op = (idx >= 0 && idx < 7) ? opMap[idx] : FilterCondition::Op::Eq; emit userChoices(cond); } SocNetV-3.7/src/forms/dialogfilterbyattribute.h000066400000000000000000000024721524402210300217140ustar00rootroot00000000000000/** * @file dialogfilterbyattribute.h * @brief Declares DialogFilterByAttribute for filtering nodes/edges by a custom attribute. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #ifndef DIALOGFILTERBYATTRIBUTE_H #define DIALOGFILTERBYATTRIBUTE_H #include #include #include "graph/filters/filter_condition.h" namespace Ui { class DialogFilterByAttribute; } class DialogFilterByAttribute : public QDialog { Q_OBJECT public: explicit DialogFilterByAttribute(const QStringList &nodeKeys, const QStringList &edgeKeys, QWidget *parent = nullptr); ~DialogFilterByAttribute(); bool wasQueryBuilderRequested() const { return m_qbRequested; } private slots: void onScopeChanged(); void getUserChoices(); signals: void userChoices(const FilterCondition &cond); private: Ui::DialogFilterByAttribute *ui; QStringList m_nodeKeys; QStringList m_edgeKeys; bool m_qbRequested = false; void repopulateKeys(FilterCondition::Scope scope); }; #endif // DIALOGFILTERBYATTRIBUTE_H SocNetV-3.7/src/forms/dialogfilterbyattribute.ui000066400000000000000000000153331524402210300221020ustar00rootroot00000000000000 DialogFilterByAttribute 0 0 420 340 380 300 Filter by Attribute 12 10 Show only nodes or edges whose custom attribute matches the condition below. Non-matching elements are hidden (reversible). true Scope Nodes true Edges Both Qt::Orientation::Horizontal 4020 Condition 8 6 Key: Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter 10 true Select an attribute key or type a custom one. Operator: Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter = (equals) β‰  (not equals) > (greater than) < (less than) β‰₯ (greater or equal) ≀ (less or equal) contains Value: Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter Enter value… Use Query Builder… true Open the Query Builder to filter by multiple conditions combined with AND logic. Qt::Orientation::Horizontal 4020 Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBoxaccepted() DialogFilterByAttributeaccept() 200280 200150 buttonBoxrejected() DialogFilterByAttributereject() 200280 200150 SocNetV-3.7/src/forms/dialogfilteredgesbyweight.cpp000077500000000000000000000031751524402210300225470ustar00rootroot00000000000000/** * @file dialogfilteredgesbyweight.cpp * @brief Implements the DialogFilterEdgesByWeight class for filtering edges based on weight in the social network visualizer. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogfilteredgesbyweight.h" #include #include #include "forms_logging.h" DialogFilterEdgesByWeight::DialogFilterEdgesByWeight(QWidget *parent) : QDialog(parent) { ui.setupUi(this); connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(getUserChoices())); (ui.buttonBox)->button(QDialogButtonBox::Ok)->setDefault(true); (ui.overThresholdBt)->setChecked(true); } void DialogFilterEdgesByWeight::getUserChoices(){ qCDebug(lcForms)<< "Dialog: gathering Data!..."; bool overThreshold=false; float my_threshold = static_cast ( (ui.weightThreshold)->value() ); if ( ui.overThresholdBt->isChecked() ) { qCDebug(lcForms)<< "Dialog: We will filter edges weighted more than threshold: " << my_threshold; overThreshold = true; } else { qCDebug(lcForms)<< "Dialog: We will filter edges weighted less than threshold: " << my_threshold; overThreshold = false; } qCDebug(lcForms)<< "Dialog: emitting userChoices" ; emit userChoices( my_threshold, overThreshold ); } SocNetV-3.7/src/forms/dialogfilteredgesbyweight.h000077500000000000000000000020641524402210300222100ustar00rootroot00000000000000/** * @file dialogfilteredgesbyweight.h * @brief Defines the DialogFilterEdgesByWeight class for filtering edges based on weight in the social network visualizer. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGFILTEREDGESBYWEIGHT_H #define DIALOGFILTEREDGESBYWEIGHT_H #include #include "ui_dialogfilteredgesbyweight.h" class DialogFilterEdgesByWeight : public QDialog { Q_OBJECT public: explicit DialogFilterEdgesByWeight (QWidget *parent = Q_NULLPTR); public slots: void getUserChoices (); signals: void userChoices(const qreal, const bool); private: Ui::DialogFilterEdgesByWeight ui; }; #endif SocNetV-3.7/src/forms/dialogfilteredgesbyweight.ui000066400000000000000000000117001524402210300223700ustar00rootroot00000000000000 DialogFilterEdgesByWeight 0 0 600 275 600 275 Filter Edges by Weight Select a weight threshold and choose which edges to hide. Hidden edges are not deleted β€” use Restore All Edges to show them again. Qt::TextFormat::RichText true 6 QLayout::SizeConstraint::SetDefaultConstraint 0 0 Weight Threshold: Liberation Sans Qt::Orientation::Horizontal 40 20 60 0 <html><head/><body><p>Enter the weight threshold. Edges above or below this value will be hidden depending on the selected option below.</p></body></html> Enter the weight threshold. Edges will be hidden based on the option selected below. Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 1 -100.000000000000000 Select behaviour: Hide edges with weight below the threshold (keep edges with weight β‰₯ threshold) Hide edges with weight above the threshold (keep edges with weight ≀ threshold) Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBox accepted() DialogFilterEdgesByWeight accept() 248 254 157 274 buttonBox rejected() DialogFilterEdgesByWeight reject() 316 260 286 274 SocNetV-3.7/src/forms/dialogfilternodesbycentrality.cpp000066400000000000000000000063071524402210300234540ustar00rootroot00000000000000/** * @file dialogfilternodesbycentrality.cpp * @brief Implements the DialogFilterNodesByCentrality class for filtering nodes based on their attributes. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogfilternodesbycentrality.h" #include #include #include "forms_logging.h" #include DialogFilterNodesByCentrality::DialogFilterNodesByCentrality( const QStringList &indexNames, const QVector &computedMask, QWidget *parent) : QDialog(parent) { ui.setupUi(this); ui.indexComboBox->addItems(indexNames); auto *model = qobject_cast(ui.indexComboBox->model()); bool atLeastOneEnabled = false; if (model) { const int count = ui.indexComboBox->count(); for (int i = 0; i < count; ++i) { const bool computed = (i < computedMask.size()) ? computedMask[i] : false; if (!computed) { if (auto *item = model->item(i)) { item->setEnabled(false); item->setToolTip( tr("Not computed yet. Run the analysis first.")); } } else { atLeastOneEnabled = true; } } } // Auto-select first enabled index if (atLeastOneEnabled) { for (int i = 0; i < ui.indexComboBox->count(); ++i) { if (ui.indexComboBox->model()->index(i, 0) .flags() .testFlag(Qt::ItemIsEnabled)) { ui.indexComboBox->setCurrentIndex(i); break; } } } else { // No indices computed ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui.indexComboBox->setEnabled(false); } connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(getUserChoices())); ui.buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); ui.overThresholdBt->setChecked(true); } void DialogFilterNodesByCentrality::getUserChoices() { const float threshold = static_cast(ui.weightThreshold->value()); const bool overThreshold = ui.overThresholdBt->isChecked(); // NOTE: prominenceIndexList is 0-based but IndexType starts at 1 const IndexType centralityIndex = static_cast(ui.indexComboBox->currentIndex() + 1); qCDebug(lcForms) << "DialogFilterNodesByCentrality:" << "index:" << ui.indexComboBox->currentText() << "(" << static_cast(centralityIndex) << ")" << "threshold:" << threshold << "overThreshold:" << overThreshold; emit userChoices(threshold, overThreshold, centralityIndex); } SocNetV-3.7/src/forms/dialogfilternodesbycentrality.h000066400000000000000000000026451524402210300231220ustar00rootroot00000000000000/** * @file dialogfilternodesbycentrality.h * @brief Defines the DialogFilterNodesByCentrality class for filtering nodes based on some attribute. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGFILTERNODESBYCENTRALITY_H #define DIALOGFILTERNODESBYCENTRALITY_H #include "ui_dialogfilternodesbycentrality.h" #include #include #include "global.h" // for IndexType SOCNETV_USE_NAMESPACE class DialogFilterNodesByCentrality : public QDialog { Q_OBJECT public: explicit DialogFilterNodesByCentrality(const QStringList &indexNames, const QVector &computedMask, QWidget *parent = nullptr); public slots: void getUserChoices(); signals: void userChoices(const float threshold, const bool overThreshold, const IndexType centralityIndex); private: Ui::DialogFilterNodesByCentrality ui; }; #endif // DIALOGFILTERNODESBYCENTRALITY_HSocNetV-3.7/src/forms/dialogfilternodesbycentrality.ui000066400000000000000000000156631524402210300233140ustar00rootroot00000000000000 DialogFilterNodesByCentrality 0 0 695 462 600 350 Filter nodes 6 QLayout::SizeConstraint::SetDefaultConstraint 0 0 Raw Score Threshold: Liberation Sans Qt::Orientation::Horizontal 40 25 90 0 <html><head/><body><p>Enter the score threshold used for hiding nodes.<br/>Use the options below to hide nodes with scores <span style=" font-weight:700;">β‰₯</span> or <span style=" font-weight:700;">≀</span> the threshold.</p></body></html> Enter the threshold to use while filtering. Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 3 -100.000000000000000 10000.000000000000000 Index: 0 0 130 0 300 16777215 Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok 10 This does not delete nodes; it only hides them in the current view. To restore hidden nodes, use Edit β†’ Filter Nodes β†’ Restore All Nodes. If you save the network while nodes are hidden, only the visible nodes and their edges will be saved. true Select behaviour: Hide nodes with scores β‰₯ threshold Hide nodes with scores ≀ threshold <html><head/><body><p>Hide nodes based on their raw (non-standardized) centrality or prestige score.</p><p>Choose an index and a score threshold, then choose whether to hide nodes with scores β‰₯ the threshold or ≀ the threshold.</p><p><span style=" font-style:italic;">Note: Indices must be computed before they can be used for filtering. The threshold applies to the raw score, not the standardized (%) value. Edges connected to hidden nodes will also be hidden.</span></p></body></html> Qt::TextFormat::RichText true buttonBox accepted() DialogFilterNodesByCentrality accept() 248 254 157 274 buttonBox rejected() DialogFilterNodesByCentrality reject() 316 260 286 274 SocNetV-3.7/src/forms/dialogimportattributes.cpp000066400000000000000000000222311524402210300221170ustar00rootroot00000000000000/** * @file dialogimportattributes.cpp * @brief Implements DialogImportAttributes (#227). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #include "dialogimportattributes.h" #include "../graph/io/table_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include static constexpr int PREVIEW_ROWS = 8; DialogImportAttributes::DialogImportAttributes(Scope scope, bool isCSV, QWidget *parent) : QDialog(parent), m_scope(scope), m_isCSV(isCSV) { const QString scopeStr = (scope == Scope::Nodes) ? tr("Node") : tr("Edge"); const QString formatStr = isCSV ? tr("CSV") : tr("JSON"); setWindowTitle(tr("Import %1 Attributes from %2").arg(scopeStr, formatStr)); setMinimumWidth(560); // ----------------------------------------------------------------------- // File selection row // ----------------------------------------------------------------------- m_fileLabel = new QLabel(tr("No file selected"), this); m_fileLabel->setWordWrap(false); m_fileLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); QPushButton *browseBtn = new QPushButton(tr("Browse…"), this); browseBtn->setToolTip(tr("Choose a %1 file to import").arg(formatStr)); QHBoxLayout *fileRow = new QHBoxLayout; fileRow->addWidget(m_fileLabel, 1); fileRow->addWidget(browseBtn); QGroupBox *fileGroup = new QGroupBox(tr("File"), this); fileGroup->setLayout(fileRow); // ----------------------------------------------------------------------- // Preview table (read-only, up to PREVIEW_ROWS rows) // ----------------------------------------------------------------------- m_previewTable = new QTableWidget(0, 0, this); m_previewTable->setEditTriggers(QAbstractItemView::NoEditTriggers); m_previewTable->setSelectionMode(QAbstractItemView::NoSelection); m_previewTable->setAlternatingRowColors(true); m_previewTable->setMinimumHeight(160); m_previewTable->horizontalHeader()->setStretchLastSection(true); m_previewTable->verticalHeader()->hide(); QGroupBox *previewGroup = new QGroupBox(tr("Preview (first %1 rows)").arg(PREVIEW_ROWS), this); QVBoxLayout *previewLayout = new QVBoxLayout(previewGroup); previewLayout->addWidget(m_previewTable); // ----------------------------------------------------------------------- // Column mapping β€” Nodes or Edges // ----------------------------------------------------------------------- QGroupBox *mappingGroup = new QGroupBox(tr("Column mapping"), this); QFormLayout *mappingForm = new QFormLayout(mappingGroup); if (scope == Scope::Nodes) { m_idCombo = new QComboBox(this); m_idCombo->setToolTip(tr("Column used to identify each node")); m_byNumberRadio = new QRadioButton(tr("Node number"), this); m_byLabelRadio = new QRadioButton(tr("Node label"), this); m_byNumberRadio->setChecked(true); QHBoxLayout *matchRow = new QHBoxLayout; matchRow->addWidget(m_byNumberRadio); matchRow->addWidget(m_byLabelRadio); matchRow->addStretch(); mappingForm->addRow(tr("ID column:"), m_idCombo); mappingForm->addRow(tr("Match by:"), matchRow); QLabel *hint = new QLabel( tr("All other columns will be imported as node attributes."), this); hint->setWordWrap(true); mappingForm->addRow(hint); } else { m_srcCombo = new QComboBox(this); m_srcCombo->setToolTip(tr("Column containing the source node number")); m_tgtCombo = new QComboBox(this); m_tgtCombo->setToolTip(tr("Column containing the target node number")); mappingForm->addRow(tr("Source column:"), m_srcCombo); mappingForm->addRow(tr("Target column:"), m_tgtCombo); QLabel *hint = new QLabel( tr("All other columns will be imported as edge attributes."), this); hint->setWordWrap(true); mappingForm->addRow(hint); } // ----------------------------------------------------------------------- // Button box β€” Import disabled until a valid file is loaded // ----------------------------------------------------------------------- m_buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); m_buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Import")); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); // ----------------------------------------------------------------------- // Layout // ----------------------------------------------------------------------- QVBoxLayout *main = new QVBoxLayout(this); main->addWidget(fileGroup); main->addWidget(previewGroup, 1); main->addWidget(mappingGroup); main->addWidget(m_buttonBox); // ----------------------------------------------------------------------- // Connections // ----------------------------------------------------------------------- connect(browseBtn, &QPushButton::clicked, this, &DialogImportAttributes::onBrowse); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &DialogImportAttributes::onAccepted); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } // --------------------------------------------------------------------------- // Slots // --------------------------------------------------------------------------- void DialogImportAttributes::onBrowse() { const QString filter = m_isCSV ? tr("CSV files (*.csv *.txt);;All files (*)") : tr("JSON files (*.json);;All files (*)"); const QString path = QFileDialog::getOpenFileName( this, tr("Open %1 file").arg(m_isCSV ? tr("CSV") : tr("JSON")), QString(), filter); if (path.isEmpty()) return; loadFile(path); } void DialogImportAttributes::onAccepted() { accept(); } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- void DialogImportAttributes::loadFile(const QString &path) { m_table = m_isCSV ? TableImport::fromCSV(path) : TableImport::fromJSON(path); if (!m_table.ok) { QMessageBox::warning(this, tr("Import error"), m_table.errorString); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); return; } // Show truncated path in the label const QFileInfo fi(path); m_fileLabel->setText(fi.fileName()); m_fileLabel->setToolTip(path); populatePreview(); populateColumnCombos(); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } void DialogImportAttributes::populatePreview() { const int colCount = m_table.headers.size(); const int rowCount = qMin(m_table.rows.size(), PREVIEW_ROWS); m_previewTable->setColumnCount(colCount); m_previewTable->setRowCount(rowCount); m_previewTable->setHorizontalHeaderLabels(m_table.headers); for (int r = 0; r < rowCount; ++r) { const QStringList &row = m_table.rows.at(r); for (int c = 0; c < colCount; ++c) { const QString val = (c < row.size()) ? row.at(c) : QString(); m_previewTable->setItem(r, c, new QTableWidgetItem(val)); } } m_previewTable->resizeColumnsToContents(); } void DialogImportAttributes::populateColumnCombos() { if (m_scope == Scope::Nodes) { m_idCombo->clear(); m_idCombo->addItems(m_table.headers); } else { m_srcCombo->clear(); m_srcCombo->addItems(m_table.headers); m_tgtCombo->clear(); m_tgtCombo->addItems(m_table.headers); // Auto-select sensible defaults if column names look right const QStringList &h = m_table.headers; for (int i = 0; i < h.size(); ++i) { const QString lower = h.at(i).toLower(); if (lower == QLatin1String("source") || lower == QLatin1String("src")) m_srcCombo->setCurrentIndex(i); if (lower == QLatin1String("target") || lower == QLatin1String("tgt") || lower == QLatin1String("dest")) m_tgtCombo->setCurrentIndex(i); } } } // --------------------------------------------------------------------------- // Result accessors // --------------------------------------------------------------------------- int DialogImportAttributes::idColumn() const { return m_idCombo ? m_idCombo->currentIndex() : 0; } bool DialogImportAttributes::matchByLabel() const { return m_byLabelRadio && m_byLabelRadio->isChecked(); } int DialogImportAttributes::srcColumn() const { return m_srcCombo ? m_srcCombo->currentIndex() : 0; } int DialogImportAttributes::tgtColumn() const { return m_tgtCombo ? m_tgtCombo->currentIndex() : 1; } SocNetV-3.7/src/forms/dialogimportattributes.h000066400000000000000000000054501524402210300215700ustar00rootroot00000000000000/** * @file dialogimportattributes.h * @brief Declares DialogImportAttributes β€” column-mapping dialog for CSV/JSON * attribute import (#227). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #pragma once #include #include "../graph/io/table_import.h" class QComboBox; class QDialogButtonBox; class QLabel; class QRadioButton; class QTableWidget; /** * @brief Modal dialog that lets the user pick a CSV or JSON file and map its * columns to graph node/edge identifiers before importing attributes. * * Usage: * @code * DialogImportAttributes dlg(DialogImportAttributes::Scope::Nodes, * true, // isCSV * parent); * if (dlg.exec() == QDialog::Accepted) { * graph->vertexAttributesImport(dlg.parsedTable().headers, * dlg.parsedTable().rows, * dlg.idColumn(), * dlg.matchByLabel()); * } * @endcode */ class DialogImportAttributes : public QDialog { Q_OBJECT public: enum class Scope { Nodes, Edges }; explicit DialogImportAttributes(Scope scope, bool isCSV, QWidget *parent = nullptr); /** Parsed file contents β€” valid only after the dialog is accepted. */ const TableImport::ParsedTable &parsedTable() const { return m_table; } /** * @name Nodes mapping results * Valid when scope == Nodes and the dialog was accepted. */ ///@{ int idColumn() const; ///< Index of the node-ID column in parsedTable().headers bool matchByLabel() const; ///< True β†’ match by label; false β†’ match by node number ///@} /** * @name Edges mapping results * Valid when scope == Edges and the dialog was accepted. */ ///@{ int srcColumn() const; ///< Index of the source-node column int tgtColumn() const; ///< Index of the target-node column ///@} private slots: void onBrowse(); void onAccepted(); private: void loadFile(const QString &path); void populatePreview(); void populateColumnCombos(); const Scope m_scope; const bool m_isCSV; TableImport::ParsedTable m_table; // File selection QLabel *m_fileLabel; // Preview QTableWidget *m_previewTable; // Nodes mapping QComboBox *m_idCombo = nullptr; QRadioButton *m_byNumberRadio = nullptr; QRadioButton *m_byLabelRadio = nullptr; // Edges mapping QComboBox *m_srcCombo = nullptr; QComboBox *m_tgtCombo = nullptr; QDialogButtonBox *m_buttonBox; }; SocNetV-3.7/src/forms/dialognodeedit.cpp000077500000000000000000000400301524402210300202710ustar00rootroot00000000000000/** * @file dialognodeedit.cpp * @brief Implements the DialogNodeEdit class for editing node properties in the network graph visualization. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialognodeedit.h" #include "ui_dialognodeedit.h" #include "global.h" #include #include #include #include "forms_logging.h" #include #include #include #include #include #include #include #include #include SOCNETV_USE_NAMESPACE DialogNodeEdit::DialogNodeEdit(QWidget *parent, const QStringList &nodeShapeList, const QStringList &iconPathList, const QString &label, const int &size, const QColor &color, const QString &shape, const QString &path, const QHash &customAttributes) : QDialog(parent), ui(new Ui::DialogNodeEdit), m_shapeList(nodeShapeList), m_iconList(iconPathList), nodeLabel(label), nodeSize(size), nodeColor(color), nodeShape(shape), iconPath(path), m_customAttributes(customAttributes) { ui->setupUi(this); qCDebug(lcForms) << "opening DialogNodeEdit." << "label" << nodeLabel << "size" << nodeSize << "color" << nodeColor << "shape" << shape << "iconPath" << iconPath << "customAttributes" << customAttributes; // Set the builtin node properties ui->labelEdit->setText(nodeLabel); ui->sizeSpin->setValue(nodeSize); ui->nodeShapeComboBox->addItems(m_shapeList); for (int i = 0; i < m_shapeList.size(); ++i) { ui->nodeShapeComboBox->setItemIcon(i, QIcon(m_iconList[i])); } ui->nodeIconSelectButton->setEnabled(false); ui->nodeIconSelectEdit->setEnabled(false); ui->removePropertyBtn->setEnabled(false); int index = -1; if ((index = m_shapeList.indexOf(nodeShape)) != -1) { ui->nodeShapeComboBox->setCurrentIndex(index); if (index == NodeShape::Custom) { ui->nodeShapeComboBox->setCurrentIndex(NodeShape::Custom); ui->nodeIconSelectButton->setEnabled(true); ui->nodeIconSelectEdit->setEnabled(true); ui->nodeIconSelectEdit->setText(iconPath); if (!iconPath.isEmpty()) { ui->nodeShapeComboBox->setItemIcon( NodeShape::Custom, QIcon(iconPath)); } else { QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui->nodeIconSelectButton->setGraphicsEffect(effect); ui->nodeIconSelectEdit->setGraphicsEffect(effect); (ui->buttonBox)->button(QDialogButtonBox::Cancel)->setDefault(true); (ui->buttonBox)->button(QDialogButtonBox::Ok)->setEnabled(false); } } } else { // default -- should never happen... ui->nodeShapeComboBox->setCurrentIndex(NodeShape::Circle); } pixmap = QPixmap(60, 20); pixmap.fill(nodeColor); ui->colorButton->setIcon(QIcon(pixmap)); ui->customAttributesTable->setRowCount(0); for (auto it = customAttributes.begin(); it != customAttributes.end(); ++it) { int row = ui->customAttributesTable->rowCount(); ui->customAttributesTable->insertRow(row); ui->customAttributesTable->setItem(row, 0, new QTableWidgetItem(it.key())); ui->customAttributesTable->setItem(row, 1, new QTableWidgetItem(it.value())); } // Connect signals and slots connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(getUserChoices())); (ui->buttonBox)->button(QDialogButtonBox::Ok)->setDefault(true); (ui->labelEdit)->setFocus(); connect(ui->labelEdit, &QLineEdit::editingFinished, this, &DialogNodeEdit::checkErrors); connect(ui->colorButton, &QToolButton::clicked, this, &DialogNodeEdit::selectColor); connect(ui->nodeShapeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &DialogNodeEdit::getNodeShape); connect(ui->nodeIconSelectButton, &QToolButton::clicked, this, &DialogNodeEdit::getNodeIconFile); connect(ui->addPropertyBtn, &QPushButton::clicked, this, &DialogNodeEdit::on_addPropertyButton_clicked); connect(ui->customAttributesTable, &QTableWidget::itemSelectionChanged, [this]() { ui->removePropertyBtn->setEnabled( !ui->customAttributesTable->selectedItems().isEmpty()); }); connect(ui->removePropertyBtn, &QPushButton::clicked, this, &DialogNodeEdit::on_removePropertyButton_clicked); } DialogNodeEdit::~DialogNodeEdit() { delete ui; } /** * @brief Sets the node shape based on the provided index and updates the UI accordingly. * * This function takes an integer index representing a node shape and sets the corresponding * shape name to the `nodeShape` member variable. It also updates the UI elements based on * whether the selected shape is custom or not. * * @param nodeShapeIndex The index representing the node shape. It should correspond to one of the * values defined in the NodeShape enumeration. * * The function performs the following actions: * - Maps the nodeShapeIndex to a string representation of the shape. * - If the node shape is custom, enables the text edit and file button, sets the icon path, * and updates the UI elements with the custom icon. * - If the node shape is not custom, disables the text edit and file button, clears the icon path, * and resets the UI elements. */ void DialogNodeEdit::getNodeShape(const int &nodeShapeIndex) { switch (nodeShapeIndex) { case NodeShape::Box: nodeShape = "box"; break; case NodeShape::Circle: nodeShape = "circle"; break; case NodeShape::Diamond: nodeShape = "diamond"; break; case NodeShape::Ellipse: nodeShape = "ellipse"; break; case NodeShape::Triangle: nodeShape = "triangle"; break; case NodeShape::Star: nodeShape = "star"; break; case NodeShape::Person: nodeShape = "person"; break; case NodeShape::PersonB: nodeShape = "person-b"; break; case NodeShape::Bugs: nodeShape = "bugs"; break; case NodeShape::Heart: nodeShape = "heart"; break; case NodeShape::Dice: nodeShape = "dice"; break; case NodeShape::Custom: nodeShape = "custom"; break; default: break; } qCDebug(lcForms) << "DialogNodeEdit::getNodeShape() - new node shape " << nodeShape; if (nodeShapeIndex == NodeShape::Custom) { // enable textedit and file button and raise file dialog ui->nodeIconSelectButton->setEnabled(true); ui->nodeIconSelectEdit->setEnabled(true); ui->nodeIconSelectEdit->setText(iconPath); if (!iconPath.isEmpty()) { ui->nodeShapeComboBox->setItemIcon(NodeShape::Custom, QIcon(iconPath)); ui->nodeIconSelectButton->setGraphicsEffect(0); ui->nodeIconSelectEdit->setGraphicsEffect(0); } else { QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui->nodeIconSelectButton->setGraphicsEffect(effect); ui->nodeIconSelectEdit->setGraphicsEffect(effect); (ui->buttonBox)->button(QDialogButtonBox::Cancel)->setDefault(true); (ui->buttonBox)->button(QDialogButtonBox::Ok)->setEnabled(false); } } else { ui->nodeIconSelectButton->setEnabled(false); ui->nodeIconSelectEdit->setEnabled(false); ui->nodeIconSelectEdit->setText(""); iconPath = QString(); ui->nodeIconSelectButton->setGraphicsEffect(0); ui->nodeIconSelectEdit->setGraphicsEffect(0); (ui->buttonBox)->button(QDialogButtonBox::Ok)->setDefault(true); (ui->buttonBox)->button(QDialogButtonBox::Ok)->setEnabled(true); } } void DialogNodeEdit::getNodeIconFile() { QString m_nodeIconFile = QFileDialog::getOpenFileName(this, tr("Select a new icon"), ui->nodeIconSelectEdit->text(), tr("Images (*.png *.jpg *.jpeg *.svg);;All (*.*)")); if (!m_nodeIconFile.isEmpty()) { qCDebug(lcForms) << m_nodeIconFile; ui->nodeIconSelectEdit->setText(m_nodeIconFile); ui->nodeIconSelectButton->setGraphicsEffect(0); ui->nodeIconSelectEdit->setGraphicsEffect(0); ui->nodeShapeComboBox->setItemIcon(NodeShape::Custom, QIcon(m_nodeIconFile)); (ui->buttonBox)->button(QDialogButtonBox::Ok)->setEnabled(true); } else { // user pressed Cancel ? // stop if (ui->nodeIconSelectEdit->text().isEmpty()) { (ui->buttonBox)->button(QDialogButtonBox::Cancel)->setDefault(true); (ui->buttonBox)->button(QDialogButtonBox::Ok)->setEnabled(false); } } } /** * @brief DialogNodeEdit::getUserChoices */ void DialogNodeEdit::getUserChoices() { qCDebug(lcForms) << " DialogNodeEdit::getUserChoices()"; nodeLabel = ui->labelEdit->text(); nodeSize = ui->sizeSpin->value(); nodeShape = "circle"; int nodeShapeIndex = ui->nodeShapeComboBox->currentIndex(); switch (nodeShapeIndex) { case NodeShape::Box: nodeShape = "box"; break; case NodeShape::Circle: nodeShape = "circle"; break; case NodeShape::Diamond: nodeShape = "diamond"; break; case NodeShape::Ellipse: nodeShape = "ellipse"; break; case NodeShape::Triangle: nodeShape = "triangle"; break; case NodeShape::Star: nodeShape = "star"; break; case NodeShape::Person: nodeShape = "person"; iconPath = m_iconList[nodeShapeIndex]; break; case NodeShape::PersonB: nodeShape = "person-b"; iconPath = m_iconList[nodeShapeIndex]; break; case NodeShape::Bugs: nodeShape = "bugs"; iconPath = m_iconList[nodeShapeIndex]; break; case NodeShape::Heart: nodeShape = "heart"; iconPath = m_iconList[nodeShapeIndex]; break; case NodeShape::Dice: nodeShape = "dice"; iconPath = m_iconList[nodeShapeIndex]; break; case NodeShape::Custom: nodeShape = "custom"; iconPath = ui->nodeIconSelectEdit->text(); break; default: break; } // Rebuild custom attributes entirely from table contents m_customAttributes.clear(); for (int i = 0; i < ui->customAttributesTable->rowCount(); ++i) { QTableWidgetItem *keyItem = ui->customAttributesTable->item(i, 0); QTableWidgetItem *valueItem = ui->customAttributesTable->item(i, 1); if (keyItem && valueItem && !keyItem->text().isEmpty()) m_customAttributes.insert(keyItem->text(), valueItem->text()); } emit userChoices(nodeLabel, nodeSize, nodeColor, nodeShape, iconPath, m_customAttributes); } void DialogNodeEdit::selectColor() { qCDebug(lcForms) << " DialogNodeEdit::selectColor()"; nodeColor = QColorDialog::getColor( Qt::red, this, tr("Select node color")); if (nodeColor.isValid()) { qCDebug(lcForms) << " color selected " << nodeColor.name(); pixmap.fill(nodeColor); ui->colorButton->setIcon(QIcon(pixmap)); } else { // user pressed Cancel qCDebug(lcForms) << " Aborted node color"; } } /** * @brief Slot function that is called when the "Add Property" button is clicked. * * This function retrieves the key and value from the respective line edits, * checks if both are non-empty, and if so, inserts the key-value pair into * the custom attributes map and updates the custom attributes table with the * new entry. After adding the new property, it clears the input fields. */ void DialogNodeEdit::on_addPropertyButton_clicked() { QString key = ui->keyLineEdit->text().trimmed(); QString value = ui->valueLineEdit->text().trimmed(); // Highlight empty fields and return QGraphicsColorizeEffect *effect = nullptr; if (key.isEmpty()) { effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui->keyLineEdit->setGraphicsEffect(effect); } else { ui->keyLineEdit->setGraphicsEffect(nullptr); } if (value.isEmpty()) { effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui->valueLineEdit->setGraphicsEffect(effect); } else { ui->valueLineEdit->setGraphicsEffect(nullptr); } if (key.isEmpty() || value.isEmpty()) return; m_customAttributes.insert(key, value); int row = ui->customAttributesTable->rowCount(); ui->customAttributesTable->insertRow(row); ui->customAttributesTable->setItem(row, 0, new QTableWidgetItem(key)); ui->customAttributesTable->setItem(row, 1, new QTableWidgetItem(value)); ui->keyLineEdit->clear(); ui->valueLineEdit->clear(); } /** * @brief Slot function called when the remove property button is clicked. * * This function removes the currently selected property from the custom attributes table * and the internal custom attributes map. If a row is selected in the table, it retrieves * the key from the first column of the selected row, removes the corresponding entry from * the custom attributes map, and then removes the row from the table. */ void DialogNodeEdit::on_removePropertyButton_clicked() { int row = ui->customAttributesTable->currentRow(); if (row != -1) { QString key = ui->customAttributesTable->item(row, 0)->text(); m_customAttributes.remove(key); ui->customAttributesTable->removeRow(row); } ui->removePropertyBtn->setEnabled(false); } /** * @brief DialogNodeEdit::checkErrors */ void DialogNodeEdit::checkErrors() { qCDebug(lcForms) << " DialogNodeEdit::checkErrors()"; QString userLabel = ui->labelEdit->text(); userLabel = userLabel.simplified(); ui->labelEdit->setText(userLabel); if (ui->labelEdit->text().isEmpty()) { qCDebug(lcForms) << "empty label!"; QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui->labelEdit->setGraphicsEffect(effect); //(ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); } else { ui->labelEdit->setGraphicsEffect(0); (ui->buttonBox)->button(QDialogButtonBox::Ok)->setEnabled(true); } // getUserChoices(); } SocNetV-3.7/src/forms/dialognodeedit.h000077500000000000000000000045011524402210300177410ustar00rootroot00000000000000/** * @file dialognodeedit.h * @brief Declares the DialogNodeEdit class for editing node properties in the network graph visualization. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGNODEEDIT_H #define DIALOGNODEEDIT_H #include #include namespace Ui { class DialogNodeEdit; } class DialogNodeEdit : public QDialog { Q_OBJECT public: explicit DialogNodeEdit(QWidget *parent = Q_NULLPTR, const QStringList &nodeShapeList = QStringList(), const QStringList &iconPathList = QStringList(), const QString &label = QString(), const int &size = 0, const QColor &color = QColor(), const QString &shape = QString(), const QString &path = QString(), const QHash &customAttributes = QHash()); ~DialogNodeEdit(); private slots: void checkErrors(); void getNodeShape(const int &nodeShapeIndex); void getNodeIconFile(); void getUserChoices(); void selectColor(); void on_addPropertyButton_clicked(); void on_removePropertyButton_clicked(); signals: void userChoices(const QString &label, const int &size, const QColor &color, const QString &shape, const QString &iconPath = QString(), const QHash &customAttributes = QHash()); void nodeEditDialogError(QString); private: Ui::DialogNodeEdit *ui; QStringList m_shapeList; QStringList m_iconList; QString nodeLabel; int nodeSize; QColor nodeColor; QString nodeShape; QString iconPath; QString nodeValue; QPixmap pixmap; QHash m_customAttributes; }; #endif SocNetV-3.7/src/forms/dialognodeedit.ui000066400000000000000000000462201524402210300201300ustar00rootroot00000000000000 DialogNodeEdit 0 0 600 551 600 550 Node Properties Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok Qt::Orientation::Vertical 20 40 <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click the button on the right to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> Custom Icon Liberation Sans 13 Qt::Orientation::Horizontal 30 28 200 0 <html><head/><body><p><span style=" font-weight:600;">Custom Icon </span></p><p>This box only shows the path of the selected custom icon file for all network nodes. If it is empty, it means you have not selected any image yet.</p><p>Click the button on the right to select a new image to be used as custom icon in all the nodes of the network.</p><p>Valid icons are images: JPG, PNG, JPEG, SVG etc.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>This is the path of the default background image. </p><p>Click the button on the right to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> 0 0 60 0 <html><head/><body><p><span style=" font-weight:600;">Custom Icon </span></p><p>Click this button to select a new custom icon for all nodes of the network. </p><p>Valid icons are images: JPG, PNG, JPEG, SVG etc.</p><p>If you don't select an image file, you must select one of the default shapes from the Node Shape menu above. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click this button to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> ... Custom attributes 5 5 5 5 0 0 140 26 <html><head/><body><p>Click to remove the selected key-value attribute (you should have already clicked one!).</p></body></html> Remove selected Qt::Orientation::Horizontal 40 20 0 0 0 25 <html><head/><body><p>Enter the key of the attribute, i.e. age or birthdate.</p></body></html> Enter key 0 0 New Attribute: 0 0 0 25 <html><head/><body><p>Enter the value of this attribute. </p><p>For example, if the key is 'age', then the value might be a number like 19. </p><p>Note: The value supports ISOΒ 8601 dates, i.e. 2011-10-05T14:48:00.000Z</p><p><br/></p></body></html> Enter value false Add 0 72 <html><head/><body><p>Shows the current custom attributes (metadata) of the node. Custom attributes are key-value pairs. </p><p>For example. you could add demographics, work years, age, time events, etc. </p></body></html> Qt::PenStyle::DotLine true false 40 140 true true true false Key Value Node shape Liberation Sans 13 Qt::Orientation::Horizontal QSizePolicy::Policy::MinimumExpanding 40 20 200 0 300 16777215 <html><head/><body><p>Select a node shape. </p><p>If you select &quot;Icon&quot; then you must click on the custom icon button (below) to select the desired icon file from your filesystem. </p></body></html> Qt::Orientation::Vertical 20 40 Node color (click the button to select) Liberation Sans 13 Qt::Orientation::Horizontal 40 20 60 25 ... 60 20 Node size Liberation Sans 13 Qt::Orientation::Horizontal 40 20 60 0 <html><head/><body><p>Set the size of the node. </p><p><br/></p><p>Default node size: 8</p></body></html> 8 Node label Liberation Sans 13 Qt::Orientation::Horizontal 20 20 200 0 <html><head/><body><p>Enter a node label. </p><p>If multiple nodes are selected, the label you define here will be set to all of them, along with a numerical suffix, i.e. if you enter &quot;Jim&quot;, then the selected actors will be labeled &quot;Jim1&quot;, &quot;Jim2&quot;, &quot;Jim3&quot; and so on. </p></body></html> Qt::Orientation::Vertical 20 40 buttonBox accepted() DialogNodeEdit accept() 233 316 157 274 buttonBox rejected() DialogNodeEdit reject() 301 322 286 274 SocNetV-3.7/src/forms/dialognodefind.cpp000066400000000000000000000164431524402210300202740ustar00rootroot00000000000000/** * @file dialognodefind.cpp * @brief Implements the DialogNodeFind class for searching and selecting nodes within the network graph in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialognodefind.h" #include "ui_dialognodefind.h" #include #include "forms_logging.h" #include #include DialogNodeFind::DialogNodeFind(QWidget *parent, QStringList indexList) : QDialog(parent), ui(new Ui::DialogNodeFind) { ui->setupUi(this); ui->labelsRadioBtn->setAutoExclusive(true); ui->numbersRadioBtn->setAutoExclusive(true); ui->indexRadioBtn->setAutoExclusive(true); ui->numbersRadioBtn->setChecked(true); ui->indexCombo->insertItems(0, indexList); ui->indexLabel->setEnabled(false); ui->indexCombo->setEnabled(false); connect ( ui->labelsRadioBtn, SIGNAL(clicked(bool)), this, SLOT( checkErrors() ) ); connect ( ui->numbersRadioBtn, SIGNAL(clicked(bool)), this, SLOT( checkErrors() ) ); connect ( ui->indexRadioBtn, SIGNAL(clicked(bool)), this, SLOT( checkErrors() ) ); connect (ui->indexCombo, &QComboBox::currentTextChanged, this, &DialogNodeFind::getIndex); // connect ( ui->indexCombo, SIGNAL(currentTextChanged(QString)), this, SLOT( checkErrors(QString) ) ); connect ( ui->plainTextEdit,SIGNAL(textChanged()), this, SLOT(checkErrors()) ); connect ( ui->buttonBox,SIGNAL(accepted()), this, SLOT(getUserChoices()) ); ui->plainTextEdit->setFocus(); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setDefault(true); } DialogNodeFind::~DialogNodeFind() { tempListA.clear(); tempListB.clear(); delete ui; } void DialogNodeFind::setError(const bool &toggle) { if ( toggle ) { QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui->plainTextEdit->setGraphicsEffect(effect); ui->buttonBox->button (QDialogButtonBox::Ok)->setEnabled(false); } else { ui->plainTextEdit->setGraphicsEffect(0); ui->buttonBox->button (QDialogButtonBox::Ok)->setEnabled(true); } } /** * @brief Gets the selected index * @param indexStr */ void DialogNodeFind::getIndex(const QString &indexStr) { selectedIndex = ui->indexCombo->currentText(); qCDebug(lcForms) << "DialogNodeFind::getIndex() str"<plainTextEdit->toPlainText(); qCDebug(lcForms)<< "DialogNodeFind::checkErrors() - raw text entered:" << textEntered; if ( ui->numbersRadioBtn->isChecked() ) { ui->textEditLabel->setText("Search for these numbers (enter line by line or csv):"); searchType = "numbers"; ui->indexLabel->setEnabled(false); ui->indexCombo->setEnabled(false); } else if ( ui->labelsRadioBtn->isChecked() ) { ui->textEditLabel->setText("Search for these labels (enter line by line or csv):"); searchType = "labels"; ui->indexLabel->setEnabled(false); ui->indexCombo->setEnabled(false); } else if ( ui->indexRadioBtn->isChecked() ) { ui->textEditLabel->setText("Search for nodes with this index score (i.e. > 0.5)"); ui->indexLabel->setEnabled(true); ui->indexCombo->setEnabled(true); searchType = "score"; } qCDebug(lcForms)<< "DialogNodeFind::checkErrors() - search type:" << searchType; list.clear(); tempListA.clear(); tempListB.clear(); if ( textEntered.isEmpty() ) { setError(true); } else { setError(false); } if (! ui->indexRadioBtn->isChecked()) { // user wants to search by numbers or labels // user has to enter a CSV or line separated list of values if (textEntered.contains("\n") && textEntered.contains(",")) { // error you cannot enter both? // return; } // check if user entered multiple lines tempListA = textEntered.split("\n", Qt::SkipEmptyParts); for (int i = 0; i < tempListA.size(); ++i) { // take every linefeed separated value str = tempListA.at(i).toLocal8Bit().constData(); qCDebug(lcForms)<< "DialogNodeFind::checkErrors() - line:" << i << "str:" << str; // check if user has entered comma tempListB = str.split(",", Qt::SkipEmptyParts); for (int j = 0; j < tempListB.size(); ++j) { // take every comma separated value str = tempListB.at(j).toLocal8Bit().constData(); qCDebug(lcForms)<< "DialogNodeFind::checkErrors() - line:" << i << "element at pos:" << j << "is:" << str; if (ui->numbersRadioBtn->isChecked()) { // user wants to search by numbers // check if str contains a dash if (str.contains ("-")) { //str.split("-") } else { if (str.contains(QRegularExpression("\\D+"))) { qCDebug(lcForms)<< "DialogNodeFind::checkErrors() - error! not number" << str; setError(true); } else { qCDebug(lcForms)<< "DialogNodeFind::checkErrors() - adding number" << str; list << str; } } } else { // user wants to search by labels qCDebug(lcForms)<< "DialogNodeFind::checkErrors() - adding label" << str; list << str; } } } } else { // user wants to search nodes by their index score // user has to enter > or < and a threshold // and select the desired index. selectedIndex = ui->indexCombo->currentText(); // check if user entered multiple lines tempListA = textEntered.split("\n", Qt::SkipEmptyParts); for (int i = 0; i < tempListA.size(); ++i) { // take every linefeed separated value str = tempListA.at(i).toLocal8Bit().constData(); if (str.contains (">") || str.contains ("<") || str.contains ("=")) { list << str; } else { qCDebug(lcForms)<< "DialogNodeFind::checkErrors() - error! search by index without > or <" ; setError(true); } } } } /** * @brief Gathers user input and emits userChoices signal */ void DialogNodeFind::getUserChoices() { qCDebug(lcForms)<< "DialogNodeFind::getUserChoices()" << list; qCDebug(lcForms)<< "DialogNodeFind::getUserChoices() type" << searchType; emit userChoices( list, searchType, selectedIndex ); } SocNetV-3.7/src/forms/dialognodefind.h000066400000000000000000000026571524402210300177430ustar00rootroot00000000000000/** * @file dialognodefind.h * @brief Declares the DialogNodeFind class used for locating and highlighting nodes by name or ID in the SocNetV network visualization. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGNODEFIND_H #define DIALOGNODEFIND_H #include namespace Ui { class DialogNodeFind; } class DialogNodeFind : public QDialog { Q_OBJECT public: explicit DialogNodeFind(QWidget *parent = Q_NULLPTR, QStringList indexList=QStringList()); ~DialogNodeFind(); public slots: void setError(const bool &toggle); void getIndex(const QString &indexStr); void checkErrors (); void getUserChoices (); signals: void userChoices( const QStringList &list, const QString &type, const QString &selectedIndex=QString()); private: Ui::DialogNodeFind *ui; QStringList list; QString searchType; QStringList tempListA; QStringList tempListB; QString str; QString selectedIndex; }; #endif // DIALOGNODEFIND_H SocNetV-3.7/src/forms/dialognodefind.ui000066400000000000000000000201571524402210300201240ustar00rootroot00000000000000 DialogNodeFind 0 0 600 410 600 410 Find nodes (and select them) 0 100 16777215 300 13 false <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Find and select nodes (by numbers, labels or index score)</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To find nodes by their number, enter numbers either comma-separated or line by line or array <br /><span style=" font-style:italic;">i.e. 1,2,3 or 1-10) </span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To find nodes by their label enter words either comma separated or line by line <br /><span style=" font-style:italic;">i.e. jim,julia</span> </p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To find and select nodes by their index score, enter the desired score in the form: <br /><span style=" font-style:italic;">&gt; threshold</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">or </p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-style:italic;">&lt; threshold</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">and select the index in the menu below</p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> By Number(s) By Label(s) By Index Score Qt::Orientation::Vertical 20 29 Search for these numbers (enter line by line or csv): Qt::TextFormat::RichText true 0 40 <html><head/><body><p>Select how to search nodes (by their number, by their label, or by index score) and enter below what you want to find. Matched nodes will be highlighted.</p></body></html> Qt::TextFormat::RichText true Qt::Orientation::Vertical 20 30 Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok false 50 0 Index false 150 0 Qt::Orientation::Vertical 20 25 buttonBox accepted() DialogNodeFind accept() 248 254 157 274 buttonBox rejected() DialogNodeFind reject() 316 260 286 274 SocNetV-3.7/src/forms/dialogpreviewfile.cpp000077500000000000000000000101701524402210300210210ustar00rootroot00000000000000/** * @file dialogpreviewfile.cpp * @brief Implements the DialogPreviewFile class which displays file contents with encoding options for preview before import in SocNetV. * @details Parts of this implementation are adapted from the Qt5 Codecs example. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include #include #include "dialogpreviewfile.h" #include "forms_logging.h" DialogPreviewFile::DialogPreviewFile(QWidget *parent) : QDialog(parent) { encodingComboBox = new QComboBox; encodingLabel = new QLabel(tr("&Encoding:")); encodingLabel->setBuddy(encodingComboBox); textEdit = new QTextEdit; textEdit->setToolTip( tr("

In this area you can preview your text file before actually loading it.

" "

SocNetV uses UTF-8 for saving and loading network files, by default.

" "

If your file is encoded in another encoding, " "select the correct encoding from the menu and " "see if strings appear correctly.

") ); textEdit->setLineWrapMode(QTextEdit::NoWrap); textEdit->setReadOnly(true); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(encodingComboBox, SIGNAL(activated(int)), this, SLOT(updateTextEdit())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); QGridLayout *mainLayout = new QGridLayout; mainLayout->addWidget(encodingLabel, 0, 0); mainLayout->addWidget(encodingComboBox, 0, 1); mainLayout->addWidget(textEdit, 1, 0, 1, 2); mainLayout->addWidget(buttonBox, 2, 0, 1, 2); setLayout(mainLayout); setWindowTitle(tr("Preview file & Choose Encoding")); resize(600, 400); } void DialogPreviewFile::setCodecList(const QList &list) { encodingComboBox->clear(); foreach (QTextCodec *codec, list) encodingComboBox->addItem(codec->name(), codec->mibEnum()); } void DialogPreviewFile::setEncodedData(const QByteArray &data, const QString &fileName, const int &fileFormat) { m_fileName = fileName; m_fileFormat = fileFormat; encodedData = data; updateTextEdit(); } void DialogPreviewFile::updateTextEdit() { int mib = encodingComboBox->itemData( encodingComboBox->currentIndex()).toInt(); QTextCodec *codec = QTextCodec::codecForMib(mib); qCDebug(lcForms) << "Selected codec name: " << codec->name(); QTextStream in(&encodedData); decodedStr = codec->toUnicode(encodedData); // // FOR FUTURE REFERENCE (IF QTextCodec Class GETS REMOVED FROM QT6 QT5 CORE COMPAT MODULE) // // Check whether QStringConverter supports user selected encoding // std::optional test_support = QStringConverter::encodingForName(codec->name()); // if ( test_support.has_value()) { // // Encoding supported // in.setEncoding(test_support.value()); // } // else { // // Encoding not supported. Retreat to UTF-9 // in.setAutoDetectUnicode(false); // } // Read the text stream // decodedStr = in.readAll(); // Update text in dialog textEdit->setPlainText(decodedStr); } void DialogPreviewFile::accept() { int mib = encodingComboBox->itemData( encodingComboBox->currentIndex()).toInt(); QTextCodec *codec = QTextCodec::codecForMib(mib); qCDebug(lcForms) << "User accepted. Returning codec name:" << codec->name(); emit loadNetworkFileWithCodec(m_fileName, codec->name(), m_fileFormat); QDialog::accept(); } SocNetV-3.7/src/forms/dialogpreviewfile.h000077500000000000000000000030451524402210300204710ustar00rootroot00000000000000/** * @file dialogpreviewfile.h * @brief Declares the DialogPreviewFile class used for displaying a preview of network files before importing them into SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGPREVIEWFILE_H #define DIALOGPREVIEWFILE_H #include #include class QComboBox; class QDialogButtonBox; class QLabel; class QTextCodec; class QTextEdit; class DialogPreviewFile : public QDialog { Q_OBJECT public: explicit DialogPreviewFile(QWidget *parent = Q_NULLPTR); void setCodecList(const QList &list); void setEncodedData(const QByteArray &data, const QString &fileName, const int &fileFormat); QString decodedString() const { return decodedStr; } signals: void loadNetworkFileWithCodec(const QString &fileName, const QString &codecName, const int &fileFormat); private slots: void updateTextEdit(); void accept(); private: QByteArray encodedData; QString decodedStr; QString m_fileName; int m_fileFormat; QComboBox *encodingComboBox; QLabel *encodingLabel; QTextEdit *textEdit; QDialogButtonBox *buttonBox; }; #endif SocNetV-3.7/src/forms/dialogquerybuilder.cpp000066400000000000000000000153741524402210300212240ustar00rootroot00000000000000/** * @file dialogquerybuilder.cpp * @brief Implements DialogQueryBuilder β€” multi-condition AND filter composer * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #include "dialogquerybuilder.h" #include #include #include #include #include #include #include #include #include #include #include DialogQueryBuilder::DialogQueryBuilder(const QStringList &nodeKeys, const QStringList &edgeKeys, QWidget *parent) : QDialog(parent) , m_nodeKeys(nodeKeys) , m_edgeKeys(edgeKeys) { setWindowTitle(tr("Query Builder")); setMinimumWidth(500); setMinimumHeight(380); QVBoxLayout *main = new QVBoxLayout(this); main->setContentsMargins(12, 12, 12, 12); main->setSpacing(10); // Description QLabel *descLabel = new QLabel( tr("Build a multi-condition filter for nodes or edges. " "All conditions are combined with AND logic β€” " "elements not matching every condition are hidden (reversible)."), this); descLabel->setWordWrap(true); main->addWidget(descLabel); // Scope group QGroupBox *scopeGroup = new QGroupBox(tr("Scope"), this); QHBoxLayout *scopeLayout = new QHBoxLayout(scopeGroup); m_nodesRadio = new QRadioButton(tr("Nodes"), scopeGroup); m_edgesRadio = new QRadioButton(tr("Edges"), scopeGroup); m_nodesRadio->setChecked(true); scopeLayout->addWidget(m_nodesRadio); scopeLayout->addWidget(m_edgesRadio); scopeLayout->addStretch(); main->addWidget(scopeGroup); // Conditions group QGroupBox *condGroup = new QGroupBox(tr("Conditions (ALL must match)"), this); QVBoxLayout *condOuter = new QVBoxLayout(condGroup); m_rowsContainer = new QWidget(condGroup); m_rowsLayout = new QVBoxLayout(m_rowsContainer); m_rowsLayout->setContentsMargins(0, 0, 0, 0); m_rowsLayout->setSpacing(4); m_scrollArea = new QScrollArea(condGroup); m_scrollArea->setWidget(m_rowsContainer); m_scrollArea->setWidgetResizable(true); m_scrollArea->setFrameShape(QFrame::NoFrame); m_scrollArea->setMinimumHeight(130); condOuter->addWidget(m_scrollArea); m_addRowBtn = new QPushButton(tr("+ Add condition"), condGroup); m_addRowBtn->setFlat(true); condOuter->addWidget(m_addRowBtn, 0, Qt::AlignLeft); main->addWidget(condGroup); // Buttons m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Apply | QDialogButtonBox::Cancel, this); main->addWidget(m_buttonBox); // Start with one empty row. addRow(); connect(m_nodesRadio, &QRadioButton::toggled, this, &DialogQueryBuilder::onScopeChanged); connect(m_edgesRadio, &QRadioButton::toggled, this, &DialogQueryBuilder::onScopeChanged); connect(m_addRowBtn, &QPushButton::clicked, this, &DialogQueryBuilder::onAddRow); connect(m_buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &DialogQueryBuilder::onApply); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } void DialogQueryBuilder::addRow() { ConditionRow cr; cr.widget = new QWidget(m_rowsContainer); QHBoxLayout *rowLay = new QHBoxLayout(cr.widget); rowLay->setContentsMargins(0, 0, 0, 0); rowLay->setSpacing(4); cr.keyCombo = new QComboBox(cr.widget); cr.keyCombo->setEditable(true); cr.keyCombo->setMinimumWidth(120); cr.keyCombo->addItems(m_nodesRadio->isChecked() ? m_nodeKeys : m_edgeKeys); cr.opCombo = new QComboBox(cr.widget); cr.opCombo->addItems({tr("="), tr("β‰ "), tr(">"), tr("<"), tr("β‰₯"), tr("≀"), tr("contains")}); cr.valueEdit = new QLineEdit(cr.widget); cr.valueEdit->setPlaceholderText(tr("value…")); cr.removeBtn = new QPushButton(tr("βˆ’"), cr.widget); cr.removeBtn->setMaximumWidth(28); cr.removeBtn->setFlat(true); cr.removeBtn->setToolTip(tr("Remove this condition")); rowLay->addWidget(cr.keyCombo, 2); rowLay->addWidget(cr.opCombo, 1); rowLay->addWidget(cr.valueEdit, 2); rowLay->addWidget(cr.removeBtn, 0); m_rowsLayout->addWidget(cr.widget); m_rows.append(cr); connect(cr.removeBtn, &QPushButton::clicked, this, [this, w = cr.widget]() { removeRow(w); }); updateRemoveButtons(); } void DialogQueryBuilder::removeRow(QWidget *rowWidget) { for (int i = 0; i < m_rows.size(); ++i) { if (m_rows[i].widget == rowWidget) { m_rowsLayout->removeWidget(rowWidget); rowWidget->deleteLater(); m_rows.removeAt(i); break; } } updateRemoveButtons(); } void DialogQueryBuilder::onScopeChanged() { repopulateKeys(); } void DialogQueryBuilder::repopulateKeys() { const QStringList keys = m_edgesRadio->isChecked() ? m_edgeKeys : m_nodeKeys; for (auto &cr : m_rows) { const QString current = cr.keyCombo->currentText(); cr.keyCombo->clear(); cr.keyCombo->addItems(keys); if (!current.isEmpty()) cr.keyCombo->setCurrentText(current); } } void DialogQueryBuilder::updateRemoveButtons() { const bool canRemove = m_rows.size() > 1; for (auto &cr : m_rows) cr.removeBtn->setEnabled(canRemove); } FilterCondition::Scope DialogQueryBuilder::currentScope() const { return m_edgesRadio->isChecked() ? FilterCondition::Scope::Edges : FilterCondition::Scope::Nodes; } void DialogQueryBuilder::onAddRow() { addRow(); } void DialogQueryBuilder::onApply() { static const FilterCondition::Op opMap[] = { FilterCondition::Op::Eq, FilterCondition::Op::Neq, FilterCondition::Op::Gt, FilterCondition::Op::Lt, FilterCondition::Op::Gte, FilterCondition::Op::Lte, FilterCondition::Op::Contains }; GraphQuery query; const FilterCondition::Scope scope = currentScope(); for (const auto &cr : std::as_const(m_rows)) { const QString key = cr.keyCombo->currentText().trimmed(); const QString value = cr.valueEdit->text().trimmed(); if (key.isEmpty()) continue; FilterCondition cond; cond.scope = scope; cond.key = key; cond.value = value; const int opIdx = cr.opCombo->currentIndex(); cond.op = (opIdx >= 0 && opIdx < 7) ? opMap[opIdx] : FilterCondition::Op::Eq; query.conditions.append(cond); } if (query.conditions.isEmpty()) return; emit userChoices(query); } SocNetV-3.7/src/forms/dialogquerybuilder.h000066400000000000000000000044111524402210300206570ustar00rootroot00000000000000/** * @file dialogquerybuilder.h * @brief Declares DialogQueryBuilder β€” multi-condition AND filter composer. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #pragma once #include #include #include #include "../graph/filters/filter_condition.h" #include "../graph/filters/graph_query.h" class QComboBox; class QLineEdit; class QPushButton; class QVBoxLayout; class QScrollArea; class QRadioButton; class QDialogButtonBox; /** * @brief Visual query builder that composes a multi-condition AND filter. * * The user picks a scope (Nodes or Edges), then adds one or more condition rows * (key / operator / value). Clicking Apply emits userChoices(GraphQuery) with * all conditions carrying the chosen scope. The caller applies the filter and * adds a single chip to the bar. * * Implemented in pure C++ (no .ui file) so condition rows can be added and * removed dynamically without UIC constraints. */ class DialogQueryBuilder : public QDialog { Q_OBJECT public: explicit DialogQueryBuilder(const QStringList &nodeKeys, const QStringList &edgeKeys, QWidget *parent = nullptr); signals: void userChoices(const GraphQuery &query); private slots: void onScopeChanged(); void onAddRow(); void onApply(); private: struct ConditionRow { QWidget *widget = nullptr; QComboBox *keyCombo = nullptr; QComboBox *opCombo = nullptr; QLineEdit *valueEdit= nullptr; QPushButton *removeBtn= nullptr; }; QStringList m_nodeKeys; QStringList m_edgeKeys; QRadioButton *m_nodesRadio; QRadioButton *m_edgesRadio; QVBoxLayout *m_rowsLayout; QWidget *m_rowsContainer; QScrollArea *m_scrollArea; QPushButton *m_addRowBtn; QDialogButtonBox*m_buttonBox; QList m_rows; void addRow(); void removeRow(QWidget *rowWidget); void repopulateKeys(); void updateRemoveButtons(); FilterCondition::Scope currentScope() const; }; SocNetV-3.7/src/forms/dialogranderdosrenyi.cpp000077500000000000000000000114341524402210300215340ustar00rootroot00000000000000/** * @file dialogranderdosrenyi.h * @brief Declares the DialogRandErdosRenyi class for generating ErdΕ‘s–RΓ©nyi random graphs in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include #include "forms_logging.h" #include #include #include #include #include #include "dialogranderdosrenyi.h" DialogRandErdosRenyi::DialogRandErdosRenyi(QWidget *parent, const qreal eprob) : QDialog(parent) { qCDebug(lcForms) << "::DialogRandErdosRenyi() " ; ui.setupUi(this); nodes = 0; model = ""; edges = 0; ui.probDoubleSpinBox->setValue(eprob); mode = ""; diag = false; connect ( ui.buttonBox, &QDialogButtonBox::accepted, this, &DialogRandErdosRenyi::getUserChoices ); ui.buttonBox->button (QDialogButtonBox::Ok)->setDefault(true); (ui.nodesSpinBox )->setFocus(); connect (ui.gnpRadioButton, &QRadioButton::clicked, this, &DialogRandErdosRenyi::checkErrors); connect (ui.gnmRadioButton, &QRadioButton::clicked, this, &DialogRandErdosRenyi::checkErrors); ui.gnpRadioButton->setChecked(true); ui.probDoubleSpinBox->setEnabled(true); ui.edgesSpinBox->setDisabled(true); ui.undirectedRadioButton->setChecked(true); ui.diagCheckBox->setChecked(false); connect (ui.gnpRadioButton, &QRadioButton::clicked, this, &DialogRandErdosRenyi::gnpModel ); connect (ui.gnmRadioButton, &QRadioButton::clicked, this, &DialogRandErdosRenyi::gnmModel ); connect ( ui.undirectedRadioButton,&QRadioButton::clicked, this, &DialogRandErdosRenyi::setModeUndirected ); connect ( ui.directedRadioButton,&QRadioButton::clicked, this, &DialogRandErdosRenyi::setModeDirected ); connect ( ui.diagCheckBox,&QCheckBox::clicked, this, &DialogRandErdosRenyi::setDiag); } void DialogRandErdosRenyi::gnpModel (){ ui.gnmRadioButton->setChecked(false); ui.probDoubleSpinBox->setEnabled(true); ui.edgesSpinBox->setDisabled(true); } void DialogRandErdosRenyi::gnmModel (){ ui.gnpRadioButton->setChecked(false); ui.probDoubleSpinBox->setDisabled(true); ui.edgesSpinBox->setEnabled(true); } void DialogRandErdosRenyi::setModeDirected (){ ui.directedRadioButton->setChecked(true) ; ui.undirectedRadioButton->setChecked(false) ; } void DialogRandErdosRenyi::setModeUndirected (){ ui.directedRadioButton->setChecked(false) ; ui.undirectedRadioButton->setChecked(true) ; } void DialogRandErdosRenyi::setDiag (){ if (ui.diagCheckBox->isChecked()) ui.diagCheckBox->setText("Yes, allow"); else ui.diagCheckBox->setText("No, set zero"); } void DialogRandErdosRenyi::checkErrors() { qCDebug(lcForms)<< " DialogRandErdosRenyi::checkErrors()" ; if ( !ui.gnpRadioButton->isChecked() && !ui.gnmRadioButton->isChecked()) { QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); QGraphicsColorizeEffect *effect2 = new QGraphicsColorizeEffect; effect2->setColor(QColor("red")); ui.gnpRadioButton->setGraphicsEffect(effect); ui.gnmRadioButton->setGraphicsEffect(effect2); (ui.buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); return; } else { ui.gnpRadioButton->setGraphicsEffect(0); ui.gnmRadioButton->setGraphicsEffect(0); (ui.buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(true); } } void DialogRandErdosRenyi::getUserChoices() { qCDebug(lcForms) << "DialogRandErdosRenyi::getUserChoices() " ; nodes = ui.nodesSpinBox->value(); model = ( ui.gnpRadioButton->isChecked() ) ? "G(n,p)" : "G(n,M)"; if ( ui.gnpRadioButton->isChecked() ) { // eprob = ui.probDoubleSpinBox->value(); } else { edges = ui.edgesSpinBox->value(); } mode = (ui.directedRadioButton->isChecked() ? "digraph" : "graph" ); diag = (ui.diagCheckBox->isChecked() ? true : false); qCDebug(lcForms) << "nodes " << nodes ; qCDebug(lcForms) << "model " << model; qCDebug(lcForms) << "eprob " << ui.probDoubleSpinBox->value(); qCDebug(lcForms) << "edges " << edges; qCDebug(lcForms) << "mode " << mode; qCDebug(lcForms) << "diag " << diag; emit userChoices(nodes, model, edges, ui.probDoubleSpinBox->value(), mode, diag); } SocNetV-3.7/src/forms/dialogranderdosrenyi.h000077500000000000000000000027101524402210300211760ustar00rootroot00000000000000/** * @file dialogranderdosrenyi.h * @brief Declares the DialogRandErdosRenyi class for generating random graphs based on the ErdΕ‘s–RΓ©nyi model in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGRANDERDOSRENYI_H #define DIALOGRANDERDOSRENYI_H #include #include "ui_dialogranderdosrenyi.h" class DialogRandErdosRenyi : public QDialog { Q_OBJECT public: explicit DialogRandErdosRenyi ( QWidget *parent = Q_NULLPTR, const qreal eprob = 0); public slots: void checkErrors(); void getUserChoices(); void gnmModel(); void gnpModel(); void setModeDirected(); void setModeUndirected(); void setDiag(); signals: void userChoices( const int nodes, const QString model, const int edges, const qreal eprob, const QString mode, const bool diag); private: QString model; QString mode; int nodes, edges; bool diag; Ui::DialogRandErdosRenyi ui; }; #endif SocNetV-3.7/src/forms/dialogranderdosrenyi.ui000066400000000000000000000434331524402210300213700ustar00rootroot00000000000000 DialogRandErdosRenyi 0 0 600 520 0 0 600 520 ErdΕ‘s–RΓ©nyi network generator 0 0 540 85 16777215 96 QFrame::Shape::NoFrame QFrame::Shadow::Sunken <html><head/><body><p>Generate random network according to ErdΕ‘s–RΓ©nyi (ER) model. </p><p>In fact, there are two models: in <span style=" font-style:italic;">G(n,p)</span> edges are created with Bernoulli trials, while in <span style=" font-style:italic;">G(n,M) </span>a graph is randomly selected from all graphs with <span style=" font-style:italic;">n</span> nodes and <span style=" font-style:italic;">M</span> edges. Read more in the manual.</p></body></html> Qt::TextFormat::RichText true Qt::Orientation::Horizontal 0 0 350 0 <html><head/><body><p>Nodes <span style=" font-style:italic;">n</span></p></body></html> 0 0 120 0 0 9999 100 Qt::Orientation::Horizontal 0 0 350 0 Model 120 0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This will create a new random network using <span style=" font-weight:600;">G(n,p)</span> model, where</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">n</span> is the number of nodes in the final graph</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">p</span> is the probability with which an edge is included in the graph</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If you select this model, you must enter the number of nodes n and the edge probability p. </p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">You may also select if the final network will be undirected or directed and if you want to allow nodes to link to themselves (diagonal non zero).</p></body></html> G(n, p) true false 100 0 <html><head/><body><p>This will create a new random network using <span style=" font-weight:600;">G(n,M)</span> model, where</p><p><span style=" font-weight:600;"> n</span> is the number of nodes in the final graph</p><p><span style=" font-weight:600;"> M</span> is the number of edges in the final graph</p><p>If you select this model, you must enter both the number of nodes n and the number of edges M</p><p>You may also select if the final network will be undirected or directed and if you want to allow nodes to link to themselves (diagonal non zero).</p></body></html> G(n, M) false false Qt::Orientation::Horizontal QLayout::SizeConstraint::SetMinimumSize 0 0 350 0 <html><head/><body><p>Edges <span style=" font-style:italic;">M </span><span style=" color:#7c7c7c;">for G(n,M) model only</span></p></body></html> 120 0 10 9999 Qt::Orientation::Horizontal 0 0 350 0 <html><head/><body><p>Edge Probability <span style=" color:#7c7c7c;">applicable only in G(n,p) model</span></p></body></html> 0 0 120 0 0.010000000000000 1.000000000000000 0.010000000000000 0.100000000000000 Qt::Orientation::Horizontal 0 0 350 0 Graph Mode 0 0 120 0 Undirected false false 0 0 120 0 Directed false false Qt::Orientation::Horizontal 0 0 350 0 Allow diagonals (loops) or set to zero? 0 0 120 0 Yes, allow false Qt::Orientation::Horizontal Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBox accepted() DialogRandErdosRenyi accept() 257 373 157 274 buttonBox rejected() DialogRandErdosRenyi reject() 325 373 286 274 SocNetV-3.7/src/forms/dialograndlattice.cpp000077500000000000000000000046401524402210300207770ustar00rootroot00000000000000/** * @file dialograndlattice.cpp * @brief Implements the DialogRandLattice class for generating random lattice networks in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include #include "forms_logging.h" #include #include #include #include #include #include "dialograndlattice.h" DialogRandLattice::DialogRandLattice(QWidget *parent) : QDialog(parent) { ui.setupUi(this); ui.circularCheckBox->setText("false"); ui.nodesSpinBox->setEnabled(false); connect ( ui.circularCheckBox, &QCheckBox::toggled , this, &DialogRandLattice::circularChanged); connect ( ui.buttonBox, &QDialogButtonBox::accepted, this, &DialogRandLattice::getUserChoices ); connect(ui.lengthSpinBox, SIGNAL(valueChanged(int)), this, SLOT(lengthChanged(int))); ui.buttonBox->button (QDialogButtonBox::Ok)->setDefault(true); } void DialogRandLattice::circularChanged(const bool &toggle) { if (toggle) { ui.circularCheckBox->setText("true"); } else { ui.circularCheckBox->setText("false"); } } void DialogRandLattice::lengthChanged(int l) { ui.nodesSpinBox->setValue(l*l); } void DialogRandLattice::getUserChoices() { qCDebug(lcForms) << "DialogRandSmallWorld::getUserChoices() " ; nodes = ui.nodesSpinBox->value(); length = ui.lengthSpinBox->value(); dimension = ui.dimSpinBox->value(); neighLength = ui.neiSpinBox->value(); mode = (ui.directedRadioButton->isChecked() ? "digraph" : "graph" ); circular = (ui.circularCheckBox->isChecked() ? true : false); qCDebug(lcForms) << "nodes " << nodes ; qCDebug(lcForms) << "length " << length; qCDebug(lcForms) << "dimension " << dimension; qCDebug(lcForms) << "neighLength" << neighLength; qCDebug(lcForms) << "mode " << mode; qCDebug(lcForms) << "diag " << circular; emit userChoices(nodes, length, dimension, neighLength, mode, circular); } SocNetV-3.7/src/forms/dialograndlattice.h000077500000000000000000000030121524402210300204340ustar00rootroot00000000000000/** * @file dialograndlattice.h * @brief Declares the DialogRandLattice class for configuring random lattice generation in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGRANDLATTICE_H #define DIALOGRANDLATTICE_H #include #include "ui_dialograndlattice.h" class DialogRandLattice : public QDialog { Q_OBJECT public: explicit DialogRandLattice(QWidget *parent = Q_NULLPTR); signals: void userChoices( const int &nodes, const int &length, const int &dimension, const int &neighLength, const QString &mode, const bool &diag); public slots: // void checkErrors(const int &i); void getUserChoices(); void circularChanged(const bool &toggle); void lengthChanged(int l); // void setModeDirected(); // void setModeUndirected(); // void setDiag(); // void modifyDegree(int value); private: Ui::DialogRandLattice ui; int nodes; int length; int dimension; int neighLength; QString mode; bool circular; }; #endif SocNetV-3.7/src/forms/dialograndlattice.ui000066400000000000000000000360531524402210300206320ustar00rootroot00000000000000 DialogRandLattice 0 0 600 460 0 0 600 460 Lattice network generator 0 0 450 70 16777215 75 QFrame::Shape::NoFrame QFrame::Shadow::Sunken <html><head/><body><p>Generate a lattice network. You can select how many dimensions the lattice will have (i.e. d=2 for a two-dimensional lattice) and the length of each dimension (i.e. l=3 for a 3x3 lattice of 9 nodes). Read more in the manual.</p></body></html> Qt::TextFormat::RichText true Qt::Orientation::Horizontal 0 0 380 0 Nodes 0 0 110 0 <html><head/><body><p><span style=" font-weight:600;">Lattice Nodes </span></p><p>The resulting lattice will have this amount of nodes . </p><p>This value changes automatically as you modify the <span style=" font-style:italic;">length l </span>of each dimension (below).</p></body></html> 4 9999 25 Qt::Orientation::Horizontal QLayout::SizeConstraint::SetMinimumSize 0 0 380 0 <html><head/><body><p>Length <span style=" font-style:italic;">l </span>in each dimension </p></body></html> 110 0 <html><head/><body><p><span style=" font-weight:600;">Length</span></p><p>The size of the lattice in each dimension.</p></body></html> 2 9999 2 5 Qt::Orientation::Horizontal 0 0 380 0 <html><head/><body><p>Dimension <span style=" font-style:italic;">d</span></p></body></html> 110 0 <html><head/><body><p><span style=" font-weight:600;">Dimension </span><span style=" font-weight:600; font-style:italic;">d</span></p><p>The dimension of the lattice.</p><p>I.e. enter 2 for a two-dimensional lattice.</p><p><br/></p></body></html> 2 3 1 2 Qt::Orientation::Horizontal 0 0 380 0 <html><head/><body><p>Neighborhood <span style=" font-style:italic;">n</span></p></body></html> 110 0 <html><head/><body><p><span style=" font-weight:600;">Neighborhood </span><span style=" font-weight:600; font-style:italic;">n</span></p><p>The distance within which the neighbors on the lattice will be connected</p></body></html> 1 6 1 1 Qt::Orientation::Horizontal 0 0 380 0 Graph Mode 0 0 110 0 Undirected true false 0 0 100 0 Directed false Qt::Orientation::Horizontal 0 0 380 0 Circular 0 0 110 0 <html><head/><body><p>If checked, the lattice will be circular</p></body></html> false false Qt::Orientation::Horizontal Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBox accepted() DialogRandLattice accept() 257 373 157 274 buttonBox rejected() DialogRandLattice reject() 325 373 286 274 SocNetV-3.7/src/forms/dialograndregular.cpp000077500000000000000000000102601524402210300210060ustar00rootroot00000000000000/** * @file dialograndregular.cpp * @brief Implements the DialogRandRegular class for generating random regular graphs in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include #include "forms_logging.h" #include #include #include #include #include #include #include "dialograndregular.h" DialogRandRegular::DialogRandRegular(QWidget *parent) : QDialog(parent) { qCDebug(lcForms) << "::DialogRandRegular() " ; ui.setupUi(this); nodes = 100; degree = 2; mode = "undirected"; diag = false; connect ( ui.buttonBox, &QDialogButtonBox::accepted, this, &DialogRandRegular::getUserChoices ); ui.buttonBox->button (QDialogButtonBox::Ok)->setDefault(true); ui.degreeSpinBox->setEnabled(true); ui.undirectedRadioButton->setChecked(true); ui.diagCheckBox->setChecked(false); ui.diagCheckBox->setEnabled(false); connect ( ui.undirectedRadioButton,&QRadioButton::clicked, this, &DialogRandRegular::setModeUndirected ); connect ( ui.directedRadioButton,&QRadioButton::clicked, this, &DialogRandRegular::setModeDirected ); connect ( ui.diagCheckBox,&QCheckBox::clicked, this, &DialogRandRegular::setDiag); ui.nodesSpinBox->setFocus(); ui.nodesSpinBox->setValue(nodes); ui.degreeSpinBox->setValue( degree ); connect(ui.nodesSpinBox, SIGNAL(valueChanged(int)), this, SLOT(checkErrors(int))); connect(ui.degreeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(checkErrors(int))); } void DialogRandRegular::modifyDegree(int value) { ui.degreeSpinBox->setValue( qCeil ( qLn (value) )); ui.degreeSpinBox->setMaximum( value ); } void DialogRandRegular::setModeDirected (){ ui.directedRadioButton->setChecked(true) ; ui.undirectedRadioButton->setChecked(false) ; ui.degreeLabel->setText("inDegree=outDegree d"); } void DialogRandRegular::setModeUndirected (){ ui.directedRadioButton->setChecked(false) ; ui.undirectedRadioButton->setChecked(true) ; ui.degreeLabel->setText("Degree d"); } void DialogRandRegular::setDiag (){ if (ui.diagCheckBox->isChecked()) ui.diagCheckBox->setText("Yes, allow"); else ui.diagCheckBox->setText("No, set zero"); } void DialogRandRegular::checkErrors(const int &i) { Q_UNUSED(i); qCDebug(lcForms)<< " DialogRandRegular::checkErrors()" ; if ( ( ui.degreeSpinBox->value() * ui.nodesSpinBox->value() ) % 2 !=0 || ( (double) ui.degreeSpinBox->value() / (double) ui.nodesSpinBox->value() ) >= 0.5 || ui.nodesSpinBox->value() < 6 ) { QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui.degreeSpinBox->setGraphicsEffect(effect); ui.nodesSpinBox->setGraphicsEffect(effect); (ui.buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); } else { ui.degreeSpinBox->setGraphicsEffect(0); ui.nodesSpinBox->setGraphicsEffect(0); (ui.buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(true); } } void DialogRandRegular::getUserChoices() { qCDebug(lcForms) << "DialogRandRegular::getUserChoices() " ; nodes = ui.nodesSpinBox->value(); degree= ui.degreeSpinBox->value(); mode = (ui.directedRadioButton->isChecked() ? "digraph" : "graph" ); diag = (ui.diagCheckBox->isChecked() ? true : false); qCDebug(lcForms) << "nodes " << nodes ; qCDebug(lcForms) << "degree" << degree; qCDebug(lcForms) << "mode " << mode; qCDebug(lcForms) << "diag " << diag; emit userChoices(nodes, degree, mode, diag); } SocNetV-3.7/src/forms/dialograndregular.h000077500000000000000000000024331524402210300204560ustar00rootroot00000000000000/** * @file dialograndregular.h * @brief Declares the DialogRandRegular class for generating random regular graphs in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGRANDREGULAR_H #define DIALOGRANDREGULAR_H #include #include "ui_dialograndregular.h" class DialogRandRegular : public QDialog { Q_OBJECT public: explicit DialogRandRegular(QWidget *parent = Q_NULLPTR); public slots: void checkErrors(const int &i); void getUserChoices(); void setModeDirected(); void setModeUndirected(); void setDiag(); void modifyDegree(int value); signals: void userChoices( const int nodes, const int degree, const QString mode, const bool diag); private: QString mode; int nodes, degree; bool diag; Ui::DialogRandRegular ui; }; #endif SocNetV-3.7/src/forms/dialograndregular.ui000066400000000000000000000337461524402210300206540ustar00rootroot00000000000000 DialogRandRegular 0 0 600 440 0 0 600 440 d-Regular network generator 0 0 450 70 16777215 95 QFrame::Shape::NoFrame QFrame::Shadow::Sunken <html><head/><body><p>Generate a <span style=" font-style:italic;">d-regular</span> random network of <span style=" font-style:italic;">n</span> nodes. This is a graph where each vertex has the same number of neighbors <span style=" font-style:italic;">d</span>.</p><p>This model produces undirect and directed provided that n &gt; 5, d/<span style=" font-style:italic;">n &lt; 0.5 </span>and <span style=" font-style:italic;">n*d </span>is even. Read more in the manual.</p></body></html> Qt::TextFormat::RichText true Qt::Orientation::Horizontal 0 0 380 0 <html><head/><body><p>The number n of nodes in the new Regular Network.</p><p>Note: For a <span style=" font-style:italic;">d</span>-regular graph of <span style=" font-style:italic;">n</span> nodes, it is necessary that <span style=" font-style:italic;">n &gt;= d + 1 </span>and <span style=" font-style:italic;">n*d </span>is even</p></body></html> Nodes 0 0 110 0 <html><head/><body><p>Enter number n of nodes in the new Regular Network.</p><p>Constraints: </p><p><span style=" font-style:italic;">n &gt; 6 </span>and <span style=" font-style:italic;"/></p><p><span style=" font-style:italic;">d/n &gt; 0.5 </span>and</p><p><span style=" font-style:italic;">n*d </span>is even</p></body></html> 4 9999 100 Qt::Orientation::Horizontal QLayout::SizeConstraint::SetMinimumSize 0 0 380 0 <html><head/><body><p>Degree <span style=" font-style:italic;">d</span></p></body></html> 110 0 <html><head/><body><p>Enter the degree <span style=" font-style:italic;">d</span> each new node will have.</p><p>Constraints: </p><p><span style=" font-style:italic;">d/n &gt; 0.5 </span>and</p><p><span style=" font-style:italic;">n*d </span>is even</p><p>In directed Graph Mode, this Degree <span style=" font-style:italic;">d</span> option corresponds to the outDegree and the inDegree, which will be both equal to <span style=" font-style:italic;">d</span> in the generated directed regular network. </p><p><br/></p></body></html> 2 9999 1 2 Qt::Orientation::Horizontal 0 0 400 0 Graph Mode 0 0 110 0 <html><head/><body><p>Click &quot;undirected&quot; to generate an undirected <span style=" font-style:italic;">d</span>-regular network </p></body></html> Undirected true false 0 0 100 0 <html><head/><body><p>Click &quot;directed&quot; to generate a directed <span style=" font-style:italic;">d</span>-regular network. </p><p>In this case, the Degree <span style=" font-style:italic;">d</span> option above corresponds to the outDegree and the inDegree which will be both equal to <span style=" font-style:italic;">d</span> in the generated regular network. </p><p>For instance, if you select <span style=" font-style:italic;">d</span>=4 and <span style=" font-style:italic;">Graph Mode</span>=directed, then each new node will have outDegree=4 (four outbound edges) and inDegree=4 (four inbound edges). The inbound and outbound edges of each node will not necessarily be from / to the same nodes.</p></body></html> Directed false Qt::Orientation::Horizontal 0 0 400 0 Allow diagonals (loops) or set to zero? 0 0 110 0 Check to allow loops (nodes linking to themselves) in the new network No loops false Qt::Orientation::Horizontal Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBox accepted() DialogRandRegular accept() 257 373 157 274 buttonBox rejected() DialogRandRegular reject() 325 373 286 274 SocNetV-3.7/src/forms/dialograndscalefree.cpp000077500000000000000000000075271524402210300213120ustar00rootroot00000000000000/** * @file randscalefreeddialog.cpp * @brief Implements the RandScaleFreeDialog class for creating scale-free networks using the BarabΓ‘si–Albert model in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialograndscalefree.h" #include #include "forms_logging.h" #include #include #include #include DialogRandScaleFree::DialogRandScaleFree(QWidget *parent) : QDialog(parent) { qCDebug(lcForms) << "DialogRandScaleFree::DialogRandScaleFree() " ; ui.setupUi(this); nodes = 0; initialNodes = 0; mode = ""; diag = false; connect ( ui.buttonBox, &QDialogButtonBox::accepted, this, &DialogRandScaleFree::getUserChoices ); ui.buttonBox->button (QDialogButtonBox::Ok)->setDefault(true); (ui.nodesSpinBox )->setFocus(); ui.initialNodesSpinBox->setEnabled(true); ui.undirectedRadioButton->setChecked(false); ui.directedRadioButton->setEnabled(true); ui.directedRadioButton->setChecked(true); ui.diagCheckBox->setText("No, set zero"); ui.diagCheckBox->setChecked(false); ui.diagCheckBox->setEnabled(false); connect ( ui.undirectedRadioButton,&QRadioButton::clicked, this, &DialogRandScaleFree::setModeUndirected ); connect ( ui.directedRadioButton,&QRadioButton::clicked, this, &DialogRandScaleFree::setModeDirected ); connect ( ui.diagCheckBox,&QCheckBox::clicked, this, &DialogRandScaleFree::setDiag); } void DialogRandScaleFree::setModeDirected (){ ui.directedRadioButton->setChecked(true) ; ui.undirectedRadioButton->setChecked(false) ; } void DialogRandScaleFree::setModeUndirected (){ ui.directedRadioButton->setChecked(false) ; ui.undirectedRadioButton->setChecked(true) ; } void DialogRandScaleFree::setDiag (){ if (ui.diagCheckBox->isChecked()) ui.diagCheckBox->setText("Yes, allow"); else ui.diagCheckBox->setText("No, set zero"); } void DialogRandScaleFree::checkErrors() { qCDebug(lcForms)<< " DialogRandSmallWorld::checkErrors()" ; // if ( !ui.gnpRadioButton->isChecked() && !ui.gnmRadioButton->isChecked()) // { // QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; // effect->setColor(QColor("red")); // ui.gnpRadioButton->setGraphicsEffect(effect); // (ui.buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); // } // else { // ui.gnpRadioButton->setGraphicsEffect(0); // ui.gnmRadioButton->setGraphicsEffect(0); // (ui.buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(true); // } //getUserChoices(); } void DialogRandScaleFree::getUserChoices() { qCDebug(lcForms) << "DialogRandScaleFree::getUserChoices() " ; nodes = ui.nodesSpinBox->value(); power = ui.powerSpinBox->value(); initialNodes = ui.initialNodesSpinBox->value(); edgesPerStep = ui.edgesPerStepSpinBox->value(); zeroAppeal = ui.zeroAppealSpinBox->value(); mode = (ui.directedRadioButton->isChecked() ? "digraph" : "graph" ); // diag = (ui.diagCheckBox->isChecked() ? true : false); qCDebug(lcForms) << "nodes " << nodes ; qCDebug(lcForms) << "initialNodes " << initialNodes; qCDebug(lcForms) << "mode " << mode; qCDebug(lcForms) << "diag " << diag; emit userChoices(nodes, power, initialNodes, edgesPerStep,zeroAppeal, mode); } SocNetV-3.7/src/forms/dialograndscalefree.h000077500000000000000000000027471524402210300207560ustar00rootroot00000000000000/** * @file randscalefreeddialog.h * @brief Declares the RandScaleFreeDialog class for generating scale-free networks based on the BarabΓ‘si–Albert model in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGRANDSCALEFREE_H #define DIALOGRANDSCALEFREE_H #include #include "ui_dialograndscalefree.h" class DialogRandScaleFree : public QDialog { Q_OBJECT public: explicit DialogRandScaleFree(QWidget *parent = Q_NULLPTR); public slots: void checkErrors(); void getUserChoices(); void setModeDirected(); void setModeUndirected(); void setDiag(); signals: void userChoices( const int &nodes, const int &power, const int &initialNodes, const int &edgesPerStep, const qreal &zeroAppeal, const QString &mode); private: QString mode; int nodes; // n int initialNodes; // m0 int edgesPerStep; //m int power; qreal zeroAppeal; // a bool diag; Ui::DialogRandScaleFree ui; }; #endif SocNetV-3.7/src/forms/dialograndscalefree.ui000066400000000000000000000417141524402210300211360ustar00rootroot00000000000000 DialogRandScaleFree 0 0 600 530 0 0 600 530 Scale-free random network generator 0 0 450 100 16777215 110 QFrame::Shape::NoFrame QFrame::Shadow::Sunken <html><head/><body><p>Generate a random scale-free network of <span style=" font-style:italic;">n</span> nodes according to the BarabΓ‘si–Albert (BA) model which uses a preferential attachment mechanism. </p><p>The model starts with <span style=" font-style:italic;">m</span><span style=" font-style:italic; vertical-align:sub;">0</span> connected nodes. In each step a new node is added, along with <span style=" font-style:italic;">m</span> edges to existing nodes. Read more in the manual.</p></body></html> Qt::TextFormat::RichText true Qt::Orientation::Horizontal 0 0 380 0 <html><head/><body><p>Nodes <span style=" font-style:italic;">n</span></p></body></html> 0 0 120 0 <html><head/><body><p>The amount of nodes in the resulting scale-free graph</p></body></html> 4 9999 100 Qt::Orientation::Horizontal QLayout::SizeConstraint::SetMinimumSize 0 0 380 0 <html><head/><body><p>Power of preferential attachment <span style=" font-style:italic;">p</span></p></body></html> 120 0 <html><head/><body><p>The power p of preferential attachment </p><p>Leave 1 for linear preferential attachment (BA model).</p></body></html> 1 9999 1 1 Qt::Orientation::Horizontal QLayout::SizeConstraint::SetMinimumSize 0 0 380 0 <html><head/><body><p>Initial connected nodes <span style=" font-style:italic;">m</span><span style=" font-style:italic; vertical-align:sub;">0</span></p></body></html> 120 0 <html><head/><body><p>The number m<span style=" vertical-align:sub;">0</span> of nodes in the initial connected network.</p><p>Leave 1 to start with just one node.</p></body></html> 1 9999 1 1 Qt::Orientation::Horizontal QLayout::SizeConstraint::SetMinimumSize 0 0 380 0 <html><head/><body><p>Edges to add in each step <span style=" font-style:italic;">m</span></p></body></html> 120 0 <html><head/><body><p>The number of edges to add in each step</p></body></html> 1 9999 1 2 Qt::Orientation::Horizontal QLayout::SizeConstraint::SetMinimumSize 0 0 380 0 <html><head/><body><p>Zero appeal <span style=" font-style:italic;">Ξ±</span></p></body></html> 120 0 <html><head/><body><p>The initial attractiveness of a node - useful for isolate nodes with d =0</p></body></html> 0 9999 1 1 Qt::Orientation::Horizontal 0 0 400 0 Graph Mode 0 0 120 0 Undirected true false 0 0 120 0 Directed false Qt::Orientation::Horizontal 0 0 440 0 Allow diagonals (loops) or set to zero? 0 0 120 0 Check to allow loops (nodes linking to themselves) in the new network No loops false Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBox accepted() DialogRandScaleFree accept() 257 373 157 274 buttonBox rejected() DialogRandScaleFree reject() 325 373 286 274 SocNetV-3.7/src/forms/dialograndsmallworld.cpp000077500000000000000000000102611524402210300215260ustar00rootroot00000000000000/** * @file dialograndsmallworld.cpp * @brief Implements the DialogRandSmallWorld class for generating small-world networks using the Watts-Strogatz model in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include #include "forms_logging.h" #include #include #include #include #include #include #include "dialograndsmallworld.h" DialogRandSmallWorld::DialogRandSmallWorld(QWidget *parent) : QDialog(parent), ui(new Ui::DialogRandSmallWorld) { qCDebug(lcForms) << "DialogRandSmallWorld::DialogRandSmallWorld() " ; ui->setupUi(this); nodes = 100; degree = qCeil ( qLn (nodes) ); bprob = 0; mode = "undirected"; diag = false; connect ( ui->buttonBox, &QDialogButtonBox::accepted, this, &DialogRandSmallWorld::getUserChoices ); ui->buttonBox->button (QDialogButtonBox::Ok)->setDefault(true); ui->probDoubleSpinBox->setEnabled(true); ui->degreeSpinBox->setEnabled(true); ui->undirectedRadioButton->setChecked(true); ui->directedRadioButton->setEnabled(false); ui->diagCheckBox->setChecked(false); ui->diagCheckBox->setEnabled(false); connect ( ui->undirectedRadioButton,&QRadioButton::clicked, this, &DialogRandSmallWorld::setModeUndirected ); connect ( ui->directedRadioButton,&QRadioButton::clicked, this, &DialogRandSmallWorld::setModeDirected ); connect ( ui->diagCheckBox,&QCheckBox::clicked, this, &DialogRandSmallWorld::setDiag); connect(ui->nodesSpinBox, SIGNAL(valueChanged(int)), this, SLOT(modifyDegree(int))); ui->nodesSpinBox->setFocus(); ui->nodesSpinBox->setValue(nodes); ui->degreeSpinBox->setValue( degree ); } void DialogRandSmallWorld::modifyDegree(int value) { ui->degreeSpinBox->setValue( qCeil ( qLn (value) )); ui->degreeSpinBox->setMaximum( value ); } void DialogRandSmallWorld::setModeDirected (){ ui->directedRadioButton->setChecked(true) ; ui->undirectedRadioButton->setChecked(false) ; } void DialogRandSmallWorld::setModeUndirected (){ ui->directedRadioButton->setChecked(false) ; ui->undirectedRadioButton->setChecked(true) ; } void DialogRandSmallWorld::setDiag (){ if (ui->diagCheckBox->isChecked()) ui->diagCheckBox->setText("Yes, allow"); else ui->diagCheckBox->setText("No, set zero"); } void DialogRandSmallWorld::checkErrors() { qCDebug(lcForms)<< " DialogRandSmallWorld::checkErrors()" ; // if ( !ui->gnpRadioButton->isChecked() && !ui->gnmRadioButton->isChecked()) // { // QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; // effect->setColor(QColor("red")); // ui->gnpRadioButton->setGraphicsEffect(effect); // (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); // } // else { // ui->gnpRadioButton->setGraphicsEffect(0); // ui->gnmRadioButton->setGraphicsEffect(0); // (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(true); // } //getUserChoices(); } void DialogRandSmallWorld::getUserChoices() { qCDebug(lcForms) << "DialogRandSmallWorld::getUserChoices() " ; nodes = ui->nodesSpinBox->value(); bprob = ui->probDoubleSpinBox->value(); degree= ui->degreeSpinBox->value(); mode = (ui->directedRadioButton->isChecked() ? "digraph" : "graph" ); diag = (ui->diagCheckBox->isChecked() ? true : false); qCDebug(lcForms) << "nodes " << nodes ; qCDebug(lcForms) << "bprob " << bprob; qCDebug(lcForms) << "degree" << degree; qCDebug(lcForms) << "mode " << mode; qCDebug(lcForms) << "diag " << diag; emit userChoices(nodes, degree, bprob, mode, diag); } SocNetV-3.7/src/forms/dialograndsmallworld.h000077500000000000000000000026021524402210300211730ustar00rootroot00000000000000/** * @file dialograndsmallworld.h * @brief Declares the DialogRandSmallWorld class for generating small-world networks based on the Watts-Strogatz model in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGRANDSMALLWORLD_H #define DIALOGRANDSMALLWORLD_H #include #include "ui_dialograndsmallworld.h" class DialogRandSmallWorld : public QDialog { Q_OBJECT public: explicit DialogRandSmallWorld(QWidget *parent = Q_NULLPTR); public slots: void checkErrors(); void getUserChoices(); void setModeDirected(); void setModeUndirected(); void setDiag(); void modifyDegree(int value); signals: void userChoices( const int nodes, const int degree, const qreal prob, const QString mode, const bool diag); private: QString mode; int nodes, degree; qreal bprob; bool diag; Ui::DialogRandSmallWorld *ui; }; #endif SocNetV-3.7/src/forms/dialograndsmallworld.ui000066400000000000000000000313031524402210300213560ustar00rootroot00000000000000 DialogRandSmallWorld 0 0 600 420 0 0 600 400 Small-World network generator 0 0 450 70 16777215 75 QFrame::Shape::NoFrame QFrame::Shadow::Sunken <html><head/><body><p>Generate random network according to the <span style=" font-style:italic;">Watts &amp; Strogatz</span> model.</p><p>This model produces graphs with small-world properties, including short average path lengths and high clustering. Read more in the manual.</p></body></html> Qt::TextFormat::RichText true Qt::Orientation::Horizontal 0 0 380 0 Nodes 0 0 110 0 <html><head/><body><p>The resulting graph will have N nodes and N*d/2 edges</p></body></html> 4 9999 100 Qt::Orientation::Horizontal QLayout::SizeConstraint::SetMinimumSize 0 0 380 0 <html><head/><body><p>Mean Degree <span style=" font-style:italic;">d</span></p></body></html> 110 0 <html><head/><body><p>This is the mean edge degree each new node will have</p></body></html> 2 9999 2 10 Qt::Orientation::Horizontal 0 0 400 0 <html><head/><body><p>Rewiring Probability <span style=" font-style:italic;">Ξ²</span></p></body></html> 0 0 110 0 0.010000000000000 1.000000000000000 0.010000000000000 0.300000000000000 Qt::Orientation::Horizontal 0 0 400 0 Graph Mode 0 0 110 0 Undirected true false 0 0 100 0 Directed false Qt::Orientation::Horizontal 0 0 400 0 Allow diagonals (loops) or set to zero? 0 0 110 0 Check to allow loops (nodes linking to themselves) in the new network No loops false Qt::Orientation::Horizontal Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBox accepted() DialogRandSmallWorld accept() 257 373 157 274 buttonBox rejected() DialogRandSmallWorld reject() 325 373 286 274 SocNetV-3.7/src/forms/dialogsettings.cpp000077500000000000000000001043721524402210300203500ustar00rootroot00000000000000/** * @file dialogsettings.cpp * @brief Implements the DialogSettings class for managing user preferences and application settings in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogsettings.h" #include "ui_dialogsettings.h" #include "forms_logging.h" #include #include #include #include #include #include #include #include #include "global.h" SOCNETV_USE_NAMESPACE // WS14: definition for the category declared in forms_logging.h, shared by all src/forms/ dialogs. Q_LOGGING_CATEGORY(lcForms, "socnetv.forms") DialogSettings::DialogSettings(QMap &appSettings, const QStringList &nodeShapeList, const QStringList &iconPathList, QWidget *parent) : QDialog(parent), m_appSettings(appSettings), m_shapeList(nodeShapeList), m_iconList(iconPathList), ui(new Ui::DialogSettings) { ui->setupUi(this); // m_appSettings = appSettings; //only use if var passed by pointer //data export ui->dataDirEdit->setText( (m_appSettings)["dataDir"]); ui->printLogoChkBox->setChecked( (appSettings["printLogo"] == "true") ? true:false ); // reports ui->reportsRealNumberPrecisionSpin-> setValue(m_appSettings["initReportsRealNumberPrecision"].toInt(0, 10) ); ui->reportsLabelsLengthSpin-> setValue(m_appSettings["initReportsLabelsLength"].toInt(0, 10) ); QStringList chartTypesList; chartTypesList << "None" << "Lines" << "Area" << "Bars" ; ui->reportsChartTypeSelect->addItems(chartTypesList); switch (appSettings["initReportsChartType"].toInt()) { case ChartType::None: ui->reportsChartTypeSelect->setCurrentText( "None"); break; case ChartType::Spline: ui->reportsChartTypeSelect->setCurrentText( "Lines"); break; case ChartType::Area: ui->reportsChartTypeSelect->setCurrentText( "Area"); break; case ChartType::Bars: ui->reportsChartTypeSelect->setCurrentText( "Bars"); break; default: ui->reportsChartTypeSelect->setCurrentText( "Lines"); break; } qCDebug(lcForms) << "reportsChartTypeSelect" << ui->reportsChartTypeSelect->currentText(); QStringList outputFormatsList; outputFormatsList << "HTML" << "CSV"; ui->reportsOutputFormatSelect->addItems(outputFormatsList); ui->reportsOutputFormatSelect->setCurrentIndex( (appSettings["initReportsOutputFormat"].toInt() == ReportFormat::Csv) ? 1 : 0); //debugging ui->printDebugChkBox->setChecked( (appSettings["printDebug"] == "true") ? true:false ); ui->progressDialogChkBox->setChecked( (appSettings["showProgressBar"] == "true") ? true:false ); /** * Style options */ ui->useCustomStylesheetChkBox->setChecked( (appSettings["useCustomStyleSheet"] == "true") ? true:false ); /** * window options */ ui->leftPanelChkBox->setChecked( ( appSettings["showLeftPanel"] == "true") ? true:false ); ui->rightPanelChkBox->setChecked( ( appSettings["showRightPanel"] == "true") ? true:false ); /** * Options (saving, etc) **/ ui->saveZeroWeightEdgesChkBox->setChecked( (appSettings["saveZeroWeightEdges"] == "true") ? true:false ); ui->showZeroWeightEdgesChkBox->setChecked( // #30 (appSettings["showZeroWeightEdges"] == "true") ? true:false ); // #30: keep color picker in sync with the show-toggle at open time bool showZero = (appSettings["showZeroWeightEdges"] == "true"); ui->label_20->setEnabled(showZero); ui->edgeColorZeroBtn->setEnabled(showZero); /** * GraphicsWidget (canvas) options */ m_bgColor = QColor (m_appSettings["initBackgroundColor"]); m_pixmap = QPixmap(60,20) ; m_pixmap.fill( m_bgColor ); ui->bgColorButton->setIcon(QIcon(m_pixmap)); ui->bgImageSelectEdit->setText((m_appSettings)["initBackgroundImage"]); ui->canvasUseOpenGLChkBox->setChecked( (appSettings["opengl"] == "true" ) ? true : false ); ui->canvasAntialiasingChkBox->setChecked( (appSettings["antialiasing"] == "true") ? true:false ); ui->canvasAntialiasingAutoAdjustChkBox->setChecked( (appSettings["canvasAntialiasingAutoAdjustment"] == "true") ? true:false ); ui->canvasSmoothPixmapTransformChkBox->setChecked( (appSettings["canvasSmoothPixmapTransform"] == "true") ? true:false ); ui->canvasSavePainterStateChkBox->setChecked( (appSettings["canvasPainterStateSave"] == "true") ? true:false ); ui->canvasCacheBackgroundChkBox->setChecked( (appSettings["canvasCacheBackground"] == "true") ? true:false ); ui->canvasEdgeHighlightingChkBox->setChecked( (appSettings["canvasEdgeHighlighting"] == "true") ? true:false ); QStringList optionsList; optionsList << "Full" << "Minimal" << "Smart" << "Bounding Rectangle" << "None"; ui->canvasUpdateModeSelect->addItems(optionsList); if ( appSettings["canvasUpdateMode"] == "Full" ) { ui->canvasUpdateModeSelect->setCurrentText( "Full"); } else if (appSettings["canvasUpdateMode"] == "Minimal" ) { ui->canvasUpdateModeSelect->setCurrentText("Minimal" ); } else if (appSettings["canvasUpdateMode"] == "Smart" ) { ui->canvasUpdateModeSelect->setCurrentText("Smart" ); } else if (appSettings["canvasUpdateMode"] == "Bounding Rectangle" ) { ui->canvasUpdateModeSelect->setCurrentText("Bounding Rectangle" ); } else if (appSettings["canvasUpdateMode"] == "None" ) { ui->canvasUpdateModeSelect->setCurrentText("None" ); } else { // ui->canvasUpdateModeSelect->setCurrentText("Minimal" ); } qCDebug(lcForms) << "canvasUpdateModeSelect" << appSettings["canvasUpdateMode"]; optionsList.clear(); optionsList << "BspTreeIndex" << "NoIndex" ; ui->canvasIndexMethodSelect->addItems(optionsList); if ( appSettings["canvasIndexMethod"] == "BspTreeIndex" ) { ui->canvasIndexMethodSelect->setCurrentText( "BspTreeIndex"); } else if (appSettings["canvasIndexMethod"] == "NoIndex" ) { ui->canvasIndexMethodSelect->setCurrentText("NoIndex" ); } else { // ui->canvasIndexMethodSelect->setCurrentText("BspTreeIndex" ); } qCDebug(lcForms) << "canvasIndexMethodSelect" << appSettings["canvasIndexMethod"]; /** * node options */ m_nodeColor = QColor (m_appSettings["initNodeColor"]); m_pixmap = QPixmap(60,20) ; m_pixmap.fill( m_nodeColor ); ui->nodeColorBtn->setIcon(QIcon(m_pixmap)); ui->nodeShapeComboBox->addItems(m_shapeList); for (int i = 0; i < m_shapeList.size(); ++i) { ui->nodeShapeComboBox->setItemIcon(i, QIcon(m_iconList[i])); } ui->nodeIconSelectButton->setEnabled(false); ui->nodeIconSelectEdit->setEnabled(false); int index = -1; if ( (index = m_shapeList.indexOf(m_appSettings["initNodeShape"])) != -1 ){ ui->nodeShapeComboBox->setCurrentIndex(index); if ( index == NodeShape::Custom ) { ui->nodeShapeComboBox->setCurrentIndex(NodeShape::Custom); ui->nodeIconSelectButton->setEnabled(true); ui->nodeIconSelectEdit->setEnabled(true); ui->nodeIconSelectEdit->setText (m_appSettings["initNodeIconPath"]); if ( ! m_appSettings["initNodeIconPath"].isEmpty() ) { ui->nodeShapeComboBox->setItemIcon( NodeShape::Custom, QIcon(m_appSettings["initNodeIconPath"])); } else { QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui->nodeIconSelectButton->setGraphicsEffect(effect); ui->nodeIconSelectEdit->setGraphicsEffect(effect); (ui->buttonBox)->button (QDialogButtonBox::Cancel)->setDefault(true); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); } } } else { // default -- should never happen... ui->nodeShapeComboBox->setCurrentIndex(NodeShape::Circle); } ui->nodeSizeSpin->setValue( m_appSettings["initNodeSize"].toInt(0, 10) ); ui->nodeNumbersChkBox->setChecked( ( m_appSettings["initNodeNumbersVisibility"] == "true") ? true : false ); ui->nodeNumbersInsideChkBox->setChecked( (m_appSettings["initNodeNumbersInside"] == "true" ) ? true:false ); if (m_appSettings["initNodeNumbersInside"] == "true") { ui->nodeNumberDistanceSpin->setEnabled(false); ui->nodeNumberSizeSpin->setValue( 0 ); } m_nodeNumberColor = QColor (m_appSettings["initNodeNumberColor"]); m_pixmap = QPixmap(60,20) ; m_pixmap.fill( m_nodeNumberColor ); ui->nodeNumberColorBtn->setIcon(QIcon(m_pixmap)); ui->nodeNumberSizeSpin->setValue( m_appSettings["initNodeNumberSize"].toInt(0, 10) ); ui->nodeNumberDistanceSpin->setValue( m_appSettings["initNodeNumberDistance"].toInt(0, 10) ); ui->nodeLabelsChkBox->setChecked( ( m_appSettings["initNodeLabelsVisibility"] == "true") ? true : false ); ui->nodeLabelSizeSpin->setValue( m_appSettings["initNodeLabelSize"].toInt(0, 10) ); m_nodeLabelColor = QColor (m_appSettings["initNodeLabelColor"]); m_pixmap = QPixmap(60,20) ; m_pixmap.fill( m_nodeLabelColor ); ui->nodeLabelColorBtn->setIcon(QIcon(m_pixmap)); ui->nodeLabelDistanceSpin->setValue( m_appSettings["initNodeLabelDistance"].toInt(0, 10) ); /** * edge options */ ui->edgesChkBox->setChecked( (m_appSettings["initEdgesVisibility"] == "true") ? true: false ); ui->edgeArrowsChkBox->setChecked( (m_appSettings["initEdgeArrows"] == "true") ? true: false ); ui->edgeArrowSizeSpin->setValue( m_appSettings["initEdgeArrowSize"].toInt(nullptr, 10)); m_edgeColor = QColor (m_appSettings["initEdgeColor"]); m_pixmap = QPixmap(60,20) ; m_pixmap.fill( m_edgeColor ); ui->edgeColorBtn->setIcon(QIcon(m_pixmap)); m_edgeColorNegative = QColor (m_appSettings["initEdgeColorNegative"]); m_pixmap = QPixmap(60,20) ; m_pixmap.fill( m_edgeColorNegative ); ui->edgeColorNegativeBtn->setIcon(QIcon(m_pixmap)); m_edgeColorZero = QColor (m_appSettings["initEdgeColorZero"]); m_pixmap = QPixmap(60,20) ; m_pixmap.fill( m_edgeColorZero); ui->edgeColorZeroBtn->setIcon(QIcon(m_pixmap)); if (m_appSettings["initEdgeShape"] == "line") { ui->edgeShapeRadioStraightLine->setChecked(true); } else if (m_appSettings["initEdgeShape"] == "bezier") { ui->edgeShapeRadioBezier->setChecked(true); } else { ui->edgeShapeRadioStraightLine->setChecked(true); } ui->edgeOffsetFromNodeSpin->setValue( m_appSettings["initEdgeOffsetFromNode"].toInt(0, 10) ); ui->edgeWeightNumbersChkBox->setChecked( (m_appSettings["initEdgeWeightNumbersVisibility"] == "true") ? true: false ); m_edgeWeightNumberColor = QColor (m_appSettings["initEdgeWeightNumberColor"]); m_pixmap = QPixmap(60,20) ; m_pixmap.fill( m_edgeWeightNumberColor ); ui->edgeWeightNumberColorBtn->setIcon(QIcon(m_pixmap)); ui->edgeWeightNumberSizeSpin->setValue( m_appSettings["initEdgeWeightNumberSize"].toInt(0, 10) ); ui->edgeLabelsChkBox->setChecked( (m_appSettings["initEdgeLabelsVisibility"] == "true") ? true: false ); /** * dialog signals to slots */ connect (ui->dataDirSelectButton, &QToolButton::clicked, this, &DialogSettings::getDataDir); connect (ui->printDebugChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setDebugMsgs); connect(ui->reportsRealNumberPrecisionSpin, SIGNAL(valueChanged(int)), this, SLOT(getReportsRealNumberPrecision(int)) ); connect (ui->reportsLabelsLengthSpin, SIGNAL(valueChanged(int)), this, SLOT(getReportsLabelsLength(int))); connect(ui->reportsChartTypeSelect, SIGNAL ( currentIndexChanged (const int &)), this, SLOT(getReportsChartType(const int &)) ); connect(ui->reportsOutputFormatSelect, SIGNAL ( currentIndexChanged (const int &)), this, SLOT(getReportsOutputFormat(const int &)) ); connect (ui->printLogoChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setPrintLogo); connect( ui->useCustomStylesheetChkBox,&QCheckBox::clicked, this, &DialogSettings::setCustomStylesheet); connect (ui->progressDialogChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setProgressDialog); connect (ui->showToolBarChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setToolBar); connect (ui->showStatusBarChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setStatusBar); connect (ui->leftPanelChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setLeftPanel); connect (ui->rightPanelChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setRightPanel); connect (ui->bgColorButton, &QToolButton::clicked, this, &DialogSettings::getCanvasBgColor); connect (ui->bgImageSelectButton, &QToolButton::clicked, this, &DialogSettings::getCanvasBgImage); connect (ui->canvasUseOpenGLChkBox , &QCheckBox::stateChanged, this, &DialogSettings::setCanvasOpenGL); connect (ui->canvasAntialiasingChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setCanvasAntialiasing); connect (ui->canvasAntialiasingAutoAdjustChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setCanvasAntialiasingAutoAdjust); connect (ui->canvasSmoothPixmapTransformChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setCanvasSmoothPixmapTransform); connect (ui->canvasSavePainterStateChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setCanvasSavePainterState); connect (ui->canvasCacheBackgroundChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setCanvasCacheBackground); connect (ui->canvasEdgeHighlightingChkBox, &QCheckBox::stateChanged, this, &DialogSettings::setCanvasEdgeHighlighting); connect(ui->canvasUpdateModeSelect, SIGNAL ( currentTextChanged (const QString &)), this, SLOT(getCanvasUpdateMode(const QString &)) ); connect(ui->canvasIndexMethodSelect, SIGNAL ( currentTextChanged (const QString &)), this, SLOT(getCanvasIndexMethod(const QString &)) ); connect (ui->nodeShapeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &DialogSettings::getNodeShapeIndex); connect (ui->nodeIconSelectButton, &QToolButton::clicked, this, &DialogSettings::getNodeIconFile); connect(ui->nodeSizeSpin, SIGNAL(valueChanged(int)), this, SLOT(getNodeSize(int)) ); connect (ui->nodeColorBtn, &QToolButton::clicked, this, &DialogSettings::getNodeColor); connect (ui->nodeNumbersChkBox, &QCheckBox::stateChanged, this, &DialogSettings::getNodeNumbersVisibility); connect (ui->nodeNumbersInsideChkBox, &QCheckBox::stateChanged, this, &DialogSettings::getNodeNumbersInside); connect (ui->nodeNumberColorBtn, &QToolButton::clicked, this, &DialogSettings::getNodeNumberColor); connect(ui->nodeNumberSizeSpin, SIGNAL(valueChanged(int)), this, SLOT(getNodeNumberSize(int)) ); connect(ui->nodeNumberDistanceSpin, SIGNAL(valueChanged(int)), this, SLOT(getNodeNumberDistance(int)) ); connect (ui->nodeLabelsChkBox, &QCheckBox::stateChanged, this, &DialogSettings::getNodeLabelsVisibility); connect(ui->nodeLabelSizeSpin, SIGNAL(valueChanged(int)), this, SLOT(getNodeLabelSize(int)) ); connect (ui->nodeLabelColorBtn, &QToolButton::clicked, this, &DialogSettings::getNodeLabelColor); connect(ui->nodeLabelDistanceSpin, SIGNAL(valueChanged(int)), this, SLOT(getNodeLabelDistance(int)) ); connect (ui->edgesChkBox, &QCheckBox::stateChanged, this, &DialogSettings::getEdgesVisibility); connect (ui->edgeArrowsChkBox, &QCheckBox::stateChanged, this, &DialogSettings::getEdgeArrowsVisibility); connect (ui->edgeArrowSizeSpin, QOverload::of(&QSpinBox::valueChanged), this, &DialogSettings::getEdgeArrowSize); connect (ui->edgeColorBtn, &QToolButton::clicked, this, &DialogSettings::getEdgeColor); connect (ui->edgeColorNegativeBtn, &QToolButton::clicked, this, &DialogSettings::getEdgeColorNegative); connect (ui->edgeColorZeroBtn, &QToolButton::clicked, this, &DialogSettings::getEdgeColorZero); connect (ui->edgeShapeRadioStraightLine, &QRadioButton::clicked, this, &DialogSettings::getEdgeShape); connect (ui->edgeShapeRadioBezier, &QRadioButton::clicked, this, &DialogSettings::getEdgeShape); connect(ui->edgeOffsetFromNodeSpin, SIGNAL(valueChanged(int)), this, SLOT(getEdgeOffsetFromNode(int)) ); connect (ui->edgeWeightNumbersChkBox, &QCheckBox::stateChanged, this, &DialogSettings::getEdgeWeightNumbersVisibility); connect (ui->edgeLabelsChkBox, &QCheckBox::stateChanged, this, &DialogSettings::getEdgeLabelsVisibility); connect (ui->saveZeroWeightEdgesChkBox, &QCheckBox::stateChanged, this, &DialogSettings::getSaveZeroWeightEdges); connect (ui->showZeroWeightEdgesChkBox, &QCheckBox::stateChanged, // #30 this, &DialogSettings::getShowZeroWeightEdges); // #30: disable color picker when zero-weight edges are hidden connect (ui->showZeroWeightEdgesChkBox, &QCheckBox::toggled, ui->label_20, &QLabel::setEnabled); connect (ui->showZeroWeightEdgesChkBox, &QCheckBox::toggled, ui->edgeColorZeroBtn, &QToolButton::setEnabled); connect ( ui->buttonBox, &QDialogButtonBox::accepted, this, &DialogSettings::validateSettings ); } /** * @brief DialogSettings::validateSettings * Validates form data and signals saveSettings to MW */ void DialogSettings::validateSettings(){ emit saveSettings(); } void DialogSettings::getDataDir(){ QString m_dataDir = QFileDialog::getExistingDirectory(this, tr("Select a new data dir"), ui->dataDirEdit->text(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (!m_dataDir.isEmpty()) { if (!m_dataDir.endsWith( QDir::separator() )) { m_dataDir += QDir::separator(); } ui->dataDirEdit->setText(m_dataDir); m_appSettings["dataDir"]= m_dataDir; emit setReportsDataDir (m_dataDir); } } /** * @brief Get the real number precision * @param size */ void DialogSettings::getReportsRealNumberPrecision( const int &precision) { m_appSettings["initReportsRealNumberPrecision"]= QString::number(precision); emit setReportsRealNumberPrecision(precision); } /** * @brief Get the real number precision * @param size */ void DialogSettings::getReportsLabelsLength( const int &length) { m_appSettings["initReportsLabelsLength"]= QString::number(length); emit setReportsLabelLength(length); } /** * @brief Gets the chart type in reports */ void DialogSettings::getReportsChartType(const int &type){ //if (!type.isEmpty() ) { qCDebug(lcForms) << "DialogSettings::getReportsChartType() - type: " << type; m_appSettings["initReportsChartType"] = QString::number(type-1); emit setReportsChartType(type-1); //} } /** * @brief Gets the report output format (HTML or CSV) */ void DialogSettings::getReportsOutputFormat(const int &format){ qCDebug(lcForms) << "DialogSettings::getReportsOutputFormat() - format: " << format; m_appSettings["initReportsOutputFormat"] = QString::number(format); emit setReportsOutputFormat(format); } /** * @brief DialogSettings::getCanvasBgColor * Opens a QColorDialog for the user to select a new bg color */ void DialogSettings::getCanvasBgColor(){ m_bgColor = QColorDialog::getColor( m_bgColor, this, tr("Select a background color") ); if ( m_bgColor.isValid()) { m_pixmap.fill(m_bgColor); ui->bgColorButton->setIcon(QIcon(m_pixmap)); ui->bgImageSelectEdit->setText(""); m_appSettings["initBackgroundColor"] = m_bgColor.name(); m_appSettings["initBackgroundImage"] = ""; emit setCanvasBgColor(m_bgColor); } else { // user pressed Cancel } } /** * @brief DialogSettings::getCanvasBgImage */ void DialogSettings::getCanvasBgImage(){ QString m_bgImage = QFileDialog::getOpenFileName( this, tr("Select a background image "), (m_appSettings)["lastUsedDirPath"], tr("All (*);;PNG (*.png);;JPG (*.jpg)") ); if (!m_bgImage.isEmpty() ) { (m_appSettings)["initBackgroundImage"] = m_bgImage ; ui->bgImageSelectEdit->setText((m_appSettings)["initBackgroundImage"]); emit setCanvasBgImage(); } else { //user pressed Cancel } } /** * @brief Gets Canvas Update Mode */ void DialogSettings::getCanvasUpdateMode(const QString &mode){ if (!mode.isEmpty() ) { m_appSettings["canvasUpdateMode"] = mode; emit setCanvasUpdateMode(mode); } } /** * @brief Gets canvas Index Method */ void DialogSettings::getCanvasIndexMethod(const QString &method){ if (!method.isEmpty() ) { m_appSettings["canvasIndexMethod"] = method; emit setCanvasIndexMethod(method); } } /** * @brief DialogSettings::getNodeColor * * Opens a QColorDialog for the user to select a new node color */ void DialogSettings::getNodeColor(){ m_nodeColor = QColorDialog::getColor( m_nodeColor, this, tr("Select a color for Nodes") ); if ( m_nodeColor.isValid()) { m_pixmap.fill(m_nodeColor); ui->nodeColorBtn->setIcon(QIcon(m_pixmap)); (m_appSettings)["initNodeColor"] = m_nodeColor.name(); emit setNodeColor(m_nodeColor); } else { // user pressed Cancel } } /** * @brief Gets the index of the selected shape in the ui::nodeShapeComboBox * If custom shape, it enables and sets the nodeIconSelectEdit/nodeIconSelectButton * Then it emits setNodeShape * @param shape */ void DialogSettings::getNodeShapeIndex(const int &shape){ m_appSettings["initNodeShape"] = m_shapeList[shape]; qCDebug(lcForms)<< "DialogSettings::getNodeShapeIndex() - " "new default shape" << m_shapeList[shape]; if ( shape == NodeShape::Custom ) { // enable textedit and file button and raise file dialog ui->nodeIconSelectButton->setEnabled(true); ui->nodeIconSelectEdit->setEnabled(true); ui->nodeIconSelectEdit->setText (m_appSettings["initNodeIconPath"]); if (!m_appSettings["initNodeIconPath"].isEmpty()) { emit setNodeShape(0, m_appSettings["initNodeShape"], m_appSettings["initNodeIconPath"]); } else { QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui->nodeIconSelectButton->setGraphicsEffect(effect); ui->nodeIconSelectEdit->setGraphicsEffect(effect); (ui->buttonBox)->button (QDialogButtonBox::Cancel)->setDefault(true); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); } } else { ui->nodeIconSelectButton->setEnabled(false); ui->nodeIconSelectEdit->setEnabled(false); ui->nodeIconSelectEdit->setText (""); ui->nodeIconSelectButton->setGraphicsEffect(0); ui->nodeIconSelectEdit->setGraphicsEffect(0); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setDefault(true); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(true); // emit signal // instead of empty iconPath string, we always emit iconPathList[shape] // this is to allow passing the path to built-in icons i.e. hearts. emit setNodeShape(0, m_appSettings["initNodeShape"], m_iconList[shape] ); } } void DialogSettings::getNodeIconFile(){ QString m_nodeIconFile = QFileDialog::getOpenFileName(this, tr("Select a new icon"), ui->nodeIconSelectEdit->text(), tr("Images (*.png *.jpg *.jpeg *.svg);;All (*.*)") ); if (!m_nodeIconFile.isEmpty()) { qCDebug(lcForms) << m_nodeIconFile; ui->nodeIconSelectEdit->setText(m_nodeIconFile); m_appSettings["initNodeIconPath"]= m_nodeIconFile; ui->nodeShapeComboBox->setItemIcon(NodeShape::Custom, QIcon(m_nodeIconFile)); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(true); emit setNodeShape(0, m_appSettings["initNodeShape"], m_appSettings["initNodeIconPath"]); } else { // user pressed Cancel ? // stop if ( ui->nodeIconSelectEdit->text().isEmpty() ) { (ui->buttonBox)->button (QDialogButtonBox::Cancel)->setDefault(true); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); } } } /** * @brief DialogSettings::getNodeSize * @param size */ void DialogSettings::getNodeSize( int size) { m_appSettings["initNodeSize"]= QString::number(size); emit setNodeSize(size, false); } /** * @brief DialogSettings::getNodeNumbersVisibility * @param toggle */ void DialogSettings::getNodeNumbersVisibility (bool toggle){ m_appSettings["initNodeNumbersVisibility"]= (toggle) ? "true" : "false"; emit setNodeNumbersVisibility(toggle); } /** * @brief DialogSettings::getNodeNumbersInside * @param toggle */ void DialogSettings::getNodeNumbersInside(bool toggle) { m_appSettings["initNodeNumbersInside"]= (toggle) ? "true" : "false"; ui->nodeNumbersChkBox->setChecked(true); ui->nodeNumberDistanceSpin->setEnabled(!toggle); ui->nodeNumberSizeSpin->setValue( ( (toggle) ? 0 : 7) ); emit setNodeNumbersInside(toggle); } /** * @brief DialogSettings::getNodeNumberSize * @param size */ void DialogSettings::getNodeNumberSize( const int size) { m_appSettings["initNodeNumberSize"]= QString::number(size); emit setNodeNumberSize(0, size, false); } /** * @brief DialogSettings::getNodeNumberDistance * @param distance */ void DialogSettings::getNodeNumberDistance(const int distance) { m_appSettings["initNodeNumberDistance"]= QString::number(distance); emit setNodeNumberDistance(0, distance); } /** * @brief DialogSettings::getNodeNumberColor * * Opens a QColorDialog for the user to select a new node number color */ void DialogSettings::getNodeNumberColor(){ m_nodeNumberColor = QColorDialog::getColor( m_nodeNumberColor, this, tr("Select color for Node Numbers") ); if ( m_nodeNumberColor.isValid()) { m_pixmap.fill(m_nodeNumberColor); ui->nodeNumberColorBtn->setIcon(QIcon(m_pixmap)); (m_appSettings)["initNodeNumberColor"] = m_nodeNumberColor.name(); emit setNodeNumberColor(0,m_nodeNumberColor); } else { // user pressed Cancel } } /** * @brief DialogSettings::getNodeLabelsVisibility * @param toggle */ void DialogSettings::getNodeLabelsVisibility (bool toggle){ m_appSettings["initNodeLabelsVisibility"]= (toggle) ? "true" : "false"; emit setNodeLabelsVisibility(toggle); } /** * @brief DialogSettings::getNodeLabelColor * * Opens a QColorDialog for the user to select a new node Label color */ void DialogSettings::getNodeLabelColor(){ m_nodeLabelColor = QColorDialog::getColor( m_nodeLabelColor, this, tr("Select color for Node Labels") ); if ( m_nodeLabelColor.isValid()) { m_pixmap.fill(m_nodeLabelColor); ui->nodeLabelColorBtn->setIcon(QIcon(m_pixmap)); (m_appSettings)["initNodeLabelColor"] = m_nodeLabelColor.name(); emit setNodeLabelColor(m_nodeLabelColor); } else { // user pressed Cancel } } /** * @brief DialogSettings::getNodeLabelSize * @param size */ void DialogSettings::getNodeLabelSize( const int size) { m_appSettings["initNodeLabelSize"]= QString::number(size); emit setNodeLabelSize(0, size); } /** * @brief DialogSettings::getNodeLabelDistance * @param distance */ void DialogSettings::getNodeLabelDistance(const int distance) { m_appSettings["initNodeLabelDistance"]= QString::number(distance); emit setNodeLabelDistance(0, distance); } /** * @brief DialogSettings::getEdgesVisibility * @param toggle */ void DialogSettings::getEdgesVisibility (const bool &toggle){ m_appSettings["initEdgesVisibility"]= (toggle) ? "true" : "false"; emit setEdgesVisibility(toggle); } /** * @brief DialogSettings::getEdgeArrowsVisibility * @param toggle */ void DialogSettings::getEdgeArrowsVisibility(const bool &toggle){ m_appSettings["initEdgeArrows"]= (toggle) ? "true" : "false"; emit setEdgeArrowsVisibility(toggle); } /** * @brief Saves the new arrow size to settings and notifies the canvas. * @param size New arrow size in pixels (2–20). */ void DialogSettings::getEdgeArrowSize(const int size){ m_appSettings["initEdgeArrowSize"] = QString::number(size); emit setEdgeArrowSize(size); } /** * @brief DialogSettings::getEdgeColor * * Opens a QColorDialog for the user to select a new edge color */ void DialogSettings::getEdgeColor(){ m_edgeColor = QColorDialog::getColor( m_edgeColor, this, tr("Select color for Edges ") ); if ( m_edgeColor.isValid()) { m_pixmap.fill(m_edgeColor); ui->edgeColorBtn->setIcon(QIcon(m_pixmap)); m_appSettings["initEdgeColor"] = m_edgeColor.name(); emit setEdgeColor(m_edgeColor, RAND_MAX); } else { // user pressed Cancel } } /** * @brief DialogSettings::getEdgeColorNegative * * Opens a QColorDialog for the user to select a new negative edge color */ void DialogSettings::getEdgeColorNegative(){ m_edgeColorNegative = QColorDialog::getColor( m_edgeColorNegative, this, tr("Select color for negative Edges") ); if ( m_edgeColorNegative.isValid()) { m_pixmap.fill(m_edgeColorNegative); ui->edgeColorNegativeBtn->setIcon(QIcon(m_pixmap)); m_appSettings["initEdgeColorNegative"] = m_edgeColorNegative.name(); emit setEdgeColor(m_edgeColorNegative, -1); } else { // user pressed Cancel } } /** * @brief DialogSettings::getEdgeColorZero * * Opens a QColorDialog for the user to select a new zero edge color */ void DialogSettings::getEdgeColorZero(){ m_edgeColorZero = QColorDialog::getColor( m_edgeColorZero, this, tr("Select color for negative Edges") ); if ( m_edgeColorZero.isValid()) { m_pixmap.fill(m_edgeColorZero); ui->edgeColorZeroBtn->setIcon(QIcon(m_pixmap)); m_appSettings["initEdgeColorZero"] = m_edgeColorZero.name(); emit setEdgeColor(m_edgeColorZero, 0); } else { // user pressed Cancel } } /** * @brief DialogSettings::getEdgeShape */ void DialogSettings::getEdgeShape(){ if ( ui->edgeShapeRadioStraightLine->isChecked () ){ m_appSettings["initEdgeShape"] = "line"; } else if ( ui->edgeShapeRadioBezier->isChecked() ){ m_appSettings["initEdgeShape"] = "bezier"; } qCDebug(lcForms)<< "DialogSettings::getEdgeShape() - new default shape " << m_appSettings["initEdgeShape"]; emit setEdgesBezier(m_appSettings["initEdgeShape"] == "bezier"); } /** * @brief Changes the edge offset from source and target nodes * @param size */ void DialogSettings::getEdgeOffsetFromNode( int offset) { qCDebug(lcForms)<< "DialogSettings::getEdgeOffsetFromNode() - new offset:" << offset; m_appSettings["initEdgeOffsetFromNode"]= QString::number(offset); emit setEdgeOffsetFromNode(offset); } /** * @brief DialogSettings::getEdgeWeightNumbersVisibility * @param toggle */ void DialogSettings::getEdgeWeightNumbersVisibility(const bool &toggle){ m_appSettings["initEdgeWeightNumbersVisibility"]= (toggle) ? "true" : "false"; emit setEdgeWeightNumbersVisibility(toggle); } /** * @brief DialogSettings::getEdgeLabelsVisibility * @param toggle */ void DialogSettings::getEdgeLabelsVisibility(const bool &toggle){ m_appSettings["initEdgeLabelsVisibility"]= (toggle) ? "true" : "false"; emit setEdgeLabelsVisibility(toggle); } /** * @brief Gets the value of saveZeroWeightEdgesChkBox * @param toggle */ void DialogSettings::getSaveZeroWeightEdges(const bool &toggle){ m_appSettings["saveZeroWeightEdges"]= (toggle) ? "true" : "false"; emit setSaveZeroWeightEdges(toggle); } /** * @brief Gets the value of showZeroWeightEdgesChkBox (#30) * @param toggle */ void DialogSettings::getShowZeroWeightEdges(const bool &toggle){ m_appSettings["showZeroWeightEdges"]= (toggle) ? "true" : "false"; emit setShowZeroWeightEdges(toggle); } DialogSettings::~DialogSettings() { delete ui; } SocNetV-3.7/src/forms/dialogsettings.h000077500000000000000000000117761524402210300200220ustar00rootroot00000000000000/** * @file dialogsettings.h * @brief Declares the DialogSettings class for managing application-wide user preferences in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGSETTINGS_H #define DIALOGSETTINGS_H #include #include namespace Ui { class DialogSettings; } class DialogSettings : public QDialog { Q_OBJECT public: explicit DialogSettings(QMap &appSettings, const QStringList &nodeShapeList, const QStringList &iconPathList, QWidget *parent = Q_NULLPTR ); ~DialogSettings(); public slots: void getDataDir(); void getReportsRealNumberPrecision(const int &precision); void getReportsLabelsLength(const int &length); void getReportsChartType(const int &type); void getReportsOutputFormat(const int &format); void getCanvasBgColor(); void getCanvasBgImage(); void getCanvasUpdateMode(const QString &text); void getCanvasIndexMethod(const QString &text); void validateSettings(); void getNodeColor(); void getNodeShapeIndex(const int &shape); void getNodeIconFile(); void getNodeSize(int); void getNodeNumbersVisibility (bool toggle); void getNodeNumbersInside(bool toggle); void getNodeNumberColor(); void getNodeNumberSize(const int); void getNodeNumberDistance(const int); void getNodeLabelsVisibility (bool toggle); void getNodeLabelColor(); void getNodeLabelSize(const int); void getNodeLabelDistance(const int); void getEdgesVisibility (const bool &toggle); void getEdgeArrowsVisibility (const bool &toggle); void getEdgeArrowSize(const int size); void getEdgeColor(); void getEdgeColorNegative(); void getEdgeColorZero(); void getEdgeShape(); void getEdgeOffsetFromNode( int offset); void getEdgeWeightNumbersVisibility(const bool &toggle); void getEdgeLabelsVisibility(const bool &toggle); void getSaveZeroWeightEdges(const bool &toggle); void getShowZeroWeightEdges(const bool &toggle); // #30 signals: void setReportsDataDir (const QString &dir); void setReportsRealNumberPrecision(const int &precision); void setReportsLabelLength(const int &length); void setReportsChartType(const int &type); void setReportsOutputFormat(const int &format); void setCustomStylesheet(const bool &toggle); void setProgressDialog(bool); void setToolBar(bool); void setStatusBar(bool); void setPrintLogo(bool); void setDebugMsgs(bool); void setRightPanel(bool); void setLeftPanel(bool); void setCanvasBgColor(const QColor); void setCanvasBgImage(); void setCanvasOpenGL(const bool &); void setCanvasAntialiasing(const bool &); void setCanvasAntialiasingAutoAdjust(const bool &); void setCanvasSmoothPixmapTransform(bool); void setCanvasSavePainterState(bool); void setCanvasCacheBackground(bool); void setCanvasEdgeHighlighting(bool); void setCanvasUpdateMode(const QString &text); void setCanvasIndexMethod(const QString &text); void setNodeColor(QColor); void setNodeShape(const int &num, QString , QString nodeIconPath=QString()); void setNodeSize(int, const bool &); void setNodeNumbersVisibility(bool); void setNodeNumbersInside(bool); void setNodeNumberSize(const int v, const int &size, const bool prompt); void setNodeNumberDistance(const int v, const int &); void setNodeNumberColor(const int &v, const QColor); void setNodeLabelsVisibility(const bool &); void setNodeLabelColor(const QColor); void setNodeLabelSize(const int v, const int &); void setNodeLabelDistance(const int v, const int &); void setEdgesVisibility (const bool &toggle); void setEdgeArrowsVisibility (const bool &toggle); void setEdgeArrowSize(const int &size); void setEdgeColor(const QColor, const int &); void setEdgesBezier(const bool); void setEdgeOffsetFromNode(const int&offset, const int &v1=0, const int &v2=0); void setEdgeWeightNumbersVisibility(const bool &toggle); void setEdgeLabelsVisibility(const bool &toggle); void setSaveZeroWeightEdges(const bool &toggle); void setShowZeroWeightEdges(const bool &toggle); // #30 void saveSettings(); private: QMap &m_appSettings ; QPixmap m_pixmap; //QString m_nodeShape; QColor m_bgColor, m_nodeColor, m_nodeNumberColor, m_nodeLabelColor; QColor m_edgeColor, m_edgeColorNegative,m_edgeColorZero, m_edgeWeightNumberColor; QStringList m_shapeList; QStringList m_iconList; Ui::DialogSettings *ui; }; #endif SocNetV-3.7/src/forms/dialogsettings.ui000066400000000000000000004730161524402210300202040ustar00rootroot00000000000000 DialogSettings 0 0 528 737 500 700 800 800 Settings & Preferences Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok false 0 0 0 General 430 0 Data Exporting <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>Click the small button on the far right corner to select a folder, where all SocNetV files and reports will be saved by default. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> Save folder Qt::Orientation::Horizontal 48 20 250 0 <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>This is the path of the folder where all SocNetV files and reports will be saved. </p><p>Click the button on the right to select a new folder. </p><p>If the folder does not exist, it wil be created.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>This is the path of the folder where all SocNetV files and reports will be saved. </p><p>Click the button on the right to select a new folder. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the folder does not exist, it wil be created.</p></body></html> 60 0 PointingHandCursor <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>Click this button to select a folder, where all SocNetV files and reports will be saved by default. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>Click this button to select a folder, where all SocNetV files and reports will be saved by default. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the folder does not exist, it wil be created.</p></body></html> ... Qt::Orientation::Vertical 470 26 470 150 Reports 100 0 200 16777215 <html><head/><body><p><span style=" font-weight:600;">Label Length</span></p><p>Change the length of labels <span style=" font-style:italic;">in reports</span>. Use the spin box to the right to change the number of digits SocNetV should write when generating reports with labels, i.e. node labels in centrality reports.</p><p>The length cannot be a negative value. The default value is 10.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> Labels length Qt::Orientation::Horizontal 40 30 60 0 60 16777215 SizeVerCursor <html><head/><body><p><span style=" font-weight:600;">Label Length</span></p><p>Sets the length of labels <span style=" font-style:italic;">in reports</span>. This value describes the number of digits SocNetV should write when generating reports with labels, i.e. node labels in centrality reports.</p><p>The length cannot be a negative value. The default value is 8.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> 6 100 0 200 16777215 <html><head/><body><p><span style=" font-weight:600;">Real Number Precision</span></p><p>Change the precision of real numbers <span style=" font-style:italic;">in reports</span>. Use the spin box on the right to select a new precision namely the number of fraction digits SocNetV should write when generating reports with real numbers, i.e. centrality reports..</p><p>The precision cannot be a negative value. The default value is 6.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> Real number precision Qt::Orientation::Horizontal 40 30 60 0 60 16777215 SizeVerCursor <html><head/><body><p><span style=" font-weight:600;">Real Number Precision</span></p><p>Sets the precision of real numbers <span style=" font-style:italic;">in reports</span>. This value describes the number of fraction digits SocNetV should write when generating reports with real numbers, i.e. centrality reports..</p><p>The precision cannot be a negative value. The default value is 6.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> 8 <html><head/><body><p><span style=" font-family:'Sans Serif'; font-size:9pt; font-weight:600;">Chart Type</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">This is the chart type that is used in reports, i.e. to plot the distribution of a prominence index, such as Degree Centrality.</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">Use the select box on the right to select a chart type.</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">SocNetV supports the following chart types:</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Spline charts, which present data as a series of data points connected by lines. </span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Area charts, which present data as an area bound by two lines.</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Bar charts, which present data as vertical bars. </span></p></body></html> Chart type Qt::Orientation::Horizontal 98 20 0 0 170 0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; font-weight:600;">Chart Type</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">Select which kind of chart type will be used in reports, i.e. to plot the </span><span style=" font-family:'DejaVu Sans'; font-size:10pt;">distribution of a </span><span style=" font-family:'DejaVu Sans'; font-size:10pt;">prominence index, such as Degree Centrality.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'DejaVu Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">SocNetV supports the following chart types:</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Spline charts, which present data as a series of data points connected by lines. </span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Area charts, which present data as an area bound by two lines.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Bar charts, which present data as vertical bars. </span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Output Format</span></p><p>Select whether new reports (i.e. Analyze &gt; ...) are written as a full HTML document, or a lean CSV table for opening in a spreadsheet application.</p><p>CSV is not offered for every report - narrative/single-value reports (e.g. Connectedness) and the visual adjacency plot are always HTML, since a CSV file has no meaningful equivalent for them.</p></body></html> Output format Qt::Orientation::Horizontal 98 20 0 0 170 0 <html><head/><body><p><span style=" font-weight:600;">Output Format</span></p><p>Select whether new reports are written as a full HTML document (default), or a lean CSV table.</p></body></html> Qt::Orientation::Vertical 388 23 470 0 Image Exporting <html><head/><body><p><span style=" font-weight:600;">SocNetV logo on exported images</span></p><p>Enable or disable the printing of a small SocNetV logo on exported PNG and BMP network images </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> <html><head/><body><p><span style=" font-weight:600;">SocNetV logo on exported images</span></p><p>Enable or disable the printing of a small SocNetV logo on exported PNG and BMP network images </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Qt::LayoutDirection::LeftToRight Print SocNetV logo on exported Images true Qt::Orientation::Vertical 17 15 470 0 Debugging and Progressing <html><head/><body><p><span style=" font-weight:600;">Debug Messages</span></p><p>Enable or disable debug messages to strerr. </p><p>Once you enable this and press OK, you must close and run SocNetV again from the command line / terminal, in order to see the debug messages.</p><p>Enabling has a significant cpu cost but lets you know what SocNetV is actually doing.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Debug Messages</span></p><p>Enables or disable debug messages to strerr. </p><p>Enabling has a significant cpu cost but lets you know what SocNetV is actually doing.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Qt::LayoutDirection::LeftToRight Print debug messages to command line <html><head/><body><p><span style=" font-weight:600;">Progress Bars</span></p><p>Enable or disable the application Progress Bars.</p><p>Progress Bars may appear during time-cost operations. </p><p>Enabling Progress Bars has a significant cpu cost but lets you know about the progress of a given operation.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Progress Bars</span></p><p>Enable or disable the application Progress Bars.</p><p>Progress Bars may appear during time-cost operations. </p><p>Enabling Progress Bars has a significant cpu cost but lets you know about the progress of a given operation.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Qt::LayoutDirection::LeftToRight Show progress dialog true Qt::Orientation::Vertical 17 20 0 0 470 0 Application Style <html><head/><body><p><span style=" font-weight:600;">Debug Messages</span></p><p>Enable or disable debug messages to strerr. </p><p>Once you enable this and press OK, you must close and run SocNetV again from the command line / terminal, in order to see the debug messages.</p><p>Enabling has a significant cpu cost but lets you know what SocNetV is actually doing.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Debug Messages</span></p><p>Enables or disable debug messages to strerr. </p><p>Enabling has a significant cpu cost but lets you know what SocNetV is actually doing.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Qt::LayoutDirection::LeftToRight Use SocNetV Stylesheet Qt::Orientation::Vertical 20 20 470 110 Window options 8 30 551 67 <html><head/><body><p><span style=" font-weight:600;">Control panel</span></p><p>Enable or disable the Control panel (left panel)</p><p>The Control Panel is the widget at the left of the application window, where you can find essential functions and options for Editing, Analyzing and Visualizing your network data.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> <html><head/><body><p><span style=" font-weight:600;">Control panel</span></p><p>Enable or disable the Control panel (left panel)</p><p>The Control Panel is the widget at the left of the application window, where you can find essential functions and options for Editing, Analyzing and Visualizing your network data.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Qt::LayoutDirection::LeftToRight Show Control panel (left panel) true <html><head/><body><p><span style=" font-weight:600;">Toolbar</span></p><p>Enable or disable the application toolbar.</p><p>The toolbar is the widget right below the menu, and carries useful icons. You can disable it if you like from here...</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> <html><head/><body><p><span style=" font-weight:600;">Toolbar</span></p><p>Enable or disable the application toolbar.</p><p>The toolbar is the widget right below the menu, and carries useful icons. You can disable it if you like from here...</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Qt::LayoutDirection::LeftToRight Show toolbar true <html><head/><body><p><span style=" font-weight:600;">Statistics panel</span></p><p>Enable or disable the statistics panel (right panel)</p><p>The Statistics panel is the widget at the right of the application window, where you can see statistics about the whole network such as node and edge/arc count, density etc as well as statistics about the last clicked node (in-degree, out-degree, clustering coefficient etc).</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> <html><head/><body><p><span style=" font-weight:600;">Statistics panel</span></p><p>Enable or disable the statistics panel (right panel)</p><p>The Statistics panel is the widget at the right of the application window, where you can see statistics about the whole network such as node and edge/arc count, density etc as well as statistics about the last clicked node (in-degree, out-degree, clustering coefficient etc).</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Qt::LayoutDirection::LeftToRight Show Statistics panel (right panel) true <html><head/><body><p><span style=" font-weight:600;">Statusbar</span></p><p>Enable or disable the application statusbar.</p><p>The statusbar is the widget at the bottom of the window, where messages appear. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> <html><head/><body><p><span style=" font-weight:600;">Statusbar</span></p><p>Enable or disable the application statusbar.</p><p>The statusbar is the widget at the bottom of the window, where messages appear. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Qt::LayoutDirection::LeftToRight Show statusbar true Nodes <html><head/><body><p><span style=" font-weight:600;">Default node settings</span></p><p>Any change to these settings will apply to all existing nodes immediately.</p><p>Once you press OK, these settings will be saved and they will be used in all future SocNetV sessions.</p></body></html> Node settings <html><head/><body><p><span style=" font-weight:600;">Node color</span></p><p>Click the colored button to select a new color for all nodes.</p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node color when creating new nodes (except when loading network files which declare specific node settings).</p></body></html> Node color Qt::Orientation::Horizontal 40 20 60 25 PointingHandCursor <html><head/><body><p><span style=" font-weight:600;">Node color</span></p><p>Click this button to select a new node color for all nodes.</p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node color when creating new nodes (except when loading network files which declare specific node settings).</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node color</span></p><p>Click to select a new default node color.</p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node color.</p></body></html> ... 60 20 <html><head/><body><p><span style=" font-weight:600;">Node shape</span></p><p>Click on any of the following shapes to select a new node shape for all nodes. </p><p>This will apply to all existing nodes immediately.</p><p>Once you press OK, the default node shape will be saved and it will be used in all future SocNetV sessions when creating new nodes (except when loading network files which declare specific shapes for nodes).</p></body></html> Node shape Qt::Orientation::Horizontal 40 20 200 0 <html><head/><body><p>Select a node shape. </p><p>If you select &quot;Icon&quot; then you must click on the custom icon button (below) to select the desired icon file from your filesystem. </p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click the button on the right to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> Custom Icon Qt::Orientation::Horizontal 30 28 200 0 <html><head/><body><p><span style=" font-weight:600;">Custom Icon </span></p><p>This box only shows the path of the selected custom icon file for all network nodes. If it is empty, it means you have not selected any image yet.</p><p>Click the button on the right to select a new image to be used as custom icon in all the nodes of the network.</p><p>Valid icons are images: JPG, PNG, JPEG, SVG etc.</p><p><br/></p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>This is the path of the default background image. </p><p>Click the button on the right to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> 0 0 60 0 <html><head/><body><p><span style=" font-weight:600;">Custom Icon </span></p><p>Click this button to select a new custom icon for all nodes of the network. </p><p>Valid icons are images: JPG, PNG, JPEG, SVG etc.</p><p>If you don't select an image file, you must select one of the default shapes from the Node Shape menu above. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click this button to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> ... <html><head/><body><p><span style=" font-weight:600;">Node size</span></p><p>Use the spin box to select a new size (in pixels) for all nodes. </p><p>Actual node size will vary according to selected shape. For instance, if the selected shape is circle and the selected size is 8, then the circle will have a diameter of 16 pixels. </p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node size when creating new nodes (except when loading network files which declare specific node settings).</p></body></html> Node size Qt::Orientation::Horizontal 40 20 60 0 SizeVerCursor <html><head/><body><p><span style=" font-weight:600;">Node size</span></p><p>Select a new size for all nodes. Actual node size will vary according to selected shape. For instance, if the selected shape is circle and the size is 8, then the circle will have a diameter of 16 pixels. </p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node size.</p></body></html> 1 8 Node Number settings <html><head/><body><p><span style=" font-weight:600;">Node number color</span></p><p>Change the color for all node numbers. Click the colored button on the right corner to select a new color.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number color when creating new nodes.</p></body></html> Number color Qt::Orientation::Horizontal 40 20 60 25 PointingHandCursor <html><head/><body><p><span style=" font-weight:600;">Node number color</span></p><p>Click to select a new color for all node numbers.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number color when creating new nodes.</p></body></html> ... 60 20 <html><head/><body><p><span style=" font-weight:600;">Number distance from node</span></p><p>Change the distance of each number from the respective node. Use the spin box on the right to select a new distance (in pixels).</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default distance from node.</p></body></html> Number distance Qt::Orientation::Horizontal 40 20 60 0 SizeVerCursor <html><head/><body><p><span style=" font-weight:600;">Number distance from nodes</span></p><p>Select a new distance (in pixels) of numbers from the respective nodes.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default distance from node.</p></body></html> 1 8 <html><head/><body><p><span style=" font-weight:600;">Node numbers</span></p><p>Enable or disable displaying node numbers.</p><p>Any change will apply to all existing nodes immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node numbers</span></p><p>Enable or disable displaying node numbers.</p><p>Any change will apply to all existing nodes immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> Qt::LayoutDirection::LeftToRight Display node numbers <html><head/><body><p><span style=" font-weight:600;">Node number font size</span></p><p>Change the font size (in pixels) of all node numbers. Use the spin box on the right to select a new font size (in pixels).</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> Number font size Qt::Orientation::Horizontal 40 20 60 0 SizeVerCursor <html><head/><body><p><span style=" font-weight:600;">Node number size</span></p><p>Select a new font size (in pixels) for node numbers. Set it to 0 to let the program automagically select a different font size according to the node size.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node number size</span></p><p>Select a new font size (in pixels) for node numbers. Set it to 0 to let the program automagically select a different font size according to the node size.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> 8 <html><head/><body><p><span style=" font-weight:600;">Node numbers inside shapes</span></p><p>Enable or disable displaying node numbers inside the node shapes</p><p>Any change will apply to all existing nodes immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node numbers inside shapes</span></p><p>Enable or disable displaying node numbers inside the node shapes</p><p>Any change will apply to all existing nodes immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> Qt::LayoutDirection::LeftToRight Display node numbers inside node shapes Node Label settings <html><head/><body><p><span style=" font-weight:600;">Node labels</span></p><p>Enable or disable displaying labels below the nodes.</p><p>Any change will apply to all existing nodes immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node labels</span></p><p>Enable or disable displaying labels below the nodes.</p><p>Any change will apply to all existing nodes immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> Qt::LayoutDirection::LeftToRight Display node labels Label color Qt::Orientation::Horizontal 40 20 60 25 PointingHandCursor <html><head/><body><p><span style=" font-weight:600;">Node label color</span></p><p>Click to select a new color for node labels.</p><p>Any change to this setting will apply to all existing node labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node label color.</p></body></html> ... 60 20 Label font size Qt::Orientation::Horizontal 40 20 60 0 SizeVerCursor <html><head/><body><p><span style=" font-weight:600;">Node label size</span></p><p>Select a new font size (in pixels) for all node labels.</p><p>Any change to this setting will apply to all existing node labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node label size.</p></body></html> 1 8 Label distance Qt::Orientation::Horizontal 40 20 60 0 SizeVerCursor <html><head/><body><p><span style=" font-weight:600;">Label distance from node</span></p><p>Change the distance of labels from the respective nodes.</p><p>Any change to this setting will apply to all existing node labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default label distance from node.</p></body></html> 8 labelGroup numbersGroup nodesGroup Edges Edge settings 260 0 <html><head/><body><p><span style=" font-weight:600;">Edges </span></p><p>Enable or disable displaying edges.</p><p>Any change will apply to all existing edges immediately.</p><p><br/></p></body></html> Qt::LayoutDirection::LeftToRight Display edges <html><head/><body><p><span style=" font-weight:600;">Edges </span></p><p>Enable or disable displaying arrows in directed edges. Useful If you work with directional social network data. You can disable it if your network is undirected.</p><p>Any change will apply to all existing edges immediately and saved for all future sessions (until you change it again).</p><p><br/></p></body></html> Qt::LayoutDirection::LeftToRight Display edge arrows <html><head/><body><p><span style=" font-weight:600;">Arrow size</span></p><p>Controls the size of the arrowheads drawn on directed edges. A larger value produces bigger arrowheads.</p><p>Any change will apply to all existing edges immediately and saved for future sessions.</p></body></html> Arrow size Qt::Orientation::Horizontal 40 20 60 0 SizeVerCursor <html><head/><body><p><span style=" font-weight:600;">Arrow size</span></p><p>Controls the size of the arrowheads on directed edges.</p><p>Any change will apply to all existing edges immediately and saved for future sessions.</p></body></html> 2 20 6 <html><head/><body><p><span style=" font-weight:600;">Default edge shape</span></p><p>Select the default edge shape for all edges. </p><p>Edges can be either straight lines (default) or bezier curves.</p><p>This will apply to all existing edgesimmediately.</p><p>Once you press OK, the default edge shape will be saved and it will be used in all future SocNetV sessions.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default edge shape</span></p><p>Select the default edge shape for all edges. </p><p>Edges can be either straight lines (default) or bezier curves.</p><p>This will apply to all existing edgesimmediately.</p><p>Once you press OK, the default edge shape will be saved and it will be used in all future SocNetV sessions.</p></body></html> Edge shape Qt::Orientation::Horizontal 40 20 <html><head/><body><p><span style=" font-weight:600;">Default edge shape</span></p><p>Select the default edge shape for all edges. </p><p>Edges can be either straight lines (default) or bezier curves.</p><p>This will apply to all existing edgesimmediately.</p><p>Once you press OK, the default edge shape will be saved and it will be used in all future SocNetV sessions.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default edge shape</span></p><p>Select the default edge shape for all edges. </p><p>Edges can be either straight lines (default) or bezier curves.</p><p>This will apply to all existing edgesimmediately.</p><p>Once you press OK, the default edge shape will be saved and it will be used in all future SocNetV sessions.</p></body></html> Straight Lines <html><head/><body><p><span style=" font-weight:600;">Default edge shape</span></p><p>Select the default edge shape for all edges. </p><p>Edges can be either straight lines (default) or bezier curves.</p><p>This will apply to all existing edgesimmediately.</p><p>Once you press OK, the default edge shape will be saved and it will be used in all future SocNetV sessions.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default edge shape</span></p><p>Select the default edge shape for all edges. </p><p>Edges can be either straight lines (default) or bezier curves.</p><p>This will apply to all existing edgesimmediately.</p><p>Once you press OK, the default edge shape will be saved and it will be used in all future SocNetV sessions.</p></body></html> Be&zier Curves <html><head/><body><p><span style=" font-weight:600;">Edge color</span></p><p>Click the colored button on the right to select a new color for all edges.</p><p>Any change to this setting will apply to all existing edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color. Until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Edge color</span></p><p>Click the colored button on the right to select a new color for all edges.</p><p>Any change to this setting will apply to all existing edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color. Until you change it again.</p></body></html> Default edge color Qt::Orientation::Horizontal 40 20 60 25 PointingHandCursor <html><head/><body><p><span style=" font-weight:600;">Edge color</span></p><p>Click to select a new color for all edges.</p><p>Any change to this setting will apply to all existing edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color. Until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Edge color</span></p><p>Click to select a new color for all edges.</p><p>Any change to this setting will apply to all existing edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color. Until you change it again.</p></body></html> ... 60 20 <html><head/><body><p><span style=" font-weight:600;">Negative edge color</span></p><p>Click the colored button at the right corner to select a new color for negative weighted edges.</p><p>Any change to this setting will apply to all existing &quot;negative&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;negative&quot; edges. Until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Negative edge color</span></p><p>Click the colored button at the right corner to select a new color for negative weighted edges.</p><p>Any change to this setting will apply to all existing &quot;negative&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;negative&quot; edges. Until you change it again.</p></body></html> Negative valued edge color Qt::Orientation::Horizontal 40 20 60 25 PointingHandCursor <html><head/><body><p><span style=" font-weight:600;">Negative edge color</span></p><p>Click this button to select a new default edge color for all negative weighted edges.</p><p>Any change to this setting will apply to all existing &quot;negative&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default color for &quot;negative&quot; edges. Until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Negative edge color</span></p><p>Click this button to select a new default edge color for all negative weighted edges.</p><p>Any change to this setting will apply to all existing &quot;negative&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default color for &quot;negative&quot; edges. Until you change it again.</p></body></html> ... 60 20 <html><head/><body><p><span style=" font-weight:600;">Show zero-weight edges</span></p><p>When checked, edges with weight 0 are drawn on the canvas using the zero-valued edge color below. When unchecked, they are silently skipped on load β€” no ghost edges appear.</p><p>Zero-weight edges are never included in any SNA computation regardless of this setting.</p></body></html> Show zero-weight edges <html><head/><body><p><span style=" font-weight:600;">Zero edge color</span></p><p>Click the colored button at the right corner to select a new color for edges with zero weights.</p><p>ATTTENTION: Zero-valued edges are being omittted during network analysis computations. This setting is available for those that have data (i.e. weighted edge lists) with zero weights on some edges and they still need to draw those edges, despite the fact that they will not be considered in any SNA computation by SocNetV.</p><p>Any change to this setting will apply to all existing &quot;zero&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;zero&quot; edges. Until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Zero edge color</span></p><p>Click the colored button at the right corner to select a new color for edges with zero weights.</p><p>ATTTENTION: Zero-valued edges are being omittted during network analysis computations. This setting is available for those that have data (i.e. weighted edge lists) with zero weights on some edges and they still need to draw those edges, despite the fact that they will not be considered in any SNA computation by SocNetV.</p><p>Any change to this setting will apply to all existing &quot;zero&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;zero&quot; edges. Until you change it again.</p></body></html> Zero valued edge color Qt::Orientation::Horizontal 40 20 60 25 PointingHandCursor <html><head/><body><p><span style=" font-weight:600;">Zero edge color</span></p><p>Click this button to select a new color for edges with zero weights.</p><p>ATTTENTION: Zero-valued edges are being omittted during network analysis computations. This setting is available for those that have data (i.e. weighted edge lists) with zero weights on some edges and they still need to draw those edges, despite the fact that they will not be considered in any SNA computation by SocNetV.</p><p>Any change to this setting will apply to all existing &quot;zero&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;zero&quot; edges. Until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Zero edge color</span></p><p>Click this button to select a new color for edges with zero weights.</p><p>ATTTENTION: Zero-valued edges are being omittted during network analysis computations. This setting is available for those that have data (i.e. weighted edge lists) with zero weights on some edges and they still need to draw those edges, despite the fact that they will not be considered in any SNA computation by SocNetV.</p><p>Any change to this setting will apply to all existing &quot;zero&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;zero&quot; edges. Until you change it again.</p></body></html> ... 60 20 Edge offset from node Qt::Orientation::Horizontal 40 20 60 0 SizeVerCursor <html><head/><body><p><span style=" font-weight:600;">Edge offset from node </span></p><p>Changes the offset of each edge from each source and target nodes. rs.</p><p>Any change to this setting will apply to all existing edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions when creating new edges.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Edge offset from node </span></p><p>Changes the offset of each edge from each source and target nodes. rs.</p><p>Any change to this setting will apply to all existing edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions when creating new edges.</p></body></html> 1 8 6 Weight number settings <html><head/><body><p><span style=" font-weight:600;">Edge weight numbers</span></p><p>Enable or disable edge weight numbers. When enabled, a number will be displayed along every edge indicating the edge weight.</p><p>Any change to this setting will apply to all existing weight numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default when creating new edges.</p></body></html> Qt::LayoutDirection::LeftToRight Display edge weight numbers Weight number color Qt::Orientation::Horizontal 40 20 60 25 PointingHandCursor <html><head/><body><p><span style=" font-weight:600;">Edge weight number color</span></p><p>Click to select a new color for all edge weight numbers.</p><p>Any change to this setting will apply to all existing weight numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge weight number color when creating new edges.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Edge weight number color</span></p><p>Click to select a new color for all edge weight numbers.</p><p>Any change to this setting will apply to all existing weight numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge weight number color when creating new edges.</p></body></html> ... 60 20 Weight number font size Qt::Orientation::Horizontal 40 20 60 0 SizeVerCursor <html><head/><body><p><span style=" font-weight:600;">Edge weight number text size</span></p><p>Click to select a new text size (in pixels) for all edge weight numbers.</p><p>Any change to this setting will apply to all existing weight numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge weight number size when creating new edges.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Edge weight number text size</span></p><p>Click to select a new text size (in pixels) for all edge weight numbers.</p><p>Any change to this setting will apply to all existing weight numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge weight number size when creating new edges.</p></body></html> 1 8 Edge label settings <html><head/><body><p><span style=" font-weight:600;">Edge labels</span></p><p>Enable or disable displaying edge labels.</p><p>Any change will apply to all existing edges immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> Qt::LayoutDirection::LeftToRight Display edge labels Label color Qt::Orientation::Horizontal 40 20 false 60 25 PointingHandCursor <html><head/><body><p><span style=" font-weight:600;">Default edge label color</span></p><p>Click to select a new default color for edge labels.</p><p>Any change to this setting will apply to all existing edge labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge label color.</p></body></html> ... 60 20 Label font size Qt::Orientation::Horizontal 40 20 false 60 0 SizeVerCursor <html><head/><body><p><span style=" font-weight:600;">Default edge label size</span></p><p>Select the default font size for all edge labels.</p><p>Any change to this setting will apply to all existing edge labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge label size.</p></body></html> 8 Canvas 0 0 0 150 <html><head/><body><p><span style=" font-weight:600;">Canvas Background</span></p><p>In this section, there are general settings for the canvas, such as the background color or image.</p><p><br/></p><p><br/></p><p><br/></p></body></html> Canvas Background <html><head/><body><p><span style=" font-weight:600;">Background color</span></p><p>Click the button on the right to select a new background color. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> Background color Qt::Orientation::Horizontal 40 20 0 0 60 0 PointingHandCursor <html><head/><body><p><span style=" font-weight:600;">Background color</span></p><p>Click this button to select a new background color. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background color</span></p><p>Click this button to select a new background color. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> ... 60 20 <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click the button on the right to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> Background Image Qt::Orientation::Horizontal 30 28 200 0 <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>This is the path of the default background image. </p><p>Click the button on the right to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>This is the path of the default background image. </p><p>Click the button on the right to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> 0 0 60 0 <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click this button to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click this button to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> ... 0 80 <html><head/><body><p><span style=" font-weight:600;">Performance Settings</span></p><p>The following options influence the performance of the SocNetV canvas, inside which nodes and edges are being drawn. They affect the look of nodes and edges as well the precision and speed of the graphics engine.</p><p>Most of these settings have noticeable effects only in large networks. For instance, if you have a network with over 3000 actors and 10000 edges you might want to disable Antialiasing, Antialiasing Auto-adjustment and Smooth Pixmap Transformation while enabling Cache Background. </p><p>Also, if you need to visually interact with edges in a large network, you can experiment with the Update Mode setting to find which is suitable for your workload. The default setting &quot;Full&quot; means that the canvas is fully redrawn every time you make a small change.</p></body></html> Hardware Acceleration <html><head/><body><p><span style=" font-weight:600;">Edge highlighting</span></p><p>Enable or disable highlighting of selected edges.</p><p>By default, SocNetV hughlights the edges you select with the mouse, as well as all edges connected to the selected node. Although a useful feature, this can slow down the application responsiveness when the network consists of thousand nodes and edges. </p><p>If disabled, selected edges will not be highlighted.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Cache Background</span></p><p>Enable or disable caching of the canvas background. </p><p>The graphics engine can cache pre-rendered content in a pixmap, which is then drawn onto the viewport. The purpose of such caching is to speed up the total rendering time for areas that are slow to render (i.e. texture, gradient and alpha blended backgrounds). </p><p>If enabled, the canvas background is cached by allocating one pixmap with the full size of the viewport. The cache is invalidated every time the view is transformed. However, when scrolling, only partial invalidation is required. </p><p>If not enabled, nothing is cached and all painting is done directly onto the viewport.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. <br/></p></body></html> Qt::LayoutDirection::LeftToRight Use OpenGL <html><head/><body><p><span style=" font-weight:600;">Performance Settings</span></p><p>The following options influence the performance of the SocNetV canvas, inside which nodes and edges are being drawn. They affect the look of nodes and edges as well the precision and speed of the graphics engine.</p><p>Most of these settings have noticeable effects only in large networks. For instance, if you have a network with over 3000 actors and 10000 edges you might want to disable Antialiasing, Antialiasing Auto-adjustment and Smooth Pixmap Transformation while enabling Cache Background. </p><p>Also, if you need to visually interact with edges in a large network, you can experiment with the Update Mode setting to find which is suitable for your workload. The default setting &quot;Full&quot; means that the canvas is fully redrawn every time you make a small change.</p></body></html> Performance settings 0 40 <html><head/><body><p><span style=" font-weight:600;">Anti-Aliasing</span></p><p>Enable or disable Anti-Aliasing. If enabled, the graphics engine will antialias edges of primitives if possible. </p><p>Anti-aliasing is a technique which makes nodes, lines and text, smoother and fancier. But it comes at the cost of speed... </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Anti-Aliasing</span></p><p>Enable or disable Anti-Aliasing.</p><p>Anti-aliasing is a technique which makes nodes, lines and text, smoother and fancier. But it comes at the cost of speed... </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> Qt::LayoutDirection::LeftToRight Antialiasing 0 40 <html><head/><body><p><span style=" font-weight:600;">Antialiasing Auto-Adjustment</span></p><p>Enable or disable antialiasing auto-adjustment of exposed areas. Items that render antialiased lines on the boundaries of their bounding rectangle can end up rendering parts of the line outside. To prevent rendering artifacts, the graphics engine expands all exposed regions by 2 pixels in all directions. </p><p>If you disable this, SocNetV will no longer perform these adjustments, minimizing the areas that require redrawing, which improves performance. A common side effect is that items that do draw with antialiasing can leave painting traces behind on the scene as they are moved.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Antialiasing Auto-Adjustment</span></p><p>Enable or disable antialiasing auto-adjustment of exposed areas. Items that render antialiased lines on the boundaries of their bounding rectangle can end up rendering parts of the line outside. To prevent rendering artifacts, the graphics engine expands all exposed regions by 2 pixels in all directions. </p><p>If you disable this, SocNetV will no longer perform these adjustments, minimizing the areas that require redrawing, which improves performance. A common side effect is that items that do draw with antialiasing can leave painting traces behind on the scene as they are moved.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> Qt::LayoutDirection::LeftToRight Antialising Auto-Adjustment 0 40 <html><head/><body><p><span style=" font-weight:600;">Smooth pixmap transformations</span></p><p>Enable or disable smooth pixmap transformations. </p><p>If enabled, the engine will use a smooth pixmap transformation algorithm (such as bilinear) rather than nearest neighbor.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Smooth pixmap transformations</span></p><p>Enable or disable smooth pixmap transformations. </p><p>If enabled, the engine will use a smooth pixmap transformation algorithm (such as bilinear) rather than nearest neighbor.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> Qt::LayoutDirection::LeftToRight Smooth Pixmap Transformation 0 40 <html><head/><body><p><span style=" font-weight:600;">Save Painter State</span></p><p>When rendering, the graphics engine can protect (&quot;save&quot;) the painter state when rendering the background or foreground, and when rendering each item. This allows us to leave the painter in an altered state. </p><p>However, if the items consistently do restore the state, you should disable this option to prevent the application from doing the same.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Save Painter State</span></p><p>When rendering, the graphics engine can protect (&quot;save&quot;) the painter state when rendering the background or foreground, and when rendering each item. This allows us to leave the painter in an altered state. </p><p>However, if the items consistently do restore the state, you should disable this option to prevent the application from doing the same.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> Qt::LayoutDirection::LeftToRight Save Painter State 0 40 <html><head/><body><p><span style=" font-weight:600;">Edge highlighting</span></p><p>Enable or disable highlighting of selected edges.</p><p>By default, SocNetV hughlights the edges you select with the mouse, as well as all edges connected to the selected node. Although a useful feature, this can slow down the application responsiveness when the network consists of thousand nodes and edges. </p><p>If disabled, selected edges will not be highlighted.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Cache Background</span></p><p>Enable or disable caching of the canvas background. </p><p>The graphics engine can cache pre-rendered content in a pixmap, which is then drawn onto the viewport. The purpose of such caching is to speed up the total rendering time for areas that are slow to render (i.e. texture, gradient and alpha blended backgrounds). </p><p>If enabled, the canvas background is cached by allocating one pixmap with the full size of the viewport. The cache is invalidated every time the view is transformed. However, when scrolling, only partial invalidation is required. </p><p>If not enabled, nothing is cached and all painting is done directly onto the viewport.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. <br/></p></body></html> Qt::LayoutDirection::LeftToRight Edge Highlighting 0 40 <html><head/><body><p><span style=" font-weight:600;">Cache Background</span></p><p>Enable or disable caching of the canvas background. </p><p>The graphics engine can cache pre-rendered content in a pixmap, which is then drawn onto the viewport. The purpose of such caching is to speed up the total rendering time for areas that are slow to render (i.e. texture, gradient and alpha blended backgrounds). </p><p>If enabled, the canvas background is cached by allocating one pixmap with the full size of the viewport. The cache is invalidated every time the view is transformed. However, when scrolling, only partial invalidation is required. </p><p>If not enabled, nothing is cached and all painting is done directly onto the viewport.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. <br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Cache Background</span></p><p>Enable or disable caching of the canvas background. </p><p>The graphics engine can cache pre-rendered content in a pixmap, which is then drawn onto the viewport. The purpose of such caching is to speed up the total rendering time for areas that are slow to render (i.e. texture, gradient and alpha blended backgrounds). </p><p>If enabled, the canvas background is cached by allocating one pixmap with the full size of the viewport. The cache is invalidated every time the view is transformed. However, when scrolling, only partial invalidation is required. </p><p>If not enabled, nothing is cached and all painting is done directly onto the viewport.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. <br/></p></body></html> Qt::LayoutDirection::LeftToRight Cache Background 110 0 <html><head/><body><p><span style=" font-family:'Sans Serif'; font-size:9pt; font-weight:600;">Update Mode</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">Use the drop-down menu on the right to select the update mode when the canvas changes. Usually you do not need to modify this property, but there are some cases where doing so can improve rendering performance. </span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">The default value is Full, where the graphics engine will update the entire canvas when some of its contents change.</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Full</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">When any visible part of the canvas changes or is reexposed, the engine will update the entire canvas. This approach is fastest when the engine spends more time figuring out what to draw than it would spend drawing (e.g., when very many small items are repeatedly updated). This is the default setting as it is the fastest with network of thousands of edges.</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Minimal</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">The engine will determine the minimal region that requires a redraw, minimizing the time spent drawing by avoiding a redraw of areas that have not changed. Although this approach provides the best performance in general, if there are many small visible changes on the scene, the engine might end up spending more time finding the minimal approach than it will spend drawing.</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Bounding Rectangle </span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">The bounding rectangle of all changes in the canvas will be redrawn. This mode has the advantage that the engine searches only one region for changes, minimizing time spent determining what needs redrawing. The disadvantage is that areas that have not changed also need to be redrawn.</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">None</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">The engine will never update the canvas when something changes; This mode disables all item changes. Normally, you don't want to use this!</span></p></body></html> Update mode Qt::Orientation::Horizontal 38 17 0 0 185 0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; font-weight:600;">Update Mode</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">Select how to update areas of the canvas that have been reexposed or changed. Usually you do not need to modify this property, but there are some cases where doing so can improve rendering performance. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">The default value is Full, where the graphics engine will update the entire canvas when some of its contents change.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Full</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt; font-weight:600;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">When any visible part of the canvas changes or is reexposed, the engine will update the entire canvas. This approach is fastest when the engine spends more time figuring out what to draw than it would spend drawing (e.g., when very many small items are repeatedly updated). This is the default setting as it is the fastest with network of thousands of edges.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Minimal</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">The engine will determine the minimal region that requires a redraw, minimizing the time spent drawing by avoiding a redraw of areas that have not changed. Although this approach provides the best performance in general, if there are many small visible changes on the scene, the engine might end up spending more time finding the minimal approach than it will spend drawing.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Bounding Rectangle </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">The bounding rectangle of all changes in the canvas will be redrawn. This mode has the advantage that the engine searches only one region for changes, minimizing time spent determining what needs redrawing. The disadvantage is that areas that have not changed also need to be redrawn.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">None</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">The engine will never update the canvas when something changes; This mode disables all item changes. Normally, you don't want to use this!</span></p></body></html> 110 0 <html><head/><body><p><span style=" font-weight:600;">Index Method</span></p><p>Controls how the graphics engine indexes item positions on the canvas. <span style=" font-weight:600;">NoIndex is strongly recommended</span> and is the default. SocNetV networks are dynamic β€” nodes and edges are continuously moved, added, and removed. BspTreeIndex rebuilds its spatial tree on every such change, which causes severe slowdowns during rubber-band selection and node drag on networks with more than a few hundred nodes.</p><p><span style=" font-weight:600; font-style:italic;">NoIndex</span><span style=" font-style:italic;">: </span>No index. Item location is O(N), but adding, moving, and removing items is O(1). Ideal for dynamic scenes. <span style=" font-weight:600;">(Recommended)</span></p><p><span style=" font-weight:600; font-style:italic;">BspTreeIndex</span><span style=" font-style:italic;">: </span>A Binary Space Partitioning tree. Item location is O(log N), but every move triggers a tree rebuild. Only suitable for static scenes where items rarely change position.</p></body></html> Index Method Qt::Orientation::Horizontal 98 20 0 0 185 0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:600;">Index Method</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'DejaVu Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">Controls how the graphics engine indexes item positions on the canvas. </span><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:600;">NoIndex is strongly recommended</span><span style=" font-family:'DejaVu Sans'; font-size:10pt;"> and is the default. SocNetV networks are dynamic β€” nodes and edges are continuously moved, added, and removed. BspTreeIndex rebuilds its spatial tree on every such change, causing severe slowdowns during rubber-band selection and node drag on networks with more than a few hundred nodes.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'DejaVu Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:600; font-style:italic;">NoIndex</span><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-style:italic;">: </span><span style=" font-family:'DejaVu Sans'; font-size:10pt;">No index. Item location is O(N), but adding, moving, and removing items is O(1). Best for dynamic scenes. </span><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:600;">(Recommended)</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;"> </span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:600; font-style:italic;">BspTreeIndex</span><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-style:italic;">: </span><span style=" font-family:'DejaVu Sans'; font-size:10pt;">A Binary Space Partitioning tree. Item location is O(log N), but every move triggers a tree rebuild. Only suitable for static scenes where items rarely change position.</span></p></body></html> Options 10 20 321 67 Saving options Save zero-weight edges (GraphML only) buttonBox accepted() DialogSettings accept() 227 281 157 274 buttonBox rejected() DialogSettings reject() 295 287 286 274 SocNetV-3.7/src/forms/dialogsimilaritymatches.cpp000077500000000000000000000045521524402210300222420ustar00rootroot00000000000000/** * @file dialogsimilaritymatches.cpp * @brief Implements the DialogSimilarityMatches class for calculating similarity between nodes using matching coefficients in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogsimilaritymatches.h" #include #include "forms_logging.h" #include DialogSimilarityMatches::DialogSimilarityMatches (QWidget *parent) : QDialog (parent) { ui.setupUi(this); (ui.buttonBox)->button (QDialogButtonBox::Ok)->setDefault(true); matrixList << "Adjacency" << "Distances"; variablesLocationList << "Rows" << "Columns" << "Both"; measureList << "Simple / Exact matching" <<"Jaccard index" <<"Hamming distance" <<"Cosine similarity" <<"Euclidean distance"; ui.matrixSelect->insertItems( 1, matrixList ); (ui.variablesLocationSelect)->insertItems( 1, variablesLocationList ); (ui.measureSelect)->insertItems( 1, measureList ); (ui.diagonalCheckBox)->setChecked(false); } void DialogSimilarityMatches::getUserChoices(){ qCDebug(lcForms)<< "DialogSimilarityMatches: gathering Data!..."; QString matrix = (ui.matrixSelect)->currentText(); QString varLocation = (ui.variablesLocationSelect)->currentText(); QString measure = (ui.measureSelect)->currentText(); bool diagonal = (ui.diagonalCheckBox)->isChecked(); qCDebug(lcForms)<< "DialogSimilarityMatches: user selected: " << matrix << varLocation << measure; emit userChoices( matrix, varLocation, measure, diagonal ); } void DialogSimilarityMatches::on_buttonBox_accepted() { this->getUserChoices(); this->accept(); } void DialogSimilarityMatches::on_buttonBox_rejected() { this->reject(); } DialogSimilarityMatches::~DialogSimilarityMatches(){ matrixList.clear(); variablesLocationList.clear(); } SocNetV-3.7/src/forms/dialogsimilaritymatches.h000077500000000000000000000025241524402210300217040ustar00rootroot00000000000000/** * @file dialogsimilaritymatches.h * @brief Declares the DialogSimilarityMatches class for computing node similarity based on matching coefficients in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGSIMILARITYMATCHES_H #define DIALOGSIMILARITYMATCHES_H #include #include "ui_dialogsimilaritymatches.h" class DialogSimilarityMatches: public QDialog { Q_OBJECT public: DialogSimilarityMatches (QWidget *parent = Q_NULLPTR); ~DialogSimilarityMatches(); public slots: void getUserChoices(); signals: void userChoices(const QString &matrix, const QString &varLocation, const QString &method, const bool &diagonal); private slots: void on_buttonBox_accepted(); void on_buttonBox_rejected(); private: Ui::DialogSimilarityMatches ui; QStringList matrixList, variablesLocationList, measureList; }; #endif SocNetV-3.7/src/forms/dialogsimilaritymatches.ui000066400000000000000000000201441524402210300220650ustar00rootroot00000000000000 DialogSimilarityMatches true 0 0 700 390 0 0 700 390 Similarity: Matches Variables in: 200 0 Qt::FocusPolicy::StrongFocus <html><head/><body><p><span style=" font-weight:600;">Rows: </span>Correlate outbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Columns: </span>Correlate inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Both: </span>Correlate both outbound and inbound ties (or distances, depending on the selected matrix above) between pairs of actors. In this case, the input matrix is expanded by listing row vectors followed by column vectors. This is useful only when you have directed data.</p><p><br/></p></body></html> Enable to include matrix diagonal in calculations Include input matrix diagonal Matching measure: 200 0 Qt::FocusPolicy::StrongFocus <html><head/><body><p>Select a matching method. </p><p><span style=" font-weight:600;">Exact Matches</span>: Examines pairs of actors for exact tie or distance matches (present or absent) to other actors and returns a proportion to their overall ties. </p><p><span style=" font-weight:600;">Positive Matches (Jaccard/Co-citation)</span>: Looks for same ties/distances reported by both actors. Returns the ratio to the total number of ties reported.</p><p><span style=" font-weight:600;">Hamming distance</span>: Reports the number of ties/distances to other actors which differ between each pair of actors.</p><p><span style=" font-weight:600;">Cosine similarity</span>: Computes the pair-wise similarity of actors as the dot product of their tie/distance vectors divided by the magnitude of these vectors. <br/></p></body></html> 0 0 400 150 <html><head/><body><p>Compute a <span style=" font-weight:600;">similarity matrix</span>, where each element (i,j) is the pair-wise similarity score of actors i and j according to the selected &quot;matching&quot; method. For example, the &quot;Simple Matching&quot; method counts the number of times that actors i and j have the same tie / distance (present or absent) to other actors. </p><p>Select input matrix and where the &quot;variables&quot; are. For instance, select Adjacency matrix and Rows to correlate the outbound ties between all pairs of actors. Select Both to correlate the both inbound and outbound ties. Hover over &quot;Matching measure&quot; select box for more info on each method.</p></body></html> Qt::TextFormat::RichText true Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok 12 Qt::Orientation::Vertical 20 20 Input matrix: 200 0 Qt::FocusPolicy::StrongFocus <html><head/><body><p><span style=" font-weight:600;">Adjacency:</span> Use the active network's adjacency matrix as input to correlate ties between all pairs of actors. </p><p><span style=" font-weight:600;">Distances:</span> Use the active network's geodesic distances matrix to correlate distances between all pairs of actors.</p></body></html> Qt::Orientation::Vertical 20 15 SocNetV-3.7/src/forms/dialogsimilaritypearson.cpp000077500000000000000000000040061524402210300222570ustar00rootroot00000000000000/** * @file dialogsimilaritypearson.cpp * @brief Implements the DialogSimilarityPearson class for computing Pearson correlation similarity between nodes in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogsimilaritypearson.h" #include #include "forms_logging.h" #include DialogSimilarityPearson::DialogSimilarityPearson (QWidget *parent) : QDialog (parent) { ui.setupUi(this); (ui.buttonBox)->button (QDialogButtonBox::Ok)->setDefault(true); matrixList << "Adjacency" << "Distances"; variablesLocationList << "Rows" << "Columns" << "Both"; (ui.matrixSelect)->insertItems( 1, matrixList ); (ui.variablesLocationSelect)->insertItems( 1, variablesLocationList ); (ui.diagonalCheckBox)->setChecked(false); } void DialogSimilarityPearson::getUserChoices(){ qCDebug(lcForms)<< "DialogSimilarityPearson: gathering Data!..."; QString matrix = (ui.matrixSelect)->currentText(); QString varLocation = (ui.variablesLocationSelect)->currentText(); bool diagonal = (ui.diagonalCheckBox)->isChecked(); qCDebug(lcForms)<< "DialogSimilarityPearson: user selected: " << matrix << varLocation; emit userChoices( matrix, varLocation,diagonal ); } void DialogSimilarityPearson::on_buttonBox_accepted() { this->getUserChoices(); this->accept(); } void DialogSimilarityPearson::on_buttonBox_rejected() { this->reject(); } DialogSimilarityPearson::~DialogSimilarityPearson(){ matrixList.clear(); variablesLocationList.clear(); } SocNetV-3.7/src/forms/dialogsimilaritypearson.h000077500000000000000000000024171524402210300217300ustar00rootroot00000000000000/** * @file dialogsimilaritypearson.h * @brief Declares the DialogSimilarityPearson class for calculating Pearson similarity between nodes in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGSIMILARITYPEARSON_H #define DIALOGSIMILARITYPEARSON_H #include #include "ui_dialogsimilaritypearson.h" class DialogSimilarityPearson: public QDialog { Q_OBJECT public: DialogSimilarityPearson (QWidget *parent = Q_NULLPTR); ~DialogSimilarityPearson(); public slots: void getUserChoices(); signals: void userChoices(const QString &matrix, const QString &varLocation, const bool &diagonal); private slots: void on_buttonBox_accepted(); void on_buttonBox_rejected(); private: Ui::DialogSimilarityPearson ui; QStringList matrixList, variablesLocationList; }; #endif SocNetV-3.7/src/forms/dialogsimilaritypearson.ui000066400000000000000000000146171524402210300221200ustar00rootroot00000000000000 DialogSimilarityPearson true 0 0 700 350 0 0 700 350 Pearson Correlations Input matrix: 200 0 Qt::FocusPolicy::StrongFocus <html><head/><body><p><span style=" font-weight:600;">Adjacency:</span> Use the active network's adjacency matrix as input to correlate ties between all pairs of actors. </p><p><span style=" font-weight:600;">Distances:</span> Use the active network's geodesic distances matrix to correlate distances between all pairs of actors.</p></body></html> Variables in: 200 0 Qt::FocusPolicy::StrongFocus <html><head/><body><p><span style=" font-weight:600;">Rows: </span>Correlate outbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Columns: </span>Correlate inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Both: </span>Correlate both outbound and inbound ties (or distances, depending on the selected matrix above) between pairs of actors. In this case, the input matrix is expanded by listing row vectors followed by column vectors. This is useful only when you have directed data.</p><p><br/></p></body></html> 12 Qt::Orientation::Vertical 20 20 0 0 400 150 <html><head/><body><p>Compute <span style=" font-weight:600;">Pearson Product Moment Correlation Coefficients</span> (PPMCC) between the rows, the columns or both of a social network matrix (adjacency or distance matrix). The result is a <span style=" font-weight:600;">correlation matrix </span>containing the correlation coefficients between each variable (i.e. row) and the others. This might be useful if you want to check the pair-wise similarity of the actors, in terms of their ties. </p><p>Select input matrix and what &quot;variables&quot; to correlate. For instance, select Adjacency matrix and Rows to correlate the outbound ties between all pairs of actors. Select Both to correlate the both inbound and outbound ties.</p></body></html> Qt::TextFormat::RichText true Enable to include matrix diagonal in calculations Include input matrix diagonal Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok Qt::Orientation::Vertical 20 20 SocNetV-3.7/src/forms/dialogsysteminfo.cpp000077500000000000000000000141641524402210300207070ustar00rootroot00000000000000/** * @file dialogsysteminfo.cpp * @brief Implements the DialogSystemInfo class for displaying runtime system and environment details in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogsysteminfo.h" #include #include #include #include #include #include #include #ifndef QT_NO_OPENGL #include #endif #ifndef QT_NO_OPENGL static QString getGlString(QOpenGLFunctions *functions, GLenum name) { if (const GLubyte *p = functions->glGetString(name)) return QString::fromLatin1(reinterpret_cast(p)); return QString(); } #endif DialogSystemInfo::DialogSystemInfo (QWidget *parent) : QDialog (parent), ui(new Ui::DialogSystemInfo) { ui->setupUi(this); (ui->buttonBox)->button (QDialogButtonBox::Ok)->setDefault(true); (ui->infoTextEdit)->setFocus(); // this->setMinimumHeight(QApplication::primaryScreen()->availableSize().height()/2); QString information; information += "QT BUILD

"; information += "Architecture:
" + QSysInfo::buildAbi() + "
"; information += "

"; information += "SOCNETV BUILD

"; information += "DirPath:
" + QApplication::applicationDirPath() + "

"; information += "SSL version (at built-time):
" + QSslSocket::sslLibraryBuildVersionString() + "
"; information += "

"; information += "YOUR SYSTEM

"; information += "OS:
" + QSysInfo::prettyProductName() + "

"; information += "Kernel:
" + QSysInfo::kernelType() + " " + QSysInfo::kernelVersion() + "

"; information += "Architecture:
" + QSysInfo::currentCpuArchitecture() + "

"; if ( QSslSocket::supportsSsl() ) { information += "SSL support:
yes

"; information += "SSL version (run-time):
" + QSslSocket::sslLibraryVersionString() + "

"; information += "About web crawler: You are good to go. But please note, you may experience warnings/problems if you have a version of OpenSSL that does not match the one used while building SocNetV "; information += "(" + QSslSocket::sslLibraryBuildVersionString() + ")
"; } else { information += "SSL support:
NO

"; #if defined(Q_OS_WIN) information += "About web crawler: If you want to use the web crawler with https:// urls, please install the same (or the closest) version of OpenSSL that was used while building your SocNetV application "; information += "(" + QSslSocket::sslLibraryBuildVersionString() + ")"; information += " You may download Win32/Win64 OpenSSL installers from: https://slproweb.com/products/Win32OpenSSL.html
"; #else information += "About web crawler: If you want to use the web crawler with https:// urls, please install the same (or the closest) version of OpenSSL that was used while building your SocNetV application "; information += "(" + QSslSocket::sslLibraryBuildVersionString() + ").
"; #endif } #ifndef QT_NO_OPENGL // Our QT build has OpenGL support. // Check if there is a current OpenGL context // because the user might have disabled the OpenGL setting if ( QOpenGLContext::currentContext() ) { QOpenGLFunctions *qglFunctions = QOpenGLContext::currentContext()->functions(); information += "
OpenGL:
"; information += "Vendor: " + getGlString(qglFunctions, GL_VENDOR) + "
"; information += "Version: " + getGlString(qglFunctions, GL_VERSION) + "
"; information += "Renderer/Card: " + getGlString(qglFunctions, GL_RENDERER) + "
"; } else { information += "
OpenGL:
"; information += tr("SocNetV has OpenGL support, " "but you have disabled it. " "
" "Please enable OpenGL from Settings -> Canvas " "to enjoy faster drawing on the canvas." "
"); } #else information += "
OpenGL:
"; information += "NONE. Build without OpenGL support!"; #endif information += "
Library Paths:
" ; foreach(QString libPath, QCoreApplication::libraryPaths() ) { information += libPath + "
"; } information += "

"; information += "YOUR SCREEN

"; information += "Geometry:
"; information += QString::number(QApplication::primaryScreen()->geometry().x()); information += " x "; information += QString::number(QApplication::primaryScreen()->geometry().y()); information += "

"; information += "Size:
"; information += QString::number(QApplication::primaryScreen()->size().width()); information += " x "; information += QString::number(QApplication::primaryScreen()->size().height()); information += "

"; information += "Available Size:
"; information += QString::number(QApplication::primaryScreen()->availableSize().width()); information += " x "; information += QString::number(QApplication::primaryScreen()->availableSize().height()); information += "

"; information += "Device Pixel Ratio (the scale factor applied by the OS/Windowing system):
"; information += QString::number(QApplication::primaryScreen()->devicePixelRatio()); information += "

"; information += "Logical DPI (i.e. 144 on Windows default 150% mode):
"; information += QString::number(QApplication::primaryScreen()->logicalDotsPerInch()); ui->infoTextEdit->setText(information); // connect ( ui.buttonBox,SIGNAL(accepted()), this, SLOT(getUserChoices()) ); } SocNetV-3.7/src/forms/dialogsysteminfo.h000077500000000000000000000016011524402210300203440ustar00rootroot00000000000000/** * @file dialogsysteminfo.h * @brief Declares the DialogSystemInfo class for displaying system and environment information in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef DIALOGSYSTEMINFO_H #define DIALOGSYSTEMINFO_H #include #include "ui_dialogsysteminfo.h" class DialogSystemInfo: public QDialog { Q_OBJECT public: explicit DialogSystemInfo (QWidget *parent = Q_NULLPTR); private: Ui::DialogSystemInfo *ui; }; #endif SocNetV-3.7/src/forms/dialogsysteminfo.ui000066400000000000000000000055701524402210300205400ustar00rootroot00000000000000 DialogSystemInfo 0 0 700 400 0 0 700 400 System Information false QFrame::Shape::NoFrame <html><head/><body><p>Use the following to provide more meaningful information in your bug reports:</p></body></html> Qt::TextFormat::RichText true 0 310 true 300 0 Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBox accepted() DialogSystemInfo accept() 266 390 157 274 buttonBox rejected() DialogSystemInfo reject() 334 390 286 274 SocNetV-3.7/src/forms/dialogwebcrawler.cpp000077500000000000000000000273251524402210300206470ustar00rootroot00000000000000/** * @file dialogwebcrawler.cpp * @brief Implements the DialogWebCrawler class for configuring and executing web crawling functionality in SocNetV. * @author Dimitris B. Kalamaras * @website http://dimitris.apeiro.gr * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "dialogwebcrawler.h" #include #include "forms_logging.h" #include #include #include #include DialogWebCrawler::DialogWebCrawler(QWidget *parent) : QDialog (parent) { ui.setupUi(this); (ui.buttonBox)->button (QDialogButtonBox::Ok)->setDefault(true); (ui.buttonBox)->button (QDialogButtonBox::Ok)->setDisabled(true); ui.seedUrlEdit->setFocus(); ui.seedUrlEdit->setPlaceholderText("Please enter a url..."); ui.patternsIncludedTextEdit->setText("*"); ui.patternsExcludedTextEdit->setText(""); // Set checkbox/options default values intLinks=true; childLinks=true; parentLinks=false; extLinksAllowed=false; socialLinks=false; extLinks=false; ui.intLinksCheckBox->setChecked (intLinks); ui.childLinksCheckBox->setChecked(childLinks); ui.parentLinksCheckBox->setChecked(parentLinks); ui.extLinksAllowedCheckBox->setChecked(extLinksAllowed); ui.extLinksCrawlCheckBox->setChecked (extLinks); ui.extLinksCrawlCheckBox->setEnabled(extLinksAllowed == true); ui.socialLinksCheckBox->setChecked(socialLinks); ui.selfLinksCheckBox->setChecked(false); ui.waitCheckBox->setChecked(true); connect (ui.seedUrlEdit, &QLineEdit::textChanged, this, &DialogWebCrawler::checkErrors); connect (ui.maxUrlsToCrawlSpinBox, &QSpinBox::editingFinished, this, &DialogWebCrawler::checkErrors); connect (ui.maxLinksPerPageSpinBox, &QSpinBox::editingFinished, this, &DialogWebCrawler::checkErrors); connect (ui.patternsIncludedTextEdit, &QTextEdit::textChanged, this, &DialogWebCrawler::checkErrors); connect (ui.patternsExcludedTextEdit, &QTextEdit::textChanged, this, &DialogWebCrawler::checkErrors); connect (ui.intLinksCheckBox, &QCheckBox::stateChanged, this, &DialogWebCrawler::checkErrors); connect (ui.childLinksCheckBox, &QCheckBox::stateChanged, this, &DialogWebCrawler::checkErrors); connect (ui.parentLinksCheckBox, &QCheckBox::stateChanged, this, &DialogWebCrawler::checkErrors); connect (ui.extLinksAllowedCheckBox, &QCheckBox::stateChanged, this, &DialogWebCrawler::checkErrors); connect (ui.extLinksCrawlCheckBox, &QCheckBox::stateChanged, this, &DialogWebCrawler::checkErrors); connect (ui.socialLinksCheckBox, &QCheckBox::stateChanged, this, &DialogWebCrawler::checkErrors); connect ( ui.buttonBox,SIGNAL(accepted()), this, SLOT(getUserChoices()) ); } /** * @brief Checks crawler form for user input errors */ void DialogWebCrawler::checkErrors(){ // // SETUP FLAGS // bool errorUrl = false; bool errorPatternsIncl = false; bool errorPatternsExcl = false; bool errorCheckboxes = false; // // Get seed url, sanitize and validate it // seedUrlInputStr = (ui.seedUrlEdit)->text(); qCDebug(lcForms)<< "seed url:" << seedUrlInputStr << "Sanitizing..."; seedUrlInputStr = seedUrlInputStr.simplified(); seedUrl = QUrl(seedUrlInputStr); qCDebug(lcForms)<< "seed url:" << seedUrl.toString() << " scheme " << seedUrl.scheme() << " host " << seedUrl.host() << " path " << seedUrl.path(); if ( seedUrl.scheme().isEmpty() || ( seedUrl.scheme() != "http" && seedUrl.scheme() != "https" )) { qCDebug(lcForms)<< "seed url has no scheme. Setting the default scheme (http) "; seedUrl.setUrl("//" + seedUrlInputStr); seedUrl.setScheme("http"); qCDebug(lcForms) << seedUrl; } if (seedUrl.path().isEmpty() ) { qCDebug(lcForms)<< "seed url without path. Adding default path '/'..."; seedUrl.setPath("/"); } if (! seedUrl.isValid() || seedUrl.host() == "" || !seedUrl.host().contains(".") ) { qCDebug(lcForms)<< "Error. seed url not valid."; QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui.seedUrlEdit->setGraphicsEffect(effect); (ui.buttonBox)->button (QDialogButtonBox::Ok)->setDisabled(true); errorUrl = true; } else { ui.seedUrlEdit->setGraphicsEffect(0); } // // GET SPINBOX VALUES AND CHECKBOX OPTIONS // maxLinksPerPage = (ui.maxLinksPerPageSpinBox)->value(); maxUrlsToCrawl = (ui.maxUrlsToCrawlSpinBox)->value(); // // CHECK INTERNAL/EXTERNAL LINKS CHECKBOXES. AT LEAST ONE SHOULD BE ENABLED // intLinks = ui.intLinksCheckBox->isChecked(); extLinksAllowed = ui.extLinksAllowedCheckBox->isChecked(); if ( !intLinks && !extLinksAllowed ) { // Both are disabled. Throw error errorCheckboxes = true; QGraphicsColorizeEffect *effect1 = new QGraphicsColorizeEffect; QGraphicsColorizeEffect *effect2 = new QGraphicsColorizeEffect; effect1->setColor(QColor("red")); effect2->setColor(QColor("red")); ui.extLinksAllowedCheckBox->setGraphicsEffect(effect1); ui.intLinksCheckBox->setGraphicsEffect(effect2); ui.parentLinksCheckBox->setEnabled(false); ui.childLinksCheckBox->setEnabled(false); ui.extLinksCrawlCheckBox->setEnabled(false); ui.selfLinksCheckBox->setEnabled(false); ui.socialLinksCheckBox->setEnabled(false); } else { // At least one of internal/external links checkboxes is enabled. // Remove any prior error ui.extLinksAllowedCheckBox->setGraphicsEffect(0); ui.intLinksCheckBox->setGraphicsEffect(0); errorCheckboxes = false; // If internal link crawling is disabled, disable related options ui.selfLinksCheckBox->setEnabled( intLinks == true); ui.parentLinksCheckBox->setEnabled(intLinks == true); ui.childLinksCheckBox->setEnabled(intLinks == true); ui.extLinksCrawlCheckBox->setEnabled(extLinksAllowed==true); ui.socialLinksCheckBox->setEnabled(extLinksAllowed==true); } // // CHECK URL PATTERNS TO INCLUDE TEXTEDIT // qCDebug(lcForms)<< "Checking included url patterns..."; urlPatternsIncluded = parseTextEditInput (ui.patternsIncludedTextEdit->toHtml()); if (urlPatternsIncluded.size() == 0 ) { // No pattern found. Throw error. QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui.patternsIncludedTextEdit->setGraphicsEffect(effect); errorPatternsIncl = true; } else { if (urlPatternsIncluded.size() == 1 && urlPatternsIncluded.at(0) =="" ) { urlPatternsIncluded.clear(); qCDebug(lcForms) << "return empty urlPatterns (ALL)"; } ui.patternsIncludedTextEdit->setGraphicsEffect(0); errorPatternsIncl = false; } // // CHECK URL PATTERNS TO EXCLUDE TEXTEDIT // qCDebug(lcForms)<< "Checking excluded url patterns..."; urlPatternsExcluded = parseTextEditInput (ui.patternsExcludedTextEdit->toHtml()); if (urlPatternsExcluded.size() == 1 ) { if (urlPatternsExcluded.at(0) == "*") { QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect; effect->setColor(QColor("red")); ui.patternsExcludedTextEdit->setGraphicsEffect(effect); errorPatternsExcl = true; } } else { ui.patternsExcludedTextEdit->setGraphicsEffect(0); errorPatternsExcl = false; } // // ENABLE/DISABLE OK BUTTON // if ( errorUrl || errorPatternsIncl || errorPatternsExcl || errorCheckboxes ) { (ui.buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(false); } if ( !errorUrl && !errorPatternsIncl && !errorPatternsExcl && !errorCheckboxes ) { (ui.buttonBox)->button (QDialogButtonBox::Ok)->setEnabled(true); } } /** * @brief Parses HTML-formatted input string and returns a list of all strings inside

...

* @param html * @return */ QStringList DialogWebCrawler::parseTextEditInput(const QString &html){ QStringList userInputParsed; if ( ! html.isEmpty() ) { QStringList userInput ; QString data; QString str; userInput = html.split("',0) +1, data.indexOf("

",0) - (data.indexOf('>',0) +1) ); qCDebug(lcForms) << "str ::" << str ; str.remove("
"); str=str.simplified(); // remove wildcards, if there are any if (str.contains("*")) { str.remove("*"); } qCDebug(lcForms) << "str fin ::" << str; // urls and classes cannot contain spaces... if (str.contains(" ")) { userInputParsed.clear(); qCDebug(lcForms) << "urls cannot contain spaces... Break." ; break; } userInputParsed << str; } } else { userInputParsed.clear(); } qCDebug(lcForms) << "stringlist size" << userInputParsed.size()<< "\n"; return userInputParsed; } /** * @brief gathers data from web crawler form */ void DialogWebCrawler::getUserChoices(){ qCDebug(lcForms)<< "Emitting user choices:" << "\n" << "seedUrl: " << seedUrl.toString() << "\n" << "urlPatternsIncluded" << urlPatternsIncluded << "\n" << "urlPatternsExcluded" << urlPatternsExcluded << "\n" << "linkClasses" << linkClasses << "\n" << "maxLinksPerPage " << maxLinksPerPage << "\n" << "totalUrlsToCrawl " << maxUrlsToCrawl << "\n" << "intLinks"<< ui.intLinksCheckBox->isChecked() << "\n" << "childLinks" << ui.childLinksCheckBox->isChecked() << "\n" << "parentLinks" << ui.parentLinksCheckBox->isChecked() << "\n" << "selfLinks" << ui.selfLinksCheckBox->isChecked() << "\n" << "extLinksAllowed"<< ui.extLinksAllowedCheckBox->isChecked()<< "\n" << "extLinksCrawl"<< ui.extLinksCrawlCheckBox->isChecked()<< "\n" << "socialLinks" << ui.socialLinksCheckBox->isChecked()<< "\n" << "delayedRequests" << ui.waitCheckBox->isChecked() << "\n"; emit userChoices( seedUrl, urlPatternsIncluded, urlPatternsExcluded, linkClasses, maxUrlsToCrawl, maxLinksPerPage, ui.intLinksCheckBox->isChecked(), ui.childLinksCheckBox->isChecked(), ui.parentLinksCheckBox->isChecked(), ui.selfLinksCheckBox->isChecked(), ui.extLinksAllowedCheckBox->isChecked(), ui.extLinksCrawlCheckBox->isChecked(), ui.socialLinksCheckBox->isChecked(), ui.waitCheckBox->isChecked() ); } SocNetV-3.7/src/forms/dialogwebcrawler.h000077500000000000000000000040141524402210300203020ustar00rootroot00000000000000/** * @file dialogwebcrawler.h * @brief Declares the DialogWebCrawler class for configuring and initiating web-based network crawling in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef WEBCRAWLERDIALOG_H #define WEBCRAWLERDIALOG_H class QUrl; #include #include "ui_dialogwebcrawler.h" class DialogWebCrawler: public QDialog { Q_OBJECT public: explicit DialogWebCrawler (QWidget *parent = Q_NULLPTR); public slots: void checkErrors (); void getUserChoices (); QStringList parseTextEditInput(const QString &html); signals: void userChoices( const QUrl &startUrl, const QStringList &, const QStringList &, const QStringList &, const int &maxNodes, const int &maxLinks, const bool &intLinks, const bool &childLinks, const bool &parentLinks, const bool &selfLinks, const bool &extLinksAllowed, const bool &extLinksCrawl, const bool &socialLinks, const bool &delayedRequests ); void webCrawlerDialogError(QString); private: Ui::DialogWebCrawler ui; QString seedUrlInputStr; QUrl seedUrl ; int maxLinksPerPage, maxUrlsToCrawl; bool extLinks, intLinks; bool extLinksAllowed; bool childLinks, parentLinks; bool socialLinks; QStringList linkClasses; QStringList urlPatternsIncluded; QStringList urlPatternsExcluded; }; #endif SocNetV-3.7/src/forms/dialogwebcrawler.ui000066400000000000000000000464761524402210300205070ustar00rootroot00000000000000 DialogWebCrawler 0 0 600 656 0 0 600 650 Generate network from web links URL patterns to include <html><head/><body><p><span style=" font-weight:600;">Allowed URL Patterns</span></p><p>Enter, in separate lines, one or more URL patterns to <span style=" font-weight:600;">include</span> while crawling. For example:</p><p>example.com/pattern/*</p><p>Do not enter spaces. Leave * to crawl all urls.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Crawl Internal Links </span></p><p>If enabled, the crawler will include and map <span style=" font-weight:600;">internal links, </span> that is pages from the same domain (or, in other words, from the same host ) as the URL being parsed each time.</p><p>If you do not want to crawl internal links, disable this option.</p><p> Please note that you MUST enable either this option or the &quot;Include external links&quot; option, for the crawler to work.</p><p>Default is to crawl internal links only. </p><p>You can further refine what kind of internal links to follow with the two options below: Child links and Parent links. </p></body></html> Crawl internal links true <html><head/><body><p><span style=" font-weight:600;">Crawl child links</span></p><p>If enabled, the crawler will map <span style=" font-weight:600;">child URLs</span></p><p>A URL is a <span style=" font-style:italic;">childUrl </span>of another URL if the two URLs share the same scheme and authority, and the latter URL's path is a parent of the path of <span style=" font-style:italic;">childUrl</span>. This applies only to internal URLs.</p><p>For instance, www.socnetv.org/docs/manual.html is a child URL of www.socnetv.org/docs/ </p><p>If you don't want to crawl child URLs, disable this option. </p></body></html> Child links false <html><head/><body><p><span style=" font-weight:600;">Crawl parent links</span></p><p>If enabled, the crawler will map <span style=" font-weight:600;">parent URLs</span>. </p><p>A URL is a <span style=" font-style:italic;">parent </span>of another URL if the two URLs share the same scheme and authority, and the former URL's path is a parent of the path of the latter URL. This applies to internal URLs.</p><p>For instance, the URL www.socnetv.org/docs/ is a parent URL of www.socnetv.org/docs/manual.html</p><p>If you don't want to crawl parent links, disable this option. </p></body></html> Parent links false URL patterns to exclude <html><head/><body><p><span style=" font-weight:600;">Excluded URL Patterns</span></p><p>Enter, in separate lines, one or more URL patterns to <span style=" font-weight:600;">exclude</span> while crawling. For example:</p><p>example.com/pattern/*</p><p>Do not enter spaces. Leave blank to crawl all urls.</p></body></html> 200 0 <html><head/><body><p>Set the max links inside a page to be followed and crawled by SocNetV.</p><p>Set this to zero if you don't want to have this limit. In this case SocNetV will follow and crawl every link found in a page.</p></body></html> Max links in each page to follow Qt::Orientation::Horizontal 150 20 60 0 <html><head/><body><p>Set the max links inside a page to be followed and crawled by SocNetV.</p><p>Set this to zero if you don't want to have this limit. In this case SocNetV will follow and crawl every link found in a page.</p></body></html> 9999 0 <html><head/><body><p><span style=" font-weight:600;">Links to social media domains</span></p><p>If enabled, the crawler will map (and possibly crawl) <span style=" font-weight:600;">links to social media websites</span>, such as twitter.com.</p><p>If disabled, the crawler will diregard any link to URLs in the following domains:</p><p>facebook.com<br/>twitter.com<br/>linkedin.com<br/>instagram.com<br/>pinterest.com<br/>telegram.org<br/>telegram.me<br/>youtube.com<br/>reddit.com<br/>plus.google.com</p><p><span style=" font-weight:600;">Note</span>: You can exclude more social media or define your custom social media exclusion list by typing domains in the &quot;URL patterns to exclude&quot; text edit above.</p></body></html> Links to social media false <html><head/><body><p>If enabled the application will draw a <span style=" font-weight:600;">self-link</span> when a page contains a link to itself. </p><p>Default is not to allow self-links.</p></body></html> Allow Self-Links false Qt::Orientation::Vertical 20 40 <html><head/><body><p><span style=" font-weight:600;">Show external links</span></p><p>If enabled, the crawler will map <span style=" font-weight:600;">links to external domains</span>. </p><p>For instance, if this option is enabled and you start crawling www.supersyntages.gr where there is a link to a page of a different domain, i.e. www.aggelies247.com/news, then a node &quot;www.aggelies247.com/news&quot; will be added to the network. </p><p>If you don't want to show external links at all, just disable this option. </p><p>Please note that you <span style=" font-weight:600;">MUST </span>enable either this option or the &quot;Include internal links&quot; option, for the crawler to work.</p></body></html> Show external links false <html><head/><body><p><span style=" font-weight:600;">Crawl external links</span></p><p>If enabled, the crawler will <span style=" font-weight:600;">map external links AND crawl them for new links as well.</span></p><p>For instance, if you enable this option and start crawling the page at https://www.supersyntages.gr where there is a link to another domain, i.e. www.linuxinsider.gr, then the crawler will visit linuxinsider.gr too to find more links. </p><p>If you don't want to crawl external links, disable this option. </p></body></html> Crawl external links false <html><head/><body><p>Wait for a random number of milliseconds (<span style=" font-weight:600;">0-1000</span>) between network requests. </p><p>Use of this option is recommended, as it lightens the server load by making the requests less frequent.</p><p>By default this option is enabled.</p></body></html> Delay between requests true 0 0 450 130 false QFrame::Shape::NoFrame <html><head/><body><p>Use the built-in web crawler to scan the HTML code of a given initial URL (i.e. a website) and map all internal or external links to other pages found there. </p><p>As new URLs are discovered, the crawler follows them to scan their HTML code for links as well. For more details, see the Manual. </p><p>Enter the initial URL below and change crawling parameters if you like.</p></body></html> Qt::TextFormat::RichText true Initial URL Qt::Orientation::Horizontal 40 20 0 0 390 22 400 24 <html><head/><body><p>Enter the initial url/domain to start crawling from, i.e. https://socnetv.org</p><p>You may omit https:// if you want. </p></body></html> 150 0 <html><head/><body><p>Set the total urls to be crawled. </p><p>This is the total nodes the result network will have. </p><p>Set value to 0, if you don't want any limits...</p></body></html> Max URLs to crawl Qt::Orientation::Horizontal 198 20 60 0 <html><head/><body><p>Set the total URLs to be crawled. </p><p>This is the <span style=" font-weight:600;">maximum nodes</span> the result network will have. </p><p>Set value to 0, if you don't want any limits...</p></body></html> 1 2000 600 true 300 0 Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBox accepted() DialogWebCrawler accept() 257 324 157 274 buttonBox rejected() DialogWebCrawler reject() 325 324 286 274 SocNetV-3.7/src/forms/edgeeditdialog.ui000066400000000000000000000221231524402210300201030ustar00rootroot00000000000000 EdgeEditDialog 0 0 450 280 450 280 Node Properties 12 Enter node label 12 false 12 Enter node value (disabled) 12 Qt::Horizontal 40 20 false 12 12 Select line shape 12 Qt::Horizontal 40 20 12 :/images/box.png:/images/box.png 12 :/images/circle.png:/images/circle.png 12 :/images/diamond.png:/images/diamond.png 12 :/images/ellipse.png:/images/ellipse.png 12 :/images/triangle.png:/images/triangle.png 12 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok 12 Select link color (click the button) 12 Qt::Horizontal 40 20 60 25 12 ... 60 20 12 Select the link weight (default 1) 12 Qt::Horizontal 40 20 12 8 buttonBox accepted() EdgeEditDialog accept() 233 316 157 274 buttonBox rejected() EdgeEditDialog reject() 301 322 286 274 SocNetV-3.7/src/forms/forms_logging.h000066400000000000000000000022251524402210300176200ustar00rootroot00000000000000/** * @file forms_logging.h * @brief Shared WS14 logging category declaration for src/forms/ dialog files. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef FORMS_LOGGING_H #define FORMS_LOGGING_H #include // One shared category for all src/forms/ dialogs -- each file has too few qDebug() calls (1-16, // pure one-shot UI code) to justify a category of its own; no pre-existing shared header exists // across the ~20 dialog*.cpp files to piggyback on the way parser.h/graphicswidget.h did, so this // small header exists purely to be that shared declaration point. Definition in // dialogsettings.cpp (arbitrary single anchor, no dependency significance). Q_DECLARE_LOGGING_CATEGORY(lcForms) #endif // FORMS_LOGGING_H SocNetV-3.7/src/global.h000066400000000000000000000226121524402210300151000ustar00rootroot00000000000000/** * @file global.h * @brief Global definitions, constants, enumerations, and utility types for SocNetV. * * All symbols are defined inside the SocNetV namespace. * Q_DECLARE_METATYPE registrations are placed outside the namespace, * as required by Qt's metatype system. */ #ifndef GLOBAL_H #define GLOBAL_H #include #include #include // ============================================================================ // Namespace macros // ============================================================================ #define SOCNETV_NAMESPACE SocNetV #ifdef SOCNETV_NAMESPACE #define SOCNETV_BEGIN_NAMESPACE \ namespace SOCNETV_NAMESPACE \ { #define SOCNETV_END_NAMESPACE } #define SOCNETV_USE_NAMESPACE using namespace SOCNETV_NAMESPACE; #else #define SOCNETV_BEGIN_NAMESPACE #define SOCNETV_END_NAMESPACE #define SOCNETV_USE_NAMESPACE #endif SOCNETV_BEGIN_NAMESPACE // ============================================================================ // Version // ============================================================================ static const QString VERSION = "3.7"; // ============================================================================ // Math constants (define only if not already provided by ) // ============================================================================ #ifndef M_PI static constexpr double M_PI = 3.14159265358979323846; #endif #ifndef M_PI_3 static constexpr double M_PI_3 = 1.04719755119659774615; #endif #ifndef M_PI_X_2 static constexpr double M_PI_X_2 = 6.28318530717958647692; #endif // ============================================================================ // Enumerations // ============================================================================ /** * @enum NodeShape * @brief Possible shapes for nodes in the network visualization. */ enum NodeShape { Box, Circle, Diamond, Ellipse, Triangle, Star, Person, PersonB, Bugs, Heart, Dice, Custom }; /** * @enum FileType * @brief Supported network file formats. */ enum FileType { NOT_SAVED = 0, ///< New or unsaved/modified network GRAPHML = 1, ///< .graphml / .xml PAJEK = 2, ///< .paj / .net ADJACENCY = 3, ///< .csv / .adj / .sm GRAPHVIZ = 4, ///< .dot UCINET = 5, ///< .dl / .dat GML = 6, ///< .gml EDGELIST_WEIGHTED = 7, ///< .csv / .txt / .list / .lst / .wlst EDGELIST_SIMPLE = 8, ///< .csv / .txt / .list / .lst TWOMODE = 9, ///< .2sm / .aff UNRECOGNIZED = -1 ///< Unrecognised format }; /** * @enum EdgeType * @brief Possible edge types in the network. */ enum EdgeType { Directed = 0, Reciprocated = 1, Undirected = 2 }; /** * @enum IndexType * @brief Centrality / prestige index identifiers. */ enum IndexType { DC = 1, ///< Degree Centrality CC = 2, ///< Closeness Centrality IRCC = 3, ///< Influence Range Closeness Centrality BC = 4, ///< Betweenness Centrality SC = 5, ///< Stress Centrality EC = 6, ///< Eccentricity Centrality PC = 7, ///< Power Centrality IC = 8, ///< Information Centrality EVC = 9, ///< Eigenvector Centrality KATZ = 10, ///< Katz Centrality BPC = 11, ///< Bonacich Power Centrality - distinct from PC (Gil-Schmidt Power Centrality) DP = 12, ///< Degree Prestige PRP = 13, ///< PageRank Prestige PP = 14, ///< Proximity Prestige CLC = 15 ///< Clustering Coefficient (Watts-Strogatz) }; /** * @enum ChartType * @brief Chart style for prominence distribution visualizations. */ enum ChartType { None = -1, Spline = 0, Area = 1, Bars = 2 }; /** * @enum ReportFormat * @brief Output format for analysis reports: a full HTML document (default), or a lean, * table-only CSV file (no prose/summary-stats/chart β€” those have no CSV equivalent). */ enum ReportFormat { Html = 0, Csv = 1 }; /** * @enum NetworkRequestType * @brief Identifies the purpose of an outgoing network request. */ enum NetworkRequestType { Generic = 0, Crawler = 1, CheckUpdate = 2 }; // ============================================================================ // User-message severity constants // ============================================================================ static const int USER_MSG_INFO = 0; static const int USER_MSG_CRITICAL = 1; static const int USER_MSG_CRITICAL_NO_NETWORK = 2; static const int USER_MSG_CRITICAL_NO_EDGES = 3; static const int USER_MSG_QUESTION = 4; static const int USER_MSG_QUESTION_CUSTOM = 5; // ============================================================================ // Subgraph type constants // ============================================================================ static const int SUBGRAPH_CLIQUE = 1; static const int SUBGRAPH_STAR = 2; static const int SUBGRAPH_CYCLE = 3; static const int SUBGRAPH_LINE = 4; // ============================================================================ // Matrix type constants // ============================================================================ static const int MATRIX_ADJACENCY = 1; static const int MATRIX_DISTANCES = 2; static const int MATRIX_DEGREE = 3; static const int MATRIX_LAPLACIAN = 4; static const int MATRIX_ADJACENCY_INVERSE = 5; static const int MATRIX_GEODESICS = 6; static const int MATRIX_REACHABILITY = 7; static const int MATRIX_ADJACENCY_TRANSPOSE = 8; static const int MATRIX_COCITATION = 9; static const int MATRIX_DISTANCES_EUCLIDEAN = 12; static const int MATRIX_DISTANCES_MANHATTAN = 13; static const int MATRIX_DISTANCES_JACCARD = 14; static const int MATRIX_DISTANCES_HAMMING = 15; static const int MATRIX_DISTANCES_CHEBYSHEV = 16; // ============================================================================ // Structs and value types // ============================================================================ /** * @struct ClickedEdge * @brief Carries the identity and type of a clicked edge. */ struct ClickedEdge { int v1 = 0; ///< First vertex int v2 = 0; ///< Second vertex int type = 0; ///< Edge type }; /** * @typedef SelectedEdge * @brief Identifies a selected edge by its two endpoint vertex numbers. * * Defined here (not in graphicswidget.h) so that Graph and GraphicsWidget * can both use it without a circular include dependency. */ typedef QPair SelectedEdge; /** * @class MyEdge * @brief Lightweight value type representing a directed or undirected edge. */ class MyEdge { public: int source = 0; int target = 0; double weight = 0.0; int type = 0; double rWeight = 0.0; ///< Reverse / reciprocal weight MyEdge() = default; MyEdge(const int &from, const int &to, const double &w = 0.0, const int &t = 0, const double &rw = 0.0) : source(from), target(to), weight(w), type(t), rWeight(rw) {} }; /** * @class EdgeVisibilityChange * @brief One edge's visibility change, batched with others in signalSetEdgesVisibilityBatch * (WS3 M2) so a bulk operation (e.g. a relation switch touching every edge) crosses from * graphThread to the GUI thread as a single queued dispatch instead of one per edge. * * Mirrors the parameters of Graph::signalSetEdgeVisibility / GraphicsWidget::setEdgeVisibility. */ class EdgeVisibilityChange { public: int relation = 0; int source = 0; int target = 0; bool visible = false; bool preserveReverseEdge = false; int edgeWeight = 1; int reverseEdgeWeight = 1; EdgeVisibilityChange() = default; EdgeVisibilityChange(const int &rel, const int &src, const int &tgt, const bool &vis, const bool &preserveReverse = false, const int &weight = 1, const int &reverseWeight = 1) : relation(rel), source(src), target(tgt), visible(vis), preserveReverseEdge(preserveReverse), edgeWeight(weight), reverseEdgeWeight(reverseWeight) {} }; /** * @class GraphDistance * @brief Holds a (target, distance) pair for use in Dijkstra's priority queue. */ class GraphDistance { public: int target; int distance; GraphDistance(int t, int dist) : target(t), distance(dist) {} }; /** * @class GraphDistancesCompare * @brief Min-priority comparator for GraphDistance (used in std::priority_queue). */ class GraphDistancesCompare { public: bool operator()(const GraphDistance &t1, const GraphDistance &t2) const { if (t1.distance == t2.distance) return t1.target > t2.target; return t1.distance > t2.distance; } }; /** * @class PairVF * @brief (value, frequency) pair, used in distribution charts. */ class PairVF { public: qreal value; qreal frequency; PairVF(qreal v, qreal f) : value(v), frequency(f) {} }; /** * @class PairVFCompare * @brief Min-priority comparator for PairVF (used in std::priority_queue). */ class PairVFCompare { public: bool operator()(const PairVF &v1, const PairVF &v2) const { return v1.value > v2.value; } }; SOCNETV_END_NAMESPACE // ============================================================================ // Qt metatype registrations // Must live outside the SocNetV namespace. // ============================================================================ Q_DECLARE_METATYPE(SOCNETV_NAMESPACE::MyEdge) Q_DECLARE_METATYPE(SOCNETV_NAMESPACE::NetworkRequestType) Q_DECLARE_METATYPE(SOCNETV_NAMESPACE::SelectedEdge) #endif // GLOBAL_HSocNetV-3.7/src/graph.cpp000077500000000000000000000414441524402210300153030ustar00rootroot00000000000000/** * @file graph.cpp * @brief Implements the Graph class for managing network structures, nodes, and edges in SocNetV. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org * @website http://dimitris.apeiro.gr */ #include "graph.h" #include #include #include #include #include #include #include #include #include #include // used in exporting centrality files #include #include #include #include //allows the use of RAND_MAX macro #include //for BFS queue Q #include // for randomizeThings #include "engine/distance_engine.h" #include "engine/graph_distance_progress_sink.h" // WS14: definitions for the categories declared in graph.h -- see that header for the rationale. Q_LOGGING_CATEGORY(lcGraph, "socnetv.graph") Q_LOGGING_CATEGORY(lcGraphCore, "socnetv.graph.core") Q_LOGGING_CATEGORY(lcStorage, "socnetv.graph.storage") Q_LOGGING_CATEGORY(lcCentrality, "socnetv.graph.centrality") Q_LOGGING_CATEGORY(lcClustering, "socnetv.graph.clustering") Q_LOGGING_CATEGORY(lcDistances, "socnetv.graph.distances") Q_LOGGING_CATEGORY(lcProminence, "socnetv.graph.prominence") Q_LOGGING_CATEGORY(lcReachability, "socnetv.graph.reachability") Q_LOGGING_CATEGORY(lcSimilarity, "socnetv.graph.similarity") Q_LOGGING_CATEGORY(lcLayouts, "socnetv.graph.layouts") Q_LOGGING_CATEGORY(lcGenerators, "socnetv.graph.generators") Q_LOGGING_CATEGORY(lcGraphMatrices, "socnetv.graph.matrices") Q_LOGGING_CATEGORY(lcCohesion, "socnetv.graph.cohesion") Q_LOGGING_CATEGORY(lcReporting, "socnetv.graph.reporting") Q_LOGGING_CATEGORY(lcFilters, "socnetv.graph.filters") Q_LOGGING_CATEGORY(lcRelations, "socnetv.graph.relations") Q_LOGGING_CATEGORY(lcGraphCrawler, "socnetv.graph.crawler") Q_LOGGING_CATEGORY(lcGraphUI, "socnetv.graph.ui") Q_LOGGING_CATEGORY(lcGraphIO, "socnetv.graph.io") /** * @brief Constructs a Graph */ Graph::Graph(const int &reserveVerticesSize, const int &reserveEdgesPerVertexSize) { qRegisterMetaType("MyEdge"); qRegisterMetaType("NetworkRequestType"); m_totalVertices = 0; m_totalEdges = 0; // We do init these two vars here, because they only get their values // on MW::resizeEvent which might happen after we have started creating // nodes. // For instance, this happens when we load a network from command line... canvasWidth = 700; canvasHeight = 600; order = true; // returns true if the indexes of the list is ordered. // Use the given vertices estimate to allocate memory // to prevent reallocations and memory fragmentation. if (reserveVerticesSize > 0) { qCDebug(lcGraph) << "Graph reserving this vertices estimate:" << reserveVerticesSize; m_graph.reserve(reserveVerticesSize); } // Store the m_reserveEdgesPerVertexSize = reserveEdgesPerVertexSize; m_graphModStatus = ModStatus::NewNet; m_graphName = ""; m_curRelation = 0; m_fileFormat = FileType::NOT_SAVED; m_graphIsDirected = true; m_graphIsWeighted = false; m_graphIsConnected = true; // empty/null graph is considered connected m_graphIsSymmetric = true; m_graphWeaklyConnectedComponents = 0; m_graphStronglyConnectedComponents = 0; m_vertexComponentId.clear(); m_graphDensity = -1; m_fileName = ""; calculatedGraphReciprocity = false; calculatedGraphSymmetry = false; calculatedGraphWeighted = false; calculatedGraphDensity = false; calculatedEdges = false; calculatedVertices = false; calculatedVerticesList = false; calculatedVerticesSet = false; calculatedAdjacencyMatrix = false; calculatedDistances = false; calculatedIsolates = false; calculatedDP = false; calculatedDC = false; calculatedIC = false; calculatedEVC = false; calculatedKC = false; calculatedBPC = false; calculatedCentralities = false; calculatedIRCC = false; calculatedPP = false; calculatedPRP = false; calculatedTriad = false; m_progressCanceled = false; m_reportsDataDir = ""; m_reportsRealPrecision = 6; m_reportsLabelLength = 8; m_reportsChartType = ChartType::Spline; m_reportsOutputFormat = ReportFormat::Html; m_vertexClicked = 0; m_clickedEdge.source = 0; m_clickedEdge.target = 0; m_visibilityHistory.clear(); // discard any pending filter snapshots file_parser = 0; web_crawler = 0; m_graphFileFormatExportSupported << FileType::GRAPHML << FileType::PAJEK << FileType::ADJACENCY << FileType::GRAPHVIZ << FileType::UCINET << FileType::EDGELIST_WEIGHTED << FileType::EDGELIST_SIMPLE; randomizeThings(); htmlHead = QString("" "" "" "" "" "" "" "" "" "" "" "" "") .arg(VERSION); htmlHeadLight = QString("" "" "" "" "" "" "" "" "" "" "") .arg(VERSION); htmlEnd = ""; } /** * @brief Destroys the Graph object */ Graph::~Graph() { qCDebug(lcGraph) << "Graph destructing (because app exit?)...Calling clear()"; this->clear("exit"); delete file_parser; } /** * @brief Clears all vertices * @param reason */ void Graph::clear(const QString &reason) { qCDebug(lcGraph) << "Clearing graph, vertices and data structures... Reason:" << reason; qCDebug(lcGraph) << "Asking parser and crawler threads to terminate..."; graphLoadedTerminateParserThreads("clear"); webCrawlTerminateThreads("clear"); qDeleteAll(m_graph.begin(), m_graph.end()); m_graph.clear(); vpos.clear(); discreteDPs.clear(); discreteSDCs.clear(); discreteCCs.clear(); discreteBCs.clear(); discreteSCs.clear(); discreteIRCCs.clear(); discreteECs.clear(); discreteEccentricities.clear(); discretePCs.clear(); discreteICs.clear(); discretePRPs.clear(); discretePPs.clear(); discreteEVCs.clear(); if (DM.size() > 0) { qCDebug(lcGraph) << "clearing DM matrix"; DM.clear(); } if (SIGMA.size() > 0) { qCDebug(lcGraph) << "clearing SIGMA matrix"; SIGMA.clear(); } if (invAM.size() > 0) { qCDebug(lcGraph) << "clearing invAM"; invAM.clear(); } if (AM.size() > 0) { qCDebug(lcGraph) << "clearing AM"; AM.clear(); } if (invM.size() > 0) { qCDebug(lcGraph) << "clearing invM"; invM.clear(); } if (XM.size() > 0) { qCDebug(lcGraph) << "clearing XM"; XM.clear(); } if (XSM.size() > 0) { qCDebug(lcGraph) << "clearing XSM"; XSM.clear(); } if (XRM.size() > 0) { qCDebug(lcGraph) << "clearing XRM"; XRM.clear(); } m_verticesList.clear(); m_verticesSet.clear(); m_verticesIsolatedList.clear(); m_vertexPairsNotConnected.clear(); m_vertexPairsUnilaterallyConnected.clear(); triadTypeFreqs.clear(); // clear relations relationsClear(); // add a default relation, only if we are not closing if (reason != "exit") { relationAdd(tr(("unnamed"))); } m_fileFormat = FileType::NOT_SAVED; m_graphName = ""; m_fileName = ""; m_totalVertices = 0; m_totalEdges = 0; outboundEdgesVert = 0; inboundEdgesVert = 0; reciprocalEdgesVert = 0; m_vertexClicked = 0; m_clickedEdge.source = 0; m_clickedEdge.target = 0; m_visibilityHistory.clear(); // discard any pending filter snapshots order = true; // returns true if the vpositions of the list is ordered. m_graphIsDirected = true; m_graphIsWeighted = false; m_graphIsConnected = true; // empty/null graph is considered connected. m_graphIsSymmetric = true; m_graphWeaklyConnectedComponents = 0; m_graphStronglyConnectedComponents = 0; m_vertexComponentId.clear(); m_graphDensity = -1; m_graphDiameter = 0; m_graphAverageDistance = 0; m_graphSumDistance = 0; m_graphGeodesicsCount = 0; // non zero distances calculatedGraphReciprocity = false; calculatedGraphSymmetry = false; calculatedGraphWeighted = false; calculatedGraphDensity = false; calculatedEdges = false; calculatedVertices = false; calculatedVerticesList = false; calculatedVerticesSet = false; calculatedAdjacencyMatrix = false; calculatedDistances = false; calculatedIsolates = false; calculatedCentralities = false; calculatedDP = false; calculatedDC = false; calculatedIC = false; calculatedEVC = false; calculatedKC = false; calculatedBPC = false; calculatedIRCC = false; calculatedPP = false; calculatedPRP = false; calculatedTriad = false; m_progressCanceled = false; m_graphModStatus = ModStatus::NewNet; // if ( urlQueue->size() > 0 ) { // urlQueue->clear(); // } if (reason != "exit") { qCDebug(lcGraph) << "Finished clearing graph data. Changing graph modification status to" << m_graphModStatus; setModStatus(m_graphModStatus, true); } qCDebug(lcGraph) << "Finished clearing graph data and structures."; } SocNetV-3.7/src/graph.h000077500000000000000000002041671524402210300147530ustar00rootroot00000000000000/** * @file graph.h * @brief Defines the Graph class and related algorithms for social network visualization. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef GRAPH_H #define GRAPH_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include "global.h" #include "graph/filters/filter_condition.h" #include "graph/filters/filter_spec.h" #include "graph/filters/graph_query.h" #include "graphvertex.h" #include "matrix.h" #include "parser.h" #include "webcrawler.h" // WS14: one logging category per src/graph// slice directory (centrality/, clustering/, // distances/, layouts/, storage/, etc.), declared here since graph.h is already included by every // slice .cpp, defined once in graph.cpp. Coarser than one-per-file (avoids ~60 near-empty categories // for small files) but finer than one blanket category (keeps independent toggling per domain via // QLoggingCategory::setFilterRules). lcGraph covers graph.cpp itself (the facade's own small file, // distinct from the core/ slice). lcMatrix (src/matrix.cpp, unrelated file) is deliberately not // reused here to avoid confusion with lcGraphMatrices (src/graph/matrices/). Q_DECLARE_LOGGING_CATEGORY(lcGraph) Q_DECLARE_LOGGING_CATEGORY(lcGraphCore) Q_DECLARE_LOGGING_CATEGORY(lcStorage) Q_DECLARE_LOGGING_CATEGORY(lcCentrality) Q_DECLARE_LOGGING_CATEGORY(lcClustering) Q_DECLARE_LOGGING_CATEGORY(lcDistances) Q_DECLARE_LOGGING_CATEGORY(lcProminence) Q_DECLARE_LOGGING_CATEGORY(lcReachability) Q_DECLARE_LOGGING_CATEGORY(lcSimilarity) Q_DECLARE_LOGGING_CATEGORY(lcLayouts) Q_DECLARE_LOGGING_CATEGORY(lcGenerators) Q_DECLARE_LOGGING_CATEGORY(lcGraphMatrices) Q_DECLARE_LOGGING_CATEGORY(lcCohesion) Q_DECLARE_LOGGING_CATEGORY(lcReporting) Q_DECLARE_LOGGING_CATEGORY(lcFilters) Q_DECLARE_LOGGING_CATEGORY(lcRelations) Q_DECLARE_LOGGING_CATEGORY(lcGraphCrawler) Q_DECLARE_LOGGING_CATEGORY(lcGraphUI) Q_DECLARE_LOGGING_CATEGORY(lcGraphIO) class QDateTime; class QPointF; class QNetworkReply; class QUrl; class QAbstractSeries; class QAbstractAxis; using namespace std; SOCNETV_USE_NAMESPACE class Chart; typedef QList VList; typedef QHash H_StrToInt; typedef QHash H_Int; typedef QHash H_f_i; typedef QPair pair_f_b; typedef QPair pair_i_fb; typedef QMultiHash H_edges; typedef QHash H_StrToBool; typedef QList L_int; typedef QList V_int; typedef QList V_str; /** * @brief The Graph class * This is the main class for a Graph, used in conjuction with GraphVertex, Parser and Matrix objects. * Graph class methods are the interface to various analysis algorithms * GraphVertex class holds each vertex data (colors, strings, statistics, etc) * Matrix class holds the adjacency matrix of the network. * Parser class loads files of networks. */ class Graph : public QObject { Q_OBJECT QThread file_parserThread; QThread webcrawlerThread; friend class DistanceEngine; public slots: // ============================================================================ // LEGACY/INTERNAL (UI / IO WIRING) // ---------------------------------------------------------------------------- // NOTE (WS2/F0): // Slots are part of Graph's coordinator role (signals/threads/UI wiring). // Engines/services should not depend on these directly. // ============================================================================ int relationCurrent(); QString relationCurrentName() const; void relationCurrentRename(const QString &newName); void relationCurrentRename(const QString &newName, const bool &signalMW); /** Slots to signals from Parser */ void vertexCreate(const int &number, const int &size, const QString &color, const QString &numColor, const int &numSize, const QString &label, const QString &labelColor, const int &labelSize, const QPointF &p, const QString &shape, const QString &iconPath = QString(), const bool &signalMW = false, const QHash &customAttributes = QHash()); void graphFileLoaded(const int &fileType, const QString &fName = QString(), const QString &netName = QString(), const int &totalNodes = 0, const int &totalLinks = 0, const int &edgeDirType = 0, const qint64 &elapsedTime = 0, const QString &message = QString()); void vertexRemoveDummyNode(int); void graphLoadedTerminateParserThreads(QString reason); void setSelectionChanged(const QList selectedVertices, const QList selectedEdges); void graphClickedEmptySpace(const QPointF &p); /** Slots to signals from GraphicsWidget and Parser*/ bool edgeCreate(const int &v1, const int &v2, const qreal &weight, const QString &color, const int &type = 0, const bool &drawArrows = true, const bool &bezier = false, const QString &label = QString(), const bool &signalMW = true, const QHash &edgeCustomAttributes = QHash()); void edgeCreateWebCrawler(const int &source, const int &target); // helper vertexCreate functions void vertexCreateAtPos(const QPointF &p); void vertexCreateAtPosRandom(const bool &signalMW = false); void vertexCreateAtPosRandomWithLabel(const int &i, const QString &label, const bool &signalMW = false); /** Slots to signals from MainWindow */ void relationSet(int relNum = RAND_MAX, const bool &updateUI = true); void relationNext(); void relationPrev(); void canvasSizeSet(const int &width, const int &height); void canvasSizeSetQuiet(const qreal &width, const qreal &height); double canvasMaxRadius() const; qreal canvasMinDimension() const; double canvasVisibleX(const double &x) const; double canvasVisibleY(const double &y) const; double canvasRandomX() const; double canvasRandomY() const; void vertexIsolatedAllToggle(const bool &toggle); void vertexClickedSet(const int &v, const QPointF &p); void edgeClickedSet(const int &v1, const int &v2, const bool &openMenu = false); void vertexFilterByCentrality(const float threshold, const bool overThreshold, const IndexType centralityIndex); void vertexFilterByEgoNetwork(const int v1, const int depth = 1); void vertexFilterBySelection(const QList &selectedVertices); void vertexFilterByAttribute(const FilterCondition &cond); void edgeFilterByAttribute(const FilterCondition &cond); void vertexFilterRestoreAll(); void vertexFilterRemoveAt(int stackIndex); QList filterSpecList() const; bool visibilityHistoryEmpty() const; void vertexFilterByQuery(const GraphQuery &query); void edgeFilterByQuery(const GraphQuery &query); void edgeFilterByWeight(const qreal, const bool); void edgeFilterReset(); void edgeFilterUnilateral(const bool &toggle); Graph *subgraphExtract(const QString &name, const bool &includeCustomAttributes = true); Graph *subgraphExtractFromSelection(const QString &name, const bool &includeCustomAttributes = true); void startWebCrawler( const QUrl &startUrl, const QStringList &urlPatternsIncluded, const QStringList &urlPatternsExcluded, const QStringList &linkClasses, const int &maxNodes, const int &maxLinksPerPage, const bool &intLinks, const bool &childLinks, const bool &parentLinks, const bool &selfLinks, const bool &extLinksIncluded, const bool &extLinksCrawl, const bool &socialLinks, const bool &delayedRequests); void slotHandleCrawlerRequestReply(); void webSpider(); void slotCancelComputation(); QString htmlEscaped(QString str) const; signals: void signalWebCrawlParse(QNetworkReply *reply); // ============================================================================ // LEGACY/INTERNAL (UI SIGNAL SURFACE) // ---------------------------------------------------------------------------- // NOTE (WS2/F0): Signals are a UI orchestration mechanism. Engines/services // must not emit/call UI-facing behavior directly. // ============================================================================ /** Signals to MainWindow */ void signalNetworkManagerRequest(const QUrl ¤tUrl, const NetworkRequestType &type); void signalGraphSavedStatus(const int &status); void signalGraphModified(const bool &undirected, const int &vertices, const int &edges, const qreal &density, const bool ¬Saved = true); void signalGraphLoaded(const int &fileType, const QString &fileName = QString(), const QString &netName = QString(), const int &totalNodes = 0, const int &totalLinks = 0, const qreal &density = 0, const qint64 &elapsedTime = 0, const QString &message = QString()); void statusMessage(const QString &message); void signalDatasetDescription(QString); void signalNodeClickedInfo(const int &number = 0, const QPointF &p = QPointF(), const QString &label = QString(), const int &inDegree = 0, const int &outDegree = 0); void signalEdgeClicked(const MyEdge &edge = MyEdge(), const bool &openMenu = false); void signalRelationAddToMW(const QString &newRelation); void signalRelationsClear(); void signalRelationRenamedToMW(const QString &newRelName); void signalRelationChangedToGW(int); void signalRelationChangedToMW(const int &relIndex = RAND_MAX); void signalGraphDirectedChanged(const bool &directed); void signalSelectionChanged(const int &selectedVertices, const int &selectedEdges); void signalPromininenceDistributionChartUpdate(QAbstractSeries *series, QAbstractAxis *axisX = Q_NULLPTR, const qreal &min = 0, const qreal &max = 0, QAbstractAxis *axisY = Q_NULLPTR, const qreal &minF = 0, const qreal &maxF = 0); /** Signals to GraphicsWidget */ void signalDrawNode(const QPointF &p, const int &num, const int &size, const QString &nodeShape, const QString &nodeIconPath, const QString &nodeColor, const QString &numberColor, const int &numSize, const int &numDistance, const QString &label, const QString &labelColor, const int &labelSize, const int &labelDistance); // signal to GW to erase a node void signalRemoveNode(int); // signal GW to draw an edge void signalDrawEdge(const int &v1, const int &v2, const qreal &weight, const QString &label = "", const QString &color = "black", const int &type = 0, const bool &drawArrows = true, const bool &bezier = false, const bool &weightNumbers = false); // signal to GW void signalRemoveEdge(const int &v1, const int &v2, const bool &removeReverse); void signalSetEdgeVisibility(const int &relation, const int &source, const int &target, const bool &toggle, const bool &preserveReverseEdge = false, const int &edgeWeight = 1, const int &reverseEdgeWeight = 1); // The last two are used only if we need to draw the edge // Bulk counterpart of signalSetEdgeVisibility (WS3 M2): a whole-graph operation like a // relation switch crosses to the GUI thread as one queued dispatch instead of one per edge. void signalSetEdgesVisibilityBatch(const QList &changes); void setVertexVisibility(const int &number, const bool &toggle); void setNodePos(const int &, const qreal &, const qreal &); void signalLayoutFinished(); void signalNodesFound(const QList foundList); void setNodeSize(const int &v, const int &size); void setNodeShape(const int &v, const QString &shape, const QString &iconPath = QString()); void setNodeColor(const int &v, const QString &color); void setNodeLabel(const int &v, const QString &label); void setNodeNumberColor(const int &v, const QString &color); void setNodeNumberSize(const int &v, const int &size); void setNodeNumberDistance(const int &v, const int &distance); void setNodeLabelSize(const int &v, const int &size); void setNodeLabelColor(const int &v, const QString &color); void setNodeLabelDistance(const int &v, const int &distance); void setEdgeWeight(const int &v1, const int &v2, const qreal &weight); void signalEdgeType(const int &v1, const int &v2, const int &type); void setEdgeColor(const int &v1, const int &v2, const QString &color); void setEdgeLabel(const int &v1, const int &v2, const QString &label); void addGuideCircle(const double &, const double &, const double &); void addGuideHLine(const double &y0); public: // ============================================================================ // GRAPH FACADE CONTRACT (WS2 / F0) // ---------------------------------------------------------------------------- // This section defines the *supported* API surface that UI and CLI code may // call going forward. // // Rules: // - New UI features MUST use only the "FACADE API (SUPPORTED)" surface. // - Engines/services MUST NOT call UI-oriented slots/signals. // - Anything explicitly marked LEGACY/INTERNAL is not allowed for new code, // even if it remains public for historical reasons. // // ============================================================================ enum ModStatus { NewNet = -1, SavedUnchanged = 0, MinorOptions = 1, VertexMetadata = 2, EdgeMetadata = 3, VertexPositions = 4, MajorChanges = 10, VertexCount = 11, EdgeCount = 12, VertexEdgeCount = 13, }; enum Clustering { Single_Linkage = 0, //"single-link" or minimum Complete_Linkage = 1, // "complete-link or maximum Average_Linkage = 2, // mean or "average-linkage" or UPGMA }; // -------------------------------------------------------------------------- // FACADE API (SUPPORTED): Visibility snapshot for non-destructive filtering. // Used by the filter system to save/restore vertex and edge visibility state. // Stored as a stack to support future undo/redo. // -------------------------------------------------------------------------- struct GraphVisibilitySnapshot { QHash nodeVisible; // vertex number β†’ was enabled QHash, bool> arcVisible; // (source,target)β†’ was visible bool active = false; // true when this snapshot holds real data FilterSpec spec; // replay descriptor for arbitrary chip removal }; /* INIT AND CLEAR*/ // -------------------------------------------------------------------------- // FACADE API (SUPPORTED): Lifecycle // -------------------------------------------------------------------------- Graph(const int &reserveVerticesSize = 5000, const int &reserveEdgesPerVertexSize = 500); ~Graph(); QThread *getThread() const; void moveToThreadFacade(QThread *thread); void clear(const QString &reason = ""); /*FILES (READ AND WRITE)*/ // -------------------------------------------------------------------------- // FACADE API (SUPPORTED): File identity / load-save // -------------------------------------------------------------------------- QString getFileName() const; void setFileName(const QString &fileName); QString getName() const; void setName(const QString &graphName); bool isSaved() const; bool isLoaded() const; int getFileFormat() const; void setFileFormat(const int &fileFormat); bool isFileFormatExportSupported(const int &fileFormat) const; void setModStatus(const int &graphNewStatus, const bool &signalMW = true); bool isModified() const; void loadFile(const QString fileName, const QString codecName, const int format, const QString delimiter = QString(), const int sm_two_mode = 1, const bool sm_has_labels = false); void saveToFile(const QString &fileName, const int &fileType, const bool &saveEdgeWeights = true, const bool &saveZeroWeightEdges = false); bool saveToPajekFormat(const QString &fileName, QString networkName = "", int maxWidth = 0, int maxHeight = 0); bool saveToAdjacencyFormat(const QString &fileName, const bool &saveEdgeWeights = true); bool saveToGraphMLFormat(const QString &fileName, const bool &saveZeroWeightEdges = false, QString networkName = "", int maxWidth = 0, int maxHeight = 0); bool saveToDotFormat(const QString &fileName); bool saveToUCINETDLFormat(const QString &fileName); bool saveToEdgeListWeightedFormat(const QString &fileName); bool saveToEdgeListSimpleFormat(const QString &fileName); QString graphMatrixTypeToString(const int &matrixType) const; int graphMatrixStrToType(const QString &matrix) const; QString graphMetricTypeToString(const int &metricType) const; int graphMetricStrToType(const QString &metricStr) const; QString graphClusteringMethodTypeToString(const int &methodType) const; int graphClusteringMethodStrToType(const QString &method) const; /* RELATIONS */ // -------------------------------------------------------------------------- // FACADE API (SUPPORTED): Relations // -------------------------------------------------------------------------- int relations(); void relationsClear(); void relationAdd(const QString &relName, const bool &changeRelation = false); /* VERTICES */ // -------------------------------------------------------------------------- // FACADE API (SUPPORTED): Vertex queries + edits // -------------------------------------------------------------------------- int vertexIndexByNumber(int v) const; // LEGACY/INTERNAL (ENGINE SUPPORT): // Access a vertex by internal storage index (vpos result). // No bounds checks: preserves existing behavior of direct m_graph[idx] usage. GraphVertex *vertexAtIndex(int idx); const GraphVertex *vertexAtIndex(int idx) const; VList::const_iterator verticesBegin() const; VList::const_iterator verticesEnd() const; int vertexNumberMax(); int vertexNumberMin(); int vertexDegreeOut(int); int vertexDegreeIn(int); QList vertexReciprocalNeighborsList(const int &v1); QSet vertexReciprocalNeighborsSet(const int &v1); QSet vertexOutNeighborsSet(const int &v1, const bool includeInEdges = false); bool vertexIsolated(const int &v1) const; bool vertexExists(const int &v1); int vertexIndexIfExists(const int &v1); bool vertexExists(const QString &label); int vertexIndexIfExists(const QString &label); bool vertexFindByNumber(const QStringList &numList); bool vertexFindByLabel(const QStringList &labelList); bool vertexFindByIndexScore(const int &index, const QStringList &thresholds, const bool &considerWeights, const bool &inverseWeights = false, const bool &dropIsolates = false); void vertexRemove(const int &v1); void vertexSizeInit(const int); void vertexSizeSet(const int &v, const int &newsize); int vertexSize(const int &v) const; void vertexShapeSetDefault(const QString, const QString &iconPath = QString()); void vertexShapeSet(const int &v, const QString &shape, const QString &iconPath = QString()); QString vertexShape(const int &v); QString vertexShapeIconPath(const int &v); bool graphHasVertexCustomIcons() const; QStringList graphHasVertexCustomAttributes() const; void vertexColorInit(const QString &color); void vertexColorSet(const int &v, const QString &color); QColor vertexColor(const int &v) const; void vertexNumberColorInit(const QString &color); void vertexNumberColorSet(const int &v = 0, const QString &color = "#000000"); void vertexNumberSizeInit(const int &size); void vertexNumberSizeSet(const int &v, const int &newsize); void vertexNumberDistanceInit(const int &distance); void vertexNumberDistanceSet(const int &v, const int &newDistance); void vertexLabelSet(const int &v, const QString &label); QString vertexLabel(const int &v) const; void vertexLabelsVisibilitySet(bool toggle); void vertexLabelSizeInit(int newSize); void vertexLabelSizeSet(const int &v, const int &labelSize); void vertexLabelColorInit(QString color); void vertexLabelColorSet(const int &v1, const QString &color); void vertexLabelDistanceInit(const int &distance); void vertexLabelDistanceSet(const int &v, const int &newDistance); void vertexLabelDistanceAllSet(const int &newDistance); void vertexCustomAttributesSet(const int &v1, const QHash &customAttributes); void vertexCustomAttributeSet(const int &v1, const QString &key, const QString &value); void vertexCustomAttributeRemove(const int &v1, const QString &key); QHash vertexCustomAttributes(const int &v1) const; int vertexAttributesImport(const QStringList &headers, const QVector &rows, int idColumn, bool matchByLabel); void vertexPosSet(const int &v, const int &x, const int &y); QPointF vertexPos(const int &v1) const; int vertexClicked() const; int vertices(const bool &dropIsolates = false, const bool &countAll = false, const bool &recount = false); int vertexEdgesOutbound(int i); int vertexEdgesInbound(int i); int verticesWithOutboundEdges(); int verticesWithInboundEdges(); int verticesWithReciprocalEdges(); QList verticesListIsolated(); QList verticesList(); QSet verticesSet(); void verticesCreateSubgraph(QList vList, const int &type = SUBGRAPH_CLIQUE, const int ¢er = 0); // Regression/testing helper: access a vertex object by its number. // Returns nullptr if not found. GraphVertex *vertexPtr(const int v); /* EDGES */ // -------------------------------------------------------------------------- // FACADE API (SUPPORTED): Edge queries + edits // -------------------------------------------------------------------------- int edgesEnabled(); MyEdge edgeClicked(); qreal edgeExists(const int &v1, const int &v2, const bool &checkReciprocal = false); qreal edgeExistsVirtual(const int &v1, const int &v2); void edgeOutboundStatusSet(const int &source, const int &target, const bool &toggle = false); void edgeInboundStatusSet(const int &target, const int &source, const bool &toggle = false); // Plain (non-signal) relay points for GraphVertex, a QtCore-only value class holding a // Graph* rather than a QObject connection (WS3 M2). Each just emits the corresponding // signal below - this is what lets GraphVertex notify the UI layer without being a // QObject itself. void notifyEdgeVisibilityChanged(const int &relation, const int &source, const int &target, const bool &toggle, const bool &preserveReverseEdge = false, const int &edgeWeight = 1, const int &reverseEdgeWeight = 1); void notifyEdgesVisibilityBatch(const QList &changes); void edgeRemove(const int &v1, const int &v2, const bool &removeReverse = false); void edgeRemoveSelected(SelectedEdge &selectedEdge, const bool &removeReverse); void edgeRemoveSelectedAll(); bool edgeSymmetric(const int &v1, const int &v2); void edgeTypeSet(const int &v1, const int &v2, const qreal &w, const int &dirType = EdgeType::Directed); void edgeWeightSet(const int &v1, const int &v2, const qreal &w, const bool &undirected = false); qreal edgeWeight(const int &v1, const int &v2) const; void edgeWeightNumbersVisibilitySet(const bool &toggle); void edgeLabelSet(const int &v1, const int &v2, const QString &label); QString edgeLabel(const int &v1, const int &v2) const; void edgeLabelsVisibilitySet(const bool &toggle); void edgeColorInit(const QString &); void edgeColorZeroInit(const QString &); // #30: configurable zero-weight edge color void showZeroWeightEdgesSet(const bool &toggle); // #30: show/hide zero-weight edges void edgeColorSet(const int &v1, const int &v2, const QString &color); QString edgeColor(const int &v1, const int &v2); bool edgeColorAllSet(const QString &color, const int &threshold = RAND_MAX); void edgeCustomAttributesSet(const int &v1, const int &v2, const QHash &attrs); QHash edgeCustomAttributes(const int &v1, const int &v2) const; int edgeAttributesImport(const QStringList &headers, const QVector &rows, int srcColumn, int tgtColumn); QStringList graphHasEdgeCustomAttributes() const; /* GRAPH methods */ // -------------------------------------------------------------------------- // FACADE API (SUPPORTED): Graph facts + settings used by UI/CLI // -------------------------------------------------------------------------- bool isEmpty() const; QList getSelectedVertices() const; int getSelectedVerticesCount() const; int getSelectedVerticesMin() const; int getSelectedVerticesMax() const; QList getSelectedEdges() const; int getSelectedEdgesCount() const; int getGeodesicsCount(); qreal graphDensity(); bool isWeighted(); bool isAnyRelationWeighted(); void setWeighted(const bool &toggle = true); qreal graphReciprocity(); bool isSymmetric(); void setSymmetric(); void addRelationSymmetricStrongTies(const bool &allRelations = false); void relationAddCocitation(); void graphDichotomization(const qreal threshold); void setDirected(const bool &toggle = true, const bool &signalMW = true); void setUndirected(const bool &toggle = true, const bool &signalMW = true); bool isDirected(); bool isUndirected(); bool isConnected(); bool isConnectedCached() const; int graphWeaklyConnectedComponents(); int graphWeaklyConnectedComponentsCached() const; int graphStronglyConnectedComponents(); int graphStronglyConnectedComponentsCached() const; const QHash &vertexComponentId() const { return m_vertexComponentId; } // -------------------------------------------------------------------------- // FACADE API (SUPPORTED): Vertex connectivity (Menger's theorem via max-flow, #7). // -------------------------------------------------------------------------- enum class NodeConnectivityStatus { Ok, // value holds the local vertex connectivity (>=0; 0 means unreachable) Adjacent, // source and target are directly connected by an edge - no finite vertex // cut exists (Menger's theorem requires non-adjacency); value is meaningless Invalid // source/target don't exist, or source == target }; struct NodeConnectivityResult { NodeConnectivityStatus status = NodeConnectivityStatus::Invalid; int value = 0; }; NodeConnectivityResult graphNodeConnectivity(int source, int target, bool respectDirection); int graphConnectivity(bool respectDirection); // WS6.7: read-only-by-convention accessors for kernel_matrix_v8's golden coverage. // Non-const because Matrix::item()/rows()/cols() are themselves non-const throughout. Matrix &matrixAdjacency() { return AM; } Matrix &matrixAdjacencyInverse() { return invAM; } Matrix &matrixDistances() { return DM; } Matrix &matrixReachability() { return XRM; } Matrix &matrixWalks() { return XM; } Matrix &matrixTotalWalks() { return XSM; } Matrix &matrixCliqueCoMembership() { return CLQM; } void createMatrixAdjacency(const bool dropIsolates = false, const bool considerWeights = true, const bool inverseWeights = false, const bool symmetrize = false); bool createMatrixAdjacencyInverse(const QString &method = "lu"); void createMatrixSimilarityMatching(Matrix &AM, Matrix &SEM, const int &measure = METRIC_SIMPLE_MATCHING, const QString &varLocation = "Rows", const bool &diagonal = false, const bool &considerWeights = true); void createMatrixSimilarityPearson(Matrix &AM, Matrix &PCC, const QString &varLocation = "Rows", const bool &diagonal = false); void createMatrixDissimilarities(Matrix &INPUT_MATRIX, Matrix &DSM, const int &metric, const QString &varLocation, const bool &diagonal, const bool &considerWeights); /* REPORT EXPORTS */ void setReportsDataDir(const QString &reportsDir); void setReportsRealNumberPrecision(const int &precision); void setReportsLabelLength(const int &length); void setReportsChartType(const int &type); void setReportsOutputFormat(const int &format); void writeDataSetToFile(const QString dir, const QString); void writeMatrixAdjacencyTo(QTextStream &os, const bool &saveEdgeWeights = true); bool writeReciprocity(const QString fileName, const bool considerWeights = false, const int &format = ReportFormat::Html); bool writeMatrix(const QString &fileName, const int &matrix = MATRIX_ADJACENCY, const bool &considerWeights = true, const bool &inverseWeights = false, const bool &dropIsolates = false, const QString &varLocation = "Rows", const bool &simpler = false, const int &format = ReportFormat::Html); void writeMatrixHTMLTable(QTextStream &outText, Matrix &M, const bool &markDiag = true, const bool &plain = false, const bool &printInfinity = true, const bool &dropIsolates = false); void writeMatrixCSVTable(QTextStream &outText, Matrix &M, const bool &printInfinity = true, const bool &dropIsolates = false); bool writeMatrixAdjacency(const QString fileName, const bool &markDiag = true, const int &format = ReportFormat::Html); void writeMatrixAdjacencyPlot(const QString fileName, const bool &simpler = false); bool writeMatrixDissimilarities(const QString fileName, const QString &metricStr, const QString &varLocation, const bool &diagonal, const bool &considerWeights, const int &format = ReportFormat::Html); bool writeMatrixSimilarityMatching(const QString fileName, const QString &measure = "Simple", const QString &matrix = "adjacency", const QString &varLocation = "rows", const bool &diagonal = false, const bool &considerWeights = true, const int &format = ReportFormat::Html); bool writeMatrixSimilarityPearson(const QString fileName, const bool considerWeights, const QString &matrix = "adjacency", const QString &varLocation = "rows", const bool &diagonal = false, const int &format = ReportFormat::Html); bool writeEccentricity(const QString fileName, const bool considerWeights = false, const bool inverseWeights = false, const bool dropIsolates = false, const int &format = ReportFormat::Html); // friend QTextStream& operator << (QTextStream& os, Graph& m); // Shared per-node score-table renderer for the centrality/prestige and (WS16 Step 3) // Reciprocity/Clustering Coefficient/Eccentricity report families. "Node" and "Label" // columns are fixed; dataColumnHeaders supplies the rest, in order. rowValues returns // that row's already-computed scores, in the same order as dataColumnHeaders - a value // equal to RAND_MAX renders as the infinity glyph, matching writeMatrixHTMLTable/ // writeMatrixCSVTable's existing sentinel convention. isBlanked (optional; null means // "never blank", matching writeCentralityEigenvector's existing behaviour) decides // whether a row's data columns are replaced with a placeholder - e.g. dropped isolates - // preserving each report's current isolate-handling exactly rather than unifying it. // isSkipped (optional) omits the row entirely - distinct from isBlanked - matching // writeEccentricity()'s existing "don't print disabled nodes at all" behaviour. void writeScoreTableHTML(QTextStream &outText, const QStringList &dataColumnHeaders, const std::function(GraphVertex *)> &rowValues, const std::function &isBlanked = nullptr, const std::function &isSkipped = nullptr); void writeScoreTableCSV(QTextStream &outText, const QStringList &dataColumnHeaders, const std::function(GraphVertex *)> &rowValues, const std::function &isBlanked = nullptr, const std::function &isSkipped = nullptr); bool writeCentralityDegree(const QString, const bool weights, const bool dropIsolates, const int &format = ReportFormat::Html); bool writeCentralityCloseness(const QString, const bool weights, const bool inverseWeights, const bool dropIsolates, const int &format = ReportFormat::Html); bool writeCentralityClosenessInfluenceRange(const QString, const bool weights, const bool inverseWeights, const bool dropIsolates, const int &format = ReportFormat::Html); bool writeCentralityBetweenness(const QString, const bool weights, const bool inverseWeights, const bool dropIsolates, const int &format = ReportFormat::Html); bool writeCentralityPower(const QString, const bool weigths, const bool inverseWeights, const bool dropIsolates, const int &format = ReportFormat::Html); bool writeCentralityStress(const QString, const bool weigths, const bool inverseWeights, const bool dropIsolates, const int &format = ReportFormat::Html); bool writeCentralityEccentricity(const QString, const bool weigths, const bool inverseWeights, const bool dropIsolates, const int &format = ReportFormat::Html); bool writeCentralityInformation(const QString, const bool weigths, const bool inverseWeights, const int &format = ReportFormat::Html); bool writeCentralityEigenvector(const QString, const bool &weigths = true, const bool &inverseWeights = false, const bool &dropIsolates = false, const int &format = ReportFormat::Html); bool writeCentralityKatz(const QString, const qreal &alpha, const bool &weigths = false, const bool &inverseWeights = false, const bool &dropIsolates = false, const int &format = ReportFormat::Html); bool writeCentralityBonacich(const QString, const qreal &alpha, const qreal &beta, const bool &weigths = false, const bool &inverseWeights = false, const bool &dropIsolates = false, const int &format = ReportFormat::Html); bool writePrestigeDegree(const QString, const bool weights, const bool dropIsolates, const int &format = ReportFormat::Html); bool writePrestigeProximity(const QString, const bool weights, const bool inverseWeights, const bool dropIsolates, const int &format = ReportFormat::Html); bool writePrestigePageRank(const QString, const bool Isolates = false, const int &format = ReportFormat::Html); // HTML-only, permanently (WS16 Step 3): produces an equivalence matrix plus a dendrogram, // neither of which is a single flat table. bool writeClusteringHierarchical(const QString &fileName, const QString &varLocation, const QString &matrix = "Adjacency", const QString &metric = "Manhattan", const QString &method = "Complete", const bool &diagonal = false, const bool &dendrogram = false, const bool &considerWeights = true, const bool &inverseWeights = false, const bool &dropIsolates = false); void writeClusteringHierarchicalResultsToStream(QTextStream &outText, const int N, const bool &dendrogram = false); // HTML-only, permanently (WS16 Step 3): writeCliqueCensus() combines 4 heterogeneous // sub-tables (clique list, actor-by-clique matrix, actor-by-actor co-membership matrix, // and a full hierarchical-clustering dendrogram sub-report) that don't reduce to one // flat CSV table. bool writeCliqueCensus(const QString &fileName, const bool considerWeights); bool writeClusteringCoefficient(const QString, const bool, const int &format = ReportFormat::Html); bool writeTriadCensus(const QString, const bool, const int &format = ReportFormat::Html); /* DISTANCES, CENTRALITIES & PROMINENCE MEASURES */ int graphConnectednessFull(const bool updateProgress = false); bool graphReachable(const int &v1, const int &v2); void createMatrixReachability(); int graphDiameter(const bool considerWeights, const bool inverseWeights); int graphDiameterCached() const; qreal graphSumDistanceCached() const; qreal graphGeodesicsCountCached() const; int graphDistanceGeodesic(const int &v1, const int &v2, const bool &considerWeights = false, const bool &inverseWeights = true); // WS5 A2: read-only accessors into m_apspDist/m_apspSigma (the flat-matrix APSP storage // DistanceEngine populates) for the current relation. Unlike graphDistanceGeodesic() above, // these never trigger a recompute - callers are expected to have already run // graphDistancesGeodesic() themselves. Returns RAND_MAX / 0 respectively if either vertex // number is unknown or nothing has been computed yet for the current relation. qreal apspDistance(const int &v1, const int &v2); int apspShortestPaths(const int &v1, const int &v2); QMap graphGeodesicDistanceDistribution(const bool &considerWeights = false, const bool &inverseWeights = false); bool writeGeodesicDistribution(const QString &fileName, const bool &considerWeights = false, const bool &inverseWeights = false); QList graphGeodesicShortestPath(const int &v1, const int &v2, const bool &considerWeights = false, const bool &inverseWeights = false); qreal graphDistanceGeodesicAverage(const bool considerWeights, const bool inverseWeights, const bool dropIsolates); qreal graphDistanceGeodesicAverageCached() const; void graphDistancesGeodesic(const bool &computeCentralities = false, const bool &considerWeights = false, const bool &inverseWeights = true, const bool &dropIsolates = false); // ============================================================================ // --- Connectivity bookkeeping --- void notConnectedPairsClear(); void notConnectedPairsInsert(int from, int to); int notConnectedPairsSize() const; // --- Distance centrality cache flags --- // CLI/benchmark helper: allows repeated runs by clearing the computed flags only. // Does not modify graph structure or results. void resetDistanceCentralityCacheFlags(); // LEGACY/INTERNAL (ENGINE SUPPORT): cached results written by DistanceEngine void setSymmetricCached(bool v); bool symmetricCached() const; void setConnectedCached(bool v); void setDiameterCached(int v); void resetDistanceAggregates(); // sets avg/sum/geodesics/diameter to 0 void addToDistanceSum(qreal delta); void incGeodesicsCount(); // Bulk-add n to the geodesics count β€” used by the post-parallel-loop reduction // so each thread contributes its total in one call instead of n individual increments. void addGeodesicsCount(int n); void setAverageDistanceCached(qreal v); bool graphMatrixDistanceGeodesicCreate(const bool &considerWeights = false, const bool &inverseWeights = false, const bool &dropIsolates = false); void graphMatrixShortestPathsCreate(const bool &considerWeights = false, const bool &inverseWeights = true, const bool &dropIsolates = false); int getProminenceIndexByName(const QString &prominenceIndexName); void prominenceDistribution(const int &index, const ChartType &type, const QString &distImageFileName = QString()); void prominenceDistributionBars(const H_StrToInt &discreteClasses, const QString &name, const QString &distImageFileName); void prominenceDistributionArea(const H_StrToInt &discreteClasses, const QString &name, const QString &distImageFileName); void prominenceDistributionSpline(const H_StrToInt &discreteClasses, const QString &seriesName, const QString &distImageFileName); void centralityDegree(const bool &considerWeights = true, const bool &dropIsolates = false); void centralityInformation(const bool considerWeights = false, const bool inverseWeights = false); void centralityEigenvector(const bool &considerWeights = false, const bool &inverseWeights = false, const bool &dropIsolates = false); qreal estimateSpectralRadius(const bool &considerWeights = false, const bool &inverseWeights = false, const bool &dropIsolates = false); void centralityKatz(const qreal &alpha, const bool &considerWeights = false, const bool &inverseWeights = false, const bool &dropIsolates = false); void centralityBonacich(const qreal &alpha, const qreal &beta, const bool &considerWeights = false, const bool &inverseWeights = false, const bool &dropIsolates = false); void centralityClosenessIR(const bool considerWeights = false, const bool inverseWeights = false, const bool dropIsolates = false); void prestigeDegree(const bool &considerWeights, const bool &dropIsolates = false); void prestigePageRank(const bool &dropIsolates = false); void prestigeProximity(const bool considerWeights = false, const bool inverseWeights = false, const bool dropIsolates = false); bool isCentralityIndexComputed(const IndexType index) const; /* REACHABILITY AND WALKS */ int walksBetween(int v1, int v2, int length); void graphWalksMatrixCreate(const int &N = 0, const int &length = 0, const bool &dropIsolates = false, const bool &considerWeights = false, const bool &inverseWeights = false, const bool &symmetrize = false); bool writeMatrixWalks(const QString &fn, const int &length = 0, const bool &simpler = false, const int &format = ReportFormat::Html); qreal numberOfTriples(int v1); /* CLIQUES, CLUSTERING, TRIADS */ void graphCliques(QSet R = QSet(), QSet P = QSet(), QSet X = QSet()); void graphCliqueAdd(const QList &clique); int graphCliquesContaining(const int &actor, const int &size = 0); int graphCliquesOfSize(const int &size); bool graphClusteringHierarchical(Matrix &STR_EQUIV, const QString &varLocation, const int &metric, const int &method, const bool &diagonal = false, const bool &diagram = false, const bool &considerWeights = true, const bool &inverseWeights = false, const bool &dropIsolates = false); qreal clusteringCoefficientLocal(const int &v1); qreal clusteringCoefficient(); bool graphTriadCensus(); void triadType_examine_MAN_label(int, int, int, GraphVertex *, GraphVertex *, GraphVertex *); // --- Triad census results (read-only access for CLI / reports) --- const QList &graphTriadTypeFreqs() const { return triadTypeFreqs; } bool hasCalculatedTriadCensus() const { return calculatedTriad; } // void eccentr_JordanCenter(); // TODO /* LAYOUTS */ void layoutRandom(); void layoutRadialRandom(const bool &guides = true); void layoutEgoRadial(const int egoVertex); void layoutCircular(const double &x0, const double &y0, const double &newRadius, const bool &guides = false); void layoutByProminenceIndex(int prominenceIndex, int layoutType, const bool &considerWeights = false, const bool &inverseWeights = false, const bool &dropIsolates = false); void layoutVertexSizeByIndegree(); void layoutVertexSizeByOutdegree(); void layoutForceDirectedSpringEmbedder(const int maxIterations); void layoutForceDirectedFruchtermanReingold(const int maxIterations); void layoutForceDirectedKamadaKawai(const int maxIterations = 500, const bool considerWeights = false, const bool inverseWeights = false, const bool dropIsolates = false, const QString &initialPositions = "current"); qreal graphDistanceEuclidean(const QPointF &a, const QPointF &b); qreal graphDistanceEuclidean(const QPointF &a); int sign(const qreal &D); qreal layoutForceDirected_F_rep(const QString model, const qreal &dist, const qreal &optimalDistance); qreal layoutForceDirected_F_att(const QString model, const qreal &dist, const qreal &optimalDistance); qreal layoutForceDirected_Eades_moveNodes(const qreal &c4); qreal layoutForceDirected_FR_moveNodes(const qreal &temperature); qreal layoutForceDirected_FR_temperature(const int iteration) const; qreal computeOptimalDistance(const int &V); void compute_angles(const QPointF &Delta, const qreal &dist, qreal &angle1, qreal &angle2, qreal °rees1, qreal °rees2); /* CRAWLER */ void webCrawlTerminateThreads(QString reason); /**RANDOM NETWORKS*/ void randomizeThings(); bool randomNetErdosCreate(const int &N, const QString &model, const int &m, const qreal &p, const QString &mode, const bool &diag); bool randomNetScaleFreeCreate(const int &N, const int &power, const int &m0, const int &m, const qreal &alpha, const QString &mode); bool randomNetSmallWorldCreate(const int &N, const int °ree, const double &beta, const QString &mode); bool randomNetRingLatticeCreate(const int &N, const int °ree, const bool updateProgress = false); bool randomNetRegularCreate(const int &N, const int °ree, const QString &mode, const bool &diag); bool randomNetLatticeCreate(const int &N, const int &length, const int &dimension, const int &nei, const QString &mode, const bool &circular); int factorial(int); // Progress cancellation query: readable by engines and sinks. bool progressCanceled() const; void resetProgressCanceled(); /** vpos stores the real position of each vertex inside m_graph. * It starts at zero (0). * We need to know the place of a vertex inside m_graph after adding * or removing many vertices */ // // LEGACY/INTERNAL: storage bookkeeping. // Do not use from UI/engines. (Later WS2 will likely privatize this and // expose intent-revealing helpers if needed.) H_Int vpos; // -------------------------------------------------------------------------- // INTERNAL PROGRESS FACADE (WS2/F4) // Algorithm slices must not emit signals directly. // -------------------------------------------------------------------------- protected: void progressStatus(const QString &msg); void runOnGuiThread(std::function fn); void uiProminenceDistributionSpline(const QVector> &points, qreal min, qreal max, qreal minF, qreal maxF, const QString &seriesName, const QString &distImageFileName); void uiProminenceDistributionArea(const QVector> &points, const qreal min, const qreal max, const qreal minF, const qreal maxF, const QString &name, const QString &distImageFileName); void uiProminenceDistributionBars(const QStringList &categories, const QVector &frequencies, const qreal min, const qreal max, const qreal minF, const qreal maxF, const QString &name, const QString &distImageFileName); private: /** private member functions */ Graph *subgraphFromVertexList(const QList &vertexNums, const QString &name, const bool &includeCustomAttributes = true); void applyVisibilitySnapshot(const GraphVisibilitySnapshot &snap); void vertexFilterReplaySpec(const FilterSpec &spec); void edgeAdd(const int &v1, const int &v2, const qreal &weight, const int &type, const QString &label, const QString &color); /** methods used by graphDistancesGeodesic() */ void dijkstra(const int &s, const int &si, const bool &computeCentralities = false, const bool &inverseWeights = false, const bool &dropIsolates = false); void minmax(qreal C, GraphVertex *v, qreal &max, qreal &min, int &maxNode, int &minNode); void resolveClasses(qreal C, H_StrToInt &discreteClasses, int &classes); void resolveClasses(qreal C, H_StrToInt &discreteClasses, int &classes, int name); void layoutRandomInMemory(); VList m_graph; // List of pointers to the vertices. Each vertex stores all info: links, colors, etc Parser *file_parser; // Our file loader threaded class. WebCrawler *web_crawler; // Our web crawler threaded class. This will parse the downloaded HTML. QQueue *urlQueue; // A queue where the crawler will put urls for the network manager to download int m_crawler_max_urls; // maximum urls we'll visit (max nodes in the resulted network) int m_crawler_visited_urls; // A counter of the urls visited. QList m_relationsList; QList m_relationsDirected; QList m_graphFileFormatExportSupported; QList triadTypeFreqs; // stores triad type frequencies QList m_verticesList; QList m_verticesIsolatedList; QList m_verticesSelected; QSet m_verticesSet; QList m_selectedEdges; QStack m_visibilityHistory; // filter undo stack QMultiHash m_vertexPairsNotConnected; QHash m_vertexPairsUnilaterallyConnected; QMultiMap m_cliques; QHash> neighboursHash; QList m_clusteringLevel; QMap m_clustersPerSequence; QMap m_clustersByName; QMap m_clusterPairNamesPerSeq; Matrix SIGMA, DM, invAM, AM, invM, WM; Matrix XM, XSM, XRM, CLQM; // WS5 A2: relation-keyed flat-matrix APSP storage, replacing GraphVertex's per-vertex // QHash>. Row = source vertex position, column = target vertex // position (see Graph::vertexIndexByNumber()/vertexAtIndex()), not vertex number. QHash m_apspDist; QHash m_apspSigma; /** used in resolveClasses and graphDistancesGeodesic() */ H_StrToInt discreteDPs, discreteSDCs, discreteCCs, discreteBCs, discreteSCs; H_StrToInt discreteIRCCs, discreteECs, discreteEccentricities; H_StrToInt discretePCs, discreteICs, discretePRPs, discretePPs, discreteEVCs; H_StrToInt discreteCLCs, discreteKCs, discreteBPCs; QString m_reportsDataDir; int m_reportsRealPrecision; int m_reportsLabelLength; ChartType m_reportsChartType; ReportFormat m_reportsOutputFormat; int m_fieldWidth, m_curRelation, m_fileFormat, m_vertexClicked; MyEdge m_clickedEdge; qreal edgeWeightTemp, edgeReverseWeightTemp; qreal meanSDC, varianceSDC; qreal meanSCC, varianceSCC; qreal meanIRCC, varianceIRCC; qreal meanSBC, varianceSBC; qreal meanSSC, varianceSSC; qreal meanEC, varianceEC; qreal meanSPC, varianceSPC; qreal meanIC, varianceIC; qreal meanEVC, varianceEVC; qreal meanKC, varianceKC; qreal meanBPC, varianceBPC; qreal meanSDP, varianceSDP; qreal meanPP, variancePP; qreal meanPRP, variancePRP; qreal minEccentricity, maxEccentricity; qreal minSDP, maxSDP, sumDP, sumSDP, groupDP; qreal minSDC, maxSDC, sumDC, sumSDC, groupDC; qreal minSCC, maxSCC, nomSCC, denomSCC, sumCC, sumSCC, groupCC, maxIndexCC; qreal minIRCC, maxIRCC, nomIRCC, denomIRCC, sumIRCC, groupIRCC; qreal minSBC, maxSBC, nomSBC, denomSBC, sumBC, sumSBC, groupSBC, maxIndexBC; qreal minSPC, maxSPC, nomSPC, denomSPC, t_sumIC, sumSPC, groupSPC, maxIndexPC; qreal minSSC, maxSSC, sumSC, sumSSC, groupSC, maxIndexSC; qreal minEC, maxEC, nomEC, denomEC, sumEC, groupEC, maxIndexEC; qreal minIC, maxIC, nomIC, denomIC, sumIC, maxIndexIC; qreal minEVC, maxEVC, nomEVC, denomEVC, sumEVC, sumSEVC, groupEVC; qreal minKC, maxKC, sumKC; qreal m_lastKatzAlpha = -1; ///< Cache of the last alpha used to compute Katz Centrality, ///< read by layoutByProminenceIndex() (WS11, #10) since the ///< generic layout-by-prominence dispatch has no parameter slot. ///< -1 means "never computed this session". qreal minBPC, maxBPC, sumBPC; qreal m_lastBonacichAlpha = -1; ///< Same caching purpose as m_lastKatzAlpha (WS11, #39); ///< -1 means "never computed this session". Alpha is kept ///< positive-only by the dialog so this sentinel is safe - ///< beta (which can be negative) has no sentinel role. qreal m_lastBonacichBeta = 0; qreal minPRP, maxPRP, nomPRC, denomPRC, sumPC, t_sumPRP, sumPRP; qreal minPP, maxPP, nomPP, denomPP, sumPP, groupPP; qreal minCLC, maxCLC, averageCLC, varianceCLC, d_factor; int maxNodeCLC, minNodeCLC; int classesSDP, maxNodeDP, minNodeDP; int classesSDC, maxNodeSDC, minNodeSDC; int classesSCC, maxNodeSCC, minNodeSCC; int classesIRCC, maxNodeIRCC, minNodeIRCC; int classesSBC, maxNodeSBC, minNodeSBC; int classesSPC, maxNodeSPC, minNodeSPC; int classesSSC, maxNodeSSC, minNodeSSC; int classesEC, maxNodeEC, minNodeEC; int classesEccentricity, maxNodeEccentricity, minNodeEccentricity; int classesIC, maxNodeIC, minNodeIC; int classesPRP, maxNodePRP, minNodePRP; int classesPP, maxNodePP, minNodePP; int classesCLC; int classesEVC, maxNodeEVC, minNodeEVC; int classesKC, maxNodeKC, minNodeKC; int classesBPC, maxNodeBPC, minNodeBPC; /** General & initialisation variables */ int m_graphModStatus; int m_reserveEdgesPerVertexSize; int m_totalVertices, m_totalEdges, m_graphDiameter, initVertexSize; int initVertexLabelSize, initVertexNumberSize; int initVertexNumberDistance, initVertexLabelDistance; bool order; bool initEdgeWeightNumbers, initEdgeLabels; qreal m_graphAverageDistance, m_graphGeodesicsCount; qreal m_graphDensity; qreal m_graphSumDistance; qreal m_graphReciprocityArc, m_graphReciprocityDyad; int m_graphReciprocityTiesReciprocated; int m_graphReciprocityTiesNonSymmetric; int m_graphReciprocityTiesTotal; int m_graphReciprocityPairsReciprocated; int m_graphReciprocityPairsTotal; bool m_graphHasVertexCustomIcons; int outboundEdgesVert, inboundEdgesVert, reciprocalEdgesVert; qreal canvasWidth, canvasHeight; bool calculatedEdges; bool calculatedVertices, calculatedVerticesList, calculatedVerticesSet; bool m_verticesCacheDropIsolates = false, m_verticesCacheCountAll = false; bool calculatedAdjacencyMatrix, calculatedDistances, calculatedCentralities; bool calculatedIsolates; bool calculatedEVC; bool calculatedKC; bool calculatedBPC; bool calculatedDP, calculatedDC, calculatedPP; bool calculatedIRCC, calculatedIC, calculatedPRP; bool calculatedTriad; bool calculatedGraphSymmetry, calculatedGraphReciprocity; bool calculatedGraphDensity, calculatedGraphWeighted; // Written by slotCancelComputation() (GUI thread, via Qt::DirectConnection) and read by // progressCanceled() (graphThread, mid-computation) - see WS15's P1 for why a plain bool and a // queued connection can't deliver this in time. std::atomic m_progressCanceled; bool m_graphIsDirected, m_graphIsSymmetric, m_graphIsWeighted, m_graphIsConnected; int m_graphWeaklyConnectedComponents; int m_graphStronglyConnectedComponents; QHash m_vertexComponentId; int csRecDepth; QString m_fileName, m_graphName, initEdgeColor, initEdgeColorZero, initVertexColor, initVertexNumberColor, initVertexLabelColor; bool initShowZeroWeightEdges; QString initVertexShape, initVertexIconPath; QString htmlHead, htmlHeadLight, htmlEnd; QDateTime actualDateTime; }; #endif SocNetV-3.7/src/graph/000077500000000000000000000000001524402210300145655ustar00rootroot00000000000000SocNetV-3.7/src/graph/centrality/000077500000000000000000000000001524402210300167435ustar00rootroot00000000000000SocNetV-3.7/src/graph/centrality/graph_centrality.cpp000066400000000000000000000705061524402210300230160ustar00rootroot00000000000000/** * @file graph_centrality.cpp * @brief Implements centrality-based prominence indices (e.g., degree, eigenvector, information, closeness IR) for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2025 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Computes the Information centrality of each vertex - diagonal included * Note that there is no known generalization of Stephenson & Zelen's theory * for information centrality to directional data * * Meaning: most centrality measures only look at the single shortest path between two * actors. Information centrality gives an actor credit for *every* path connecting it to * others, not just the best one - shorter, less roundabout paths count for more - so an * actor sitting on many decent alternative routes can score as centrally as one sitting on * the single best route. * * When to use: when redundancy of connection matters more than the single fastest route - * e.g. assessing how robust an actor's information access is to a single link failing, or * comparing actors whose "one best path" scores (closeness, betweenness) look similar but * whose actual web of alternative routes differs a lot. * * Compare to: Closeness Centrality (CC) also scores actors by distance to others, but only via * the single shortest path; Betweenness (BC) counts how often an actor sits on shortest paths * between others. Information Centrality is the only one of the three that uses every * connecting path, not just the shortest one. * * Weights: unlike CC/BC/SC/EC/PC/IRCC, this is not shortest-path routing - the raw weight * feeds directly into the B matrix (B_ij = 1 - w_ij), where a *larger* raw weight already * pulls two actors closer together. Don't invert a strength-type weight here (that would * weaken your strongest ties); only invert if the weight genuinely represents a cost. * * Math: build matrix B where B_ii = 1 + (sum of i's edge weights) and B_ij = 1 - w_ij for * i != j, then invert it to get C = B^-1. Information centrality is * IC(i) = 1 / [ C_ii + (tr(C) - 2*R) / n ], where tr(C) is the trace of C and R is the sum * of any one row of C (interchangeable by construction of B). * * @param considerWeights * @param inverseWeights */ void Graph::centralityInformation(const bool considerWeights, const bool inverseWeights) { qCDebug(lcCentrality) << "Graph::centralityInformation()"; if (calculatedIC) { qCDebug(lcCentrality) << "Graph::centralityInformation() - already computed. Return."; return; } discreteICs.clear(); sumIC = 0; maxIC = 0; t_sumIC = 0; minIC = RAND_MAX; classesIC = 0; varianceIC = 0; meanIC = 0; maxNodeIC = 0; minNodeIC = 0; VList::const_iterator it; int i = 0, j = 0; qreal m_weight = 0; qreal weightSum = 1; qreal traceC = 0; qreal commonRowSum = 0; qreal IC = 0; qreal SIC = 0; /* Note: isolated nodes must be dropped from the AM Otherwise, the SIGMA matrix might be singular, therefore non-invertible. */ const bool dropIsolates = true; const bool symmetrize = true; const int n = vertices(dropIsolates, false, true); /* Degenerate case: no non-isolated vertices */ if (n == 0) { for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setIC(0); (*it)->setSIC(0); } calculatedIC = true; return; } /* Degenerate case: IC is not meaningful for fewer than 2 non-isolated vertices */ if (n < 2) { for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setIC(0); (*it)->setSIC(0); } calculatedIC = true; return; } createMatrixAdjacency(dropIsolates, considerWeights, inverseWeights, symmetrize); if (progressCanceled()) { return; } QString pMsg = tr("Computing Information Centralities. \nPlease wait..."); progressStatus(pMsg); WM.resize(n, n); invM.resize(n, n); for (i = 0; i < n; i++) { weightSum = 1; for (j = 0; j < n; j++) { if (i == j) continue; m_weight = AM.item(i, j); weightSum += m_weight; // sum of weights for all edges incident to i WM.setItem(i, j, 1 - m_weight); } WM.setItem(i, i, weightSum); } if (progressCanceled()) { return; } progressStatus(tr("Computing inverse adjacency matrix. Please wait...")); const bool invertible = invM.inverse(WM, [this] { return progressCanceled(); }); if (progressCanceled()) { return; } // Fix #269: WM can be singular; without this check IC would be computed from invM // left as all zeros, silently reporting wrong scores instead of "not defined". if (!invertible) { qCDebug(lcCentrality) << "Graph::centralityInformation() - weight matrix is singular."; progressStatus(tr("Information Centrality is not defined: the weight matrix is singular.")); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setIC(0); (*it)->setSIC(0); } calculatedIC = true; return; } progressStatus(tr("Computing IC scores. Please wait...")); traceC = 0; commonRowSum = 0; for (j = 0; j < n; j++) { commonRowSum += invM.item(0, j); } for (i = 0; i < n; i++) { traceC += invM.item(i, i); } i = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if ((*it)->isIsolated()) { (*it)->setIC(0); (*it)->setSIC(0); continue; } IC = 1.0 / (invM.item(i, i) + (traceC - 2.0 * commonRowSum) / n); (*it)->setIC(IC); t_sumIC += IC; ++i; } if (t_sumIC > 0) { for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if ((*it)->isIsolated()) { (*it)->setSIC(0); continue; } IC = (*it)->IC(); SIC = IC / t_sumIC; (*it)->setSIC(SIC); sumIC += SIC; resolveClasses(SIC, discreteICs, classesIC); minmax(SIC, (*it), maxIC, minIC, maxNodeIC, minNodeIC); } meanIC = sumIC / static_cast(n); qreal x = 0; varianceIC = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if ((*it)->isIsolated()) continue; x = ((*it)->SIC() - meanIC); x *= x; varianceIC += x; } varianceIC /= static_cast(n); } else { for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setSIC(0); } sumIC = 0; meanIC = 0; varianceIC = 0; } calculatedIC = true; WM.clear(); } /** * @brief Estimates the adjacency matrix's spectral radius (dominant eigenvalue magnitude). * * Meaning: how "amplifying" the network's connectivity structure is - the factor by which a * signal spreading through the network grows per step, in the worst case. Used to validate the * convergence bound for measures built on a geometric series of the adjacency matrix (Katz * Centrality, Bonacich Power Centrality): their attenuation parameter must satisfy * |x| < 1/lambda_max, or the underlying series - and the matrix inversion computing it in closed * form - does not converge. * * When to use: before running Katz or Bonacich, to know (or show the user) the actual valid * range for alpha/beta on this specific network, rather than a generic guess - dense/large * networks can have a lambda_max well into double digits, making the valid range far smaller * than it would be for a sparse one. * * Compare to: centralityEigenvector() also power-iterates the adjacency matrix to find its * dominant eigenvector, and computes this same lambda_max as a side effect - this method exists * separately because callers here (Katz, Bonacich, and their dialogs) only need the eigenvalue, * not the ranking eigenvector itself. * * Math: via power iteration (Matrix::powerIteration()), starting from a unit seed vector - * ||Ax|| approximates lambda_max once the iteration converges (Perron-Frobenius: for a * non-negative matrix like an adjacency matrix, this is exact for a connected network). * * @param considerWeights * @param inverseWeights * @param dropIsolates * @return the estimated spectral radius, or 0 if the network is empty or the computation was * canceled. 0 conventionally means "no bound - any value converges" to callers. */ qreal Graph::estimateSpectralRadius(const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates) { const bool symmetrize = false; const int N = vertices(dropIsolates); if (N == 0) { return 0; } createMatrixAdjacency(dropIsolates, considerWeights, inverseWeights, symmetrize); if (progressCanceled()) { return 0; } qreal *seed = new (nothrow) qreal[N]; Q_CHECK_PTR(seed); for (int k = 0; k < N; k++) seed[k] = 1; qreal dummySum = 0, dummyMax = 0, dummyMin = RAND_MAX; int dummyMaxI = 0, dummyMinI = 0; qreal lambdaMax = 0; AM.powerIteration(seed, dummySum, dummyMax, dummyMaxI, dummyMin, dummyMinI, 0.0000001, 500, [this] { return progressCanceled(); }, &lambdaMax); delete[] seed; if (progressCanceled()) { return 0; } return lambdaMax; } /** * @brief Computes Eigenvector centrality * * Meaning: not just how many connections you have, but how important your connections are - * being tied to a few highly-connected people can outscore being tied to many * poorly-connected ones. It is a "popularity feeds back on itself" measure: your score * depends on your neighbors' scores, which depend on their neighbors' scores, and so on * until the whole network settles into one stable ranking. * * When to use: ranking actors by prestige/influence rather than raw activity - e.g. finding * the truly influential accounts in a social network (as opposed to merely the most active * ones), or citation-network-style "important papers cited by other important papers" * reasoning. Needs a connected (or largely connected) graph to produce a meaningful ranking. * * Weights: not shortest-path routing - the adjacency matrix entry is used directly as "how * much this tie contributes." Don't invert a strength-type weight (that would make your * strongest ties count least); only invert if the weight genuinely represents a cost. * * Compare to: Katz Centrality (see centralityKatz()) and Bonacich Power Centrality (see * centralityBonacich()) generalize this same "connections to well-connected others matter" idea * by summing actual walks with a distance-based decay factor instead of finding the dominant * eigenvector directly, which makes the decay directly tunable by the user rather than fixed by * the network's own structure. Power Centrality (PC, Gil-Schmidt, see graphDistancesGeodesic()) * is a different, older, similarly-named but unrelated measure computed straight from vertex * degrees rather than via eigen-decomposition. * * Math: the eigenvector centrality vector x is the dominant eigenvector of the adjacency * matrix A, i.e. the vector solving A*x = lambda_max*x for the largest eigenvalue lambda_max. * Computed here via power iteration (Matrix::powerIteration()): start from any vector, * repeatedly multiply by A and rescale to unit length - it converges to that eigenvector. * * @param considerWeights * @param inverseWeights */ void Graph::centralityEigenvector(const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates) { if (calculatedEVC) { qCDebug(lcCentrality) << "Graph not changed - EVC already computed. Return."; return; } qCDebug(lcCentrality) << "(Re)Computing Eigenvector centrality scores..."; progressStatus(tr("Calculating EVC scores...")); classesEVC = 0; discreteEVCs.clear(); sumEVC = 0; maxEVC = 0; minEVC = RAND_MAX; varianceEVC = 0; meanEVC = 0; maxNodeEVC = 0; minNodeEVC = 0; VList::const_iterator it; const bool symmetrize = false; const bool useDegrees = false; int i = 0; const int N = vertices(dropIsolates); if (N == 0) { for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setEVC(0); (*it)->setSEVC(0); } calculatedEVC = true; return; } qreal *EVC = new (nothrow) qreal[N]; Q_CHECK_PTR(EVC); qreal SEVC = 0; createMatrixAdjacency(dropIsolates, considerWeights, inverseWeights, symmetrize); if (progressCanceled()) { delete[] EVC; return; } QString pMsg = tr("Computing Eigenvector Centrality scores. \nPlease wait..."); progressStatus(pMsg); if (useDegrees) { qCDebug(lcCentrality) << "Using outDegree for initial EVC vector"; progressStatus(tr("Computing outDegrees. Please wait...")); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if ((*it)->isIsolated() && dropIsolates) continue; EVC[i] = (*it)->degreeOut(); i++; } } else { qCDebug(lcCentrality) << "Using unit initial EVC vector"; for (int k = 0; k < N; k++) EVC[k] = 1; } if (progressCanceled()) { delete[] EVC; return; } qreal lambdaMax = 0; AM.powerIteration(EVC, sumEVC, maxEVC, maxNodeEVC, minEVC, minNodeEVC, 0.0000001, 500, [this] { return progressCanceled(); }, &lambdaMax); qCDebug(lcCentrality) << "Graph::centralityEigenvector() - dominant eigenvalue (lambdaMax) =" << lambdaMax; if (progressCanceled()) { delete[] EVC; return; } progressStatus(tr("Leading eigenvector computed. " "Analysing centralities. Please wait...")); // Recompute sum defensively from final vector sumEVC = 0; for (int k = 0; k < N; ++k) sumEVC += EVC[k]; meanEVC = sumEVC / static_cast(N); i = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if ((*it)->isIsolated() && dropIsolates) { (*it)->setEVC(0); (*it)->setSEVC(0); continue; } (*it)->setEVC(EVC[i]); SEVC = (maxEVC != 0) ? (EVC[i] / maxEVC) : 0; (*it)->setSEVC(SEVC); resolveClasses(SEVC, discreteEVCs, classesEVC); varianceEVC += (EVC[i] - meanEVC) * (EVC[i] - meanEVC); i++; } varianceEVC /= static_cast(N); calculatedEVC = true; delete[] EVC; } /** * @brief Calculates the degree (outDegree) centrality of each vertex - diagonal included * * Meaning: the simplest centrality there is - how many direct connections (ties) does this * actor have? More connections means more prominence, full stop; no attention is paid to who * those connections are or how the rest of the network is shaped. * * When to use: a fast first-pass screen for "who's active/popular" on any graph, directed or * not, connected or not - cheap to compute and easy to explain to a non-technical audience. * Weak at finding brokers or long-range influence; pair with betweenness or eigenvector * centrality when those matter. * * Compare to: Degree Prestige (DP, see prestigeDegree()) is this same idea restricted to * inbound ties only - meaningful on directed graphs, where "connections this actor made" and * "connections this actor received" can differ a lot. * * Weights: no inversion choice here (considerWeights only) - when considered, weights are * summed directly, so a stronger tie always adds more, regardless of what the weight means. * * Math: DC(i) = number of edges incident to i (or the sum of their weights, if weights are * considered). Standardized SDC(i) = DC(i) / (N-1), the fraction of all other actors i is * directly tied to. * * @param considerWeights * @param dropIsolates */ void Graph::centralityDegree(const bool &considerWeights, const bool &dropIsolates) { if (calculatedDC) { qCDebug(lcCentrality) << "Graph not changed - no need to recompute degree centralities. Returning."; return; } qreal DC = 0, nom = 0, denom = 0, SDC = 0; qreal weight; classesSDC = 0; discreteSDCs.clear(); sumSDC = 0; sumDC = 0; maxSDC = 0; minSDC = RAND_MAX; varianceSDC = 0; meanSDC = 0; int N = vertices(dropIsolates); VList::const_iterator it, it1; QString pMsg = tr("Computing out-Degree Centralities for %1 nodes. \nPlease wait...").arg(N); qCDebug(lcCentrality) << pMsg; progressStatus(pMsg); if (progressCanceled()) { return; } for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { DC = 0; if (!(*it)->isEnabled() || (dropIsolates && (*it)->isIsolated())) { continue; } for (it1 = m_graph.cbegin(); it1 != m_graph.cend(); ++it1) { if (!(*it1)->isEnabled() || (dropIsolates && (*it1)->isIsolated())) { continue; } if ((weight = edgeExists((*it)->number(), (*it1)->number())) != 0.0) { if (considerWeights) DC += weight; else DC++; // check here if the matrix is symmetric - we need this below if (weight != edgeExists((*it1)->number(), (*it)->number())) m_graphIsSymmetric = false; } } (*it)->setDC(DC); // Set OutDegree sumDC += DC; // store sumDC (for std calc below) } if (progressCanceled()) { return; } // Calculate std Out-Degree, min, max, classes and sumSDC for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { DC = (*it)->DC(); if (!considerWeights) { SDC = (DC / (N - 1.0)); } else { SDC = (DC / (sumDC)); } (*it)->setSDC(SDC); // Set Standard DC qCDebug(lcCentrality) << "vertex" << (*it)->number() << "-- DC=" << DC << "SDC=" << SDC; sumSDC += SDC; resolveClasses(SDC, discreteSDCs, classesSDC); if (maxSDC < SDC) { maxSDC = SDC; maxNodeSDC = (*it)->number(); } if (minSDC > SDC) { minSDC = SDC; minNodeSDC = (*it)->number(); } } if (minSDC == maxSDC) maxNodeSDC = -1; meanSDC = sumSDC / (qreal)N; // Calculate Variance and the Degree Centralization of the whole graph. for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (dropIsolates && (*it)->isIsolated()) { continue; } SDC = (*it)->SDC(); nom += (maxSDC - SDC); varianceSDC += (SDC - meanSDC) * (SDC - meanSDC); } varianceSDC = varianceSDC / (qreal)N; if (m_graphIsSymmetric) { // we divide by N-1 because we use std C values denom = (N - 1.0) * (N - 2.0) / (N - 1.0); } else { denom = (N - 1.0) * (N - 1.0) / (N - 1.0); } if (N < 3) { denom = N - 1.0; } if (!considerWeights) { groupDC = nom / denom; } calculatedDC = true; } /** * @brief Computes an "improved" closeness centrality index, IRCC, which can be used * on disconnected graphs. * IRCC is an improved node-level centrality closeness index which focuses on the * influence range of each node (the set of nodes that are reachable from it) * For each node v, this index calculates the fraction of nodes in its influence * range and divides it by the average distance of those nodes from v, * ignoring nodes that are not reachable from it. * * Meaning: how efficiently can an actor reach the part of the network it *can* reach? Plain * closeness centrality breaks down on a disconnected network, since "distance to everyone" is * undefined once some actors are unreachable. IRCC sidesteps that by only ever averaging * distances to actors that are actually reachable, so it stays meaningful even when the * network is split into several disconnected pieces. * * When to use: any time the network might not be fully connected - social media crawls, * partial datasets, or naturally fragmented networks (e.g. isolated friend clusters) - where * plain Closeness Centrality (CC) would otherwise be undefined or require dropping actors. * * Weights: shortest-path-based, same as CC - if a weight represents value/strength, invert it * so a strong tie behaves like a short/cheap path. * * Compare to: Closeness Centrality (CC, see graphDistancesGeodesic()) is the classic version * this generalizes - use CC directly once the graph is known to be fully connected. Proximity * Prestige (PP, see prestigeProximity()) is prestige's directed-graph counterpart of this same * idea (distance *from* others, rather than *to* them). * * Math: for actor i, let J_i be the set of nodes reachable from i (its influence range). * IRCC(i) = [ |J_i| / (N-1) ] / [ (sum of d(i,j) for j in J_i) / |J_i| ] - the fraction of * the network i can reach, divided by the average distance to that reachable set. * * @param considerWeights * @param inverseWeights * @param dropIsolates */ void Graph::centralityClosenessIR(const bool considerWeights, const bool inverseWeights, const bool dropIsolates) { if (calculatedIRCC) { qCDebug(lcCentrality) << "Graph not changed - no need to recompute IRCC. Returning"; return; } qCDebug(lcCentrality) << "(Re)Computing IRCC closeness centrality..."; graphDistancesGeodesic(false, considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { return; } // calculate centralities VList::const_iterator it, jt; qreal IRCC = 0, SIRCC = 0; qreal Ji = 0; qreal dist = 0; qreal sumD = 0; qreal averageD = 0; qreal N = vertices(dropIsolates, false, true); classesIRCC = 0; discreteIRCCs.clear(); sumIRCC = 0; maxIRCC = 0; minIRCC = N - 1; varianceIRCC = 0; meanIRCC = 0; QString pMsg = tr("Computing Influence Range Centrality scores. \n" "Please wait"); progressStatus(pMsg); qCDebug(lcCentrality) << "dropIsolates" << dropIsolates; qCDebug(lcCentrality) << "computing scores for actors: " << N; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (progressCanceled()) { return; } IRCC = 0; sumD = 0; Ji = 0; if ((*it)->isIsolated()) { continue; } for (jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { if ((*it)->number() == (*jt)->number()) { continue; } if (!(*jt)->isEnabled()) { continue; } dist = apspDistance((*it)->number(), (*jt)->number()); if (dist != RAND_MAX) { sumD += dist; Ji++; // compute |Ji| } qCDebug(lcCentrality) << "dist(" << (*it)->number() << "," << (*jt)->number() << ") =" << dist << "sumD" << sumD << " Ji" << Ji; } qCDebug(lcCentrality) << "" << (*it)->number() << " sumD" << sumD << "distanceSum" << (*it)->distanceSum(); // sanity check for sumD=0 (=> node is disconnected) if (sumD != 0) { averageD = sumD / Ji; qCDebug(lcCentrality) << "averageD = sumD / Ji" << averageD; qCDebug(lcCentrality) << "Ji / (N-1)" << Ji << "/" << N - 1; IRCC = (Ji / (qreal)(N - 1)) / averageD; qCDebug(lcCentrality) << "[ Ji / (N-1) ] / [ sumD / Ji]" << IRCC; } sumIRCC += IRCC; (*it)->setIRCC(IRCC); (*it)->setSIRCC(IRCC); // IRCC is a ratio, already std resolveClasses(IRCC, discreteIRCCs, classesIRCC); minmax(IRCC, (*it), maxIRCC, minIRCC, maxNodeIRCC, minNodeIRCC); } meanIRCC = sumIRCC / (qreal)N; if (minIRCC == maxIRCC) maxNodeIRCC = -1; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!dropIsolates || !(*it)->isIsolated()) { SIRCC = (*it)->SIRCC(); varianceIRCC += (SIRCC - meanIRCC) * (SIRCC - meanIRCC); } } varianceIRCC = varianceIRCC / (qreal)N; calculatedIRCC = true; } /** * @brief Computes minimum and maximum centralities during graphDistancesGeodesic() * @param C * @param v * @param max * @param min * @param maxNode * @param minNode */ void Graph::minmax(qreal C, GraphVertex *v, qreal &max, qreal &min, int &maxNode, int &minNode) { qCDebug(lcCentrality) << "MINMAX C = " << C << " max = " << max << " min = " << min << " name = " << v->number(); if (C > max) { max = C; maxNode = v->number(); } if (C < min) { min = C; minNode = v->number(); } } /** * @brief Checks if score C is a new prominence class * If yes, it stores that number in a QHash type where the score is the key. * If no, increases the frequency of this prominence score by 1 * Called from graphDistancesGeodesic() * @param C * @param discreteClasses * @param classes */ void Graph::resolveClasses(qreal C, H_StrToInt &discreteClasses, int &classes) { int frq = 0; H_StrToInt::iterator it2; it2 = discreteClasses.find(QString::number(C)); // Amort. O(1) complexity if (it2 == discreteClasses.end()) { classes++; discreteClasses.insert(QString::number(C), 1); } else { frq = it2.value(); discreteClasses.insert(QString::number(C), frq + 1); } } /** * @brief Overloaded method. It only adds displaying current vertex for debugging purposes. * @param C * @param discreteClasses * @param classes * @param vertex */ void Graph::resolveClasses(qreal C, H_StrToInt &discreteClasses, int &classes, int vertex) { int frq = 0; H_StrToInt::iterator it2; Q_UNUSED(vertex); it2 = discreteClasses.find(QString::number(C)); // Amort. O(1) complexity if (it2 == discreteClasses.end()) { classes++; discreteClasses.insert(QString::number(C), 1); } else { frq = it2.value(); discreteClasses.insert(QString::number(C), frq + 1); } } /** * @brief Returns true if the given centrality/prestige index has been computed. * * Uses the per-index calculated* flags set by each analysis method. * Matches the IndexType enum defined in global.h. */ bool Graph::isCentralityIndexComputed(const IndexType index) const { switch (index) { case IndexType::DC: return calculatedDC; case IndexType::CC: // CC, IRCC, BC, SC, EC, PC are all case IndexType::IRCC: // byproducts of graphDistancesGeodesic case IndexType::BC: case IndexType::SC: case IndexType::EC: case IndexType::PC: return calculatedCentralities; case IndexType::IC: return calculatedIC; case IndexType::EVC: return calculatedEVC; case IndexType::KATZ: return calculatedKC; case IndexType::BPC: return calculatedBPC; case IndexType::DP: return calculatedDP; case IndexType::PRP: return calculatedPRP; case IndexType::PP: return calculatedPP; case IndexType::CLC: return !m_graph.isEmpty() && m_graph.first()->hasCLC(); default: return false; } }SocNetV-3.7/src/graph/centrality/graph_centrality_bonacich.cpp000066400000000000000000000205611524402210300246400ustar00rootroot00000000000000/** * @file graph_centrality_bonacich.cpp * @brief Implements Bonacich Power Centrality for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Computes Bonacich Power Centrality of each vertex, with parameters alpha and beta. * * Meaning: like Katz Centrality, credit for indirect connections discounted by distance - but * with a twist: beta can be negative, in which case being tied to well-connected others can * *hurt* rather than help a node's score (e.g. a buyer connected to powerful sellers has less * bargaining power the more powerful those sellers are). alpha is a free overall scale factor * that doesn't change the relative ranking, only the numbers' size. * * When to use: whenever the sign of "connections to well-connected others" itself matters * (bargaining power, exchange networks) rather than always being a good thing - Katz Centrality * and Eigenvector Centrality both assume more indirect connections is always better. * * Weights: like Katz and Eigenvector Centrality, this is walk-counting, not shortest-path * routing - don't invert a strength-type weight (it would make your strongest ties contribute * the least); only invert if the weight genuinely represents a cost. * * Compare to: Katz Centrality (KC, see centralityKatz()) is the same walk-counting idea with a * single always-positive decay parameter; Bonacich generalizes it with a second, possibly * negative parameter beta and a free scale factor alpha. Not to be confused with Power * Centrality (PC, Gil-Schmidt, see graphDistancesGeodesic()), a same-sounding but unrelated * measure. * * Math: b = alpha * (I - beta*R)^-1 * R * 1, where R = A^T (the transpose only matters for * directed graphs, same convention as Katz). Only beta must satisfy |beta| < 1/lambda_max(A) for * the underlying geometric series to converge; alpha is an unconstrained outer scale factor. * Unlike every other centrality measure in this app, b(i) can come out negative when beta is * negative - that is a genuine property of the measure, not a bug, and SBPC (b divided by max b) * is not guaranteed to land in [0,1] in that case. * * @param alpha * @param beta * @param considerWeights * @param inverseWeights * @param dropIsolates */ void Graph::centralityBonacich(const qreal &alpha, const qreal &beta, const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates) { if (calculatedBPC && m_lastBonacichAlpha == alpha && m_lastBonacichBeta == beta) { qCDebug(lcCentrality) << "Graph not changed and alpha/beta unchanged - BPC already computed. Return."; return; } qCDebug(lcCentrality) << "(Re)Computing Bonacich Power centrality scores... alpha =" << alpha << "beta =" << beta; progressStatus(tr("Calculating Bonacich Power Centrality scores...")); classesBPC = 0; discreteBPCs.clear(); sumBPC = 0; maxBPC = 0; minBPC = RAND_MAX; varianceBPC = 0; meanBPC = 0; maxNodeBPC = 0; minNodeBPC = 0; VList::const_iterator it; const int N = vertices(dropIsolates); if (N == 0) { for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setBPC(0); (*it)->setSBPC(0); } calculatedBPC = true; return; } // estimateSpectralRadius() builds AM (via createMatrixAdjacency) as a side effect - it stays // populated for the transpose step below, no need to build it again here. Unlike Katz, alpha // here is a free outer scale factor and has no convergence bound of its own. const qreal lambdaMax = estimateSpectralRadius(considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { return; } qCDebug(lcCentrality) << "Graph::centralityBonacich() - lambdaMax =" << lambdaMax << "beta =" << beta; if (lambdaMax > 0 && qAbs(beta) >= 1.0 / lambdaMax) { qCDebug(lcCentrality) << "Graph::centralityBonacich() - beta does not satisfy the " "convergence bound |beta| < 1/lambdaMax. Aborting."; progressStatus(tr("Bonacich Power Centrality is not defined for beta = %1: it must " "satisfy |beta| < %2 (1 / the network's largest eigenvalue), or the " "underlying computation does not converge.") .arg(beta) .arg(1.0 / lambdaMax)); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setBPC(0); (*it)->setSBPC(0); } calculatedBPC = true; return; } progressStatus(tr("Computing Bonacich Power Centrality scores. \nPlease wait...")); // Build R = A^T into WM (same transpose convention as Katz). transpose() heap-allocates a // new Matrix each call; we own it here and delete it once copied. Matrix &AT = AM.transpose(); WM.resize(N, N); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { WM.setItem(i, j, AT.item(i, j)); } } delete &AT; // R*1 (row sums of R) must be captured before WM is overwritten into (I - beta*R) below. qreal *Rsum = new (nothrow) qreal[N]; Q_CHECK_PTR(Rsum); for (int i = 0; i < N; i++) { qreal rowSum = 0; for (int j = 0; j < N; j++) { rowSum += WM.item(i, j); } Rsum[i] = rowSum; } WM.multiplyScalar(beta); WM.subtractFromI(); // WM now holds (I - beta*R) invM.resize(N, N); progressStatus(tr("Inverting (I - beta*R). Please wait...")); const bool invertible = invM.inverse(WM, [this] { return progressCanceled(); }); if (progressCanceled()) { delete[] Rsum; return; } if (!invertible) { qCDebug(lcCentrality) << "Graph::centralityBonacich() - (I - beta*R) is singular."; progressStatus(tr("Bonacich Power Centrality is not defined: (I - beta*R) is singular.")); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setBPC(0); (*it)->setSBPC(0); } calculatedBPC = true; delete[] Rsum; return; } // beta/alpha are only cached as "last known good" once the computation actually succeeds - // layoutByProminenceIndex() (WS11, #39) relies on this to know Bonacich is safe to re-run. m_lastBonacichAlpha = alpha; m_lastBonacichBeta = beta; // b = alpha * (I - beta*R)^-1 * R * 1 = alpha * invM * Rsum qreal *out = new (nothrow) qreal[N]; Q_CHECK_PTR(out); invM.productByVector(Rsum, out, false); delete[] Rsum; qreal *BPC = new (nothrow) qreal[N]; Q_CHECK_PTR(BPC); for (int i = 0; i < N; i++) { BPC[i] = alpha * out[i]; } delete[] out; int i = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if ((*it)->isIsolated() && dropIsolates) { (*it)->setBPC(0); (*it)->setSBPC(0); continue; } (*it)->setBPC(BPC[i]); sumBPC += BPC[i]; if (BPC[i] > maxBPC) { maxBPC = BPC[i]; maxNodeBPC = (*it)->number(); } if (BPC[i] < minBPC) { minBPC = BPC[i]; minNodeBPC = (*it)->number(); } i++; } delete[] BPC; meanBPC = sumBPC / static_cast(N); qreal SBPC = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if ((*it)->isIsolated() && dropIsolates) { continue; } SBPC = (maxBPC != 0) ? ((*it)->BPC() / maxBPC) : 0; (*it)->setSBPC(SBPC); resolveClasses(SBPC, discreteBPCs, classesBPC); varianceBPC += ((*it)->BPC() - meanBPC) * ((*it)->BPC() - meanBPC); } varianceBPC /= static_cast(N); calculatedBPC = true; } SocNetV-3.7/src/graph/centrality/graph_centrality_katz.cpp000066400000000000000000000204361524402210300240440ustar00rootroot00000000000000/** * @file graph_centrality_katz.cpp * @brief Implements Katz Centrality for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Computes Katz Centrality of each vertex, with attenuation factor alpha. * * Meaning: credit for indirect connections, discounted the further away they are. Unlike * degree centrality, which only counts direct ties, Katz centrality also counts * friends-of-friends, friends-of-friends-of-friends, and so on - but each extra hop counts for * less, shrunk by alpha (a direct tie counts full price, a 2-hop tie counts alpha times as * much, a 3-hop tie counts alpha^2 times as much). * * When to use: ranking actors by total reach through the network, with a user-tunable * "how much does distance matter" knob - useful when the natural decay eigenvector centrality * settles on isn't the one you want, or the graph is disconnected/sparse enough that * eigenvector centrality's power iteration doesn't produce a useful ranking. * * Weights: like Eigenvector Centrality, this is walk-counting, not shortest-path routing - the * adjacency matrix entry is used directly as tie strength. Don't invert a strength-type weight * (that would make your strongest ties contribute the least to every walk); only invert if the * weight genuinely represents a cost. * * Compare to: Eigenvector Centrality (EVC, see centralityEigenvector()) generalizes the same * "connections to well-connected others matter" idea via eigen-decomposition instead of an * explicit decay parameter. Bonacich Power Centrality (see centralityBonacich()) extends this * same formula with a second parameter beta that can be negative, flipping whether being * connected to well-connected others helps or hurts. Not to be confused with Power Centrality * (PC, Gil-Schmidt, see graphDistancesGeodesic()), a same-sounding but unrelated measure. * * Math: C_Katz(i) = sum_{k=1}^inf sum_j alpha^k (A^k)_ji, i.e. the discounted count of walks * of every length arriving at i, since (A^k)_ij is the number of length-k walks between i and * j. Computed in closed form via the matrix identity * I + xA + x^2*A^2 + x^3*A^3 + ... = (I - xA)^-1 (valid for |x| < 1/lambda_max, the same * geometric-series identity used for ordinary numbers, applied to matrices): * C_Katz = ((I - alpha*A^T)^-1 - I) * 1 (the transpose only matters for directed graphs; for * undirected graphs A = A^T). alpha must satisfy |alpha| < 1/lambda_max(A), where lambda_max is * the adjacency matrix's dominant eigenvalue (see estimateSpectralRadius()), or the underlying * geometric series does not converge and the inversion is singular/meaningless. * * @param alpha * @param considerWeights * @param inverseWeights * @param dropIsolates */ void Graph::centralityKatz(const qreal &alpha, const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates) { if (calculatedKC && m_lastKatzAlpha == alpha) { qCDebug(lcCentrality) << "Graph not changed and alpha unchanged - KC already computed. Return."; return; } qCDebug(lcCentrality) << "(Re)Computing Katz centrality scores... alpha =" << alpha; progressStatus(tr("Calculating Katz Centrality scores...")); classesKC = 0; discreteKCs.clear(); sumKC = 0; maxKC = 0; minKC = RAND_MAX; varianceKC = 0; meanKC = 0; maxNodeKC = 0; minNodeKC = 0; VList::const_iterator it; const int N = vertices(dropIsolates); if (N == 0) { for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setKC(0); (*it)->setSKC(0); } calculatedKC = true; return; } // estimateSpectralRadius() builds AM (via createMatrixAdjacency) as a side effect - it stays // populated for the transpose step below, no need to build it again here. const qreal lambdaMax = estimateSpectralRadius(considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { return; } qCDebug(lcCentrality) << "Graph::centralityKatz() - lambdaMax =" << lambdaMax << "alpha =" << alpha; if (lambdaMax > 0 && qAbs(alpha) >= 1.0 / lambdaMax) { qCDebug(lcCentrality) << "Graph::centralityKatz() - alpha does not satisfy the " "convergence bound |alpha| < 1/lambdaMax. Aborting."; progressStatus(tr("Katz Centrality is not defined for alpha = %1: it must satisfy " "|alpha| < %2 (1 / the network's largest eigenvalue), or the " "underlying computation does not converge.") .arg(alpha) .arg(1.0 / lambdaMax)); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setKC(0); (*it)->setSKC(0); } calculatedKC = true; return; } progressStatus(tr("Computing Katz Centrality scores. \nPlease wait...")); // Build WM = I - alpha * A^T, then invert it into invM = (I - alpha*A^T)^-1. // transpose() heap-allocates a new Matrix each call (existing Matrix API convention - // see its own call sites in graph_reports.cpp); we own it here and delete it once done. Matrix &AT = AM.transpose(); WM.resize(N, N); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { WM.setItem(i, j, AT.item(i, j)); } } delete &AT; WM.multiplyScalar(alpha); WM.subtractFromI(); // WM now holds (I - alpha*A^T) invM.resize(N, N); progressStatus(tr("Inverting (I - alpha*A^T). Please wait...")); const bool invertible = invM.inverse(WM, [this] { return progressCanceled(); }); if (progressCanceled()) { return; } if (!invertible) { qCDebug(lcCentrality) << "Graph::centralityKatz() - (I - alpha*A^T) is singular."; progressStatus(tr("Katz Centrality is not defined: (I - alpha*A^T) is singular.")); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setKC(0); (*it)->setSKC(0); } calculatedKC = true; return; } // alpha is only cached as "last known good" once the computation actually succeeds - // layoutByProminenceIndex() (WS11, #10) relies on this to know Katz is safe to re-run. m_lastKatzAlpha = alpha; // C_Katz(i) = (row sum of invM, row i) - 1: the "-1" removes the k=0 "self" term that the // (I - alpha*A^T)^-1 identity always includes but Katz's own sum (starting at k=1) does not. qreal *KC = new (nothrow) qreal[N]; Q_CHECK_PTR(KC); for (int i = 0; i < N; i++) { qreal rowSum = 0; for (int j = 0; j < N; j++) { rowSum += invM.item(i, j); } KC[i] = rowSum - 1.0; } int i = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if ((*it)->isIsolated() && dropIsolates) { (*it)->setKC(0); (*it)->setSKC(0); continue; } (*it)->setKC(KC[i]); sumKC += KC[i]; if (KC[i] > maxKC) { maxKC = KC[i]; maxNodeKC = (*it)->number(); } if (KC[i] < minKC) { minKC = KC[i]; minNodeKC = (*it)->number(); } i++; } delete[] KC; meanKC = sumKC / static_cast(N); qreal SKC = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if ((*it)->isIsolated() && dropIsolates) { continue; } SKC = (maxKC != 0) ? ((*it)->KC() / maxKC) : 0; (*it)->setSKC(SKC); resolveClasses(SKC, discreteKCs, classesKC); varianceKC += ((*it)->KC() - meanKC) * ((*it)->KC() - meanKC); } varianceKC /= static_cast(N); calculatedKC = true; } SocNetV-3.7/src/graph/centrality/graph_prestige.cpp000066400000000000000000000460321524402210300224570ustar00rootroot00000000000000/** * @file graph_prestige.cpp * @brief Implements prestige (prominence) index algorithms for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Computes the Degree Prestige (in-degree) of each vertex - diagonal included * Also the mean value and the variance of the in-degrees. * * Meaning: prestige measures flip centrality's question around - instead of "how many ties * does this actor have," they ask "how many ties point *at* this actor." Degree prestige is * the simplest version: how many others chose to connect to this actor, ignoring how many * connections the actor made outward. Meaningless on an undirected graph, where in- and * out-ties are the same thing. * * When to use: directed networks where "being chosen by others" is the thing worth measuring * - citation counts, follower counts, nomination/endorsement data - and a quick raw count is * enough (see PageRank Prestige below when the *quality* of who's choosing you also matters). * * Compare to: Degree Centrality (DC, see centralityDegree()) is this same raw-count idea for * outbound ties (or all ties, on an undirected graph). * * Weights: no inversion choice here (considerWeights only) - when considered, weights are * summed directly, so a stronger inbound tie always adds more. * * Math: DP(i) = number of inbound edges to i (or their summed weights, if weights are * considered). Standardized SDP(i) = DP(i) / (N-1). * * @param weights * @param dropIsolates */ void Graph::prestigeDegree(const bool &considerWeights, const bool &dropIsolates) { if (calculatedDP) { qCDebug(lcCentrality) << "Graph not changed - no need to recompute Degree Prestige scores. Returning"; return; } qCDebug(lcCentrality) << "(Re)Computing Degree Prestige scores..."; int N = vertices(dropIsolates); int v2 = 0, v1 = 0; VList::const_iterator it; QHash *enabledInEdges = new QHash; QHash::const_iterator hit; qreal DP = 0, SDP = 0, nom = 0, denom = 0; qreal weight; classesSDP = 0; sumSDP = 0; sumDP = 0; maxSDP = 0; minSDP = N - 1; discreteDPs.clear(); varianceSDP = 0; meanSDP = 0; m_graphIsSymmetric = true; QString pMsg = tr("Computing Degree Prestige (in-Degree). \n Please wait ..."); progressStatus(pMsg); qCDebug(lcCentrality) << "vertices" << N << "graph modified. Recomputing..."; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (progressCanceled()) { delete enabledInEdges; return; } v1 = (*it)->number(); qCDebug(lcCentrality) << "computing DP for vertex" << v1; DP = 0; if (!(*it)->isEnabled()) { qCDebug(lcCentrality) << "vertex disabled. Continue."; continue; } qCDebug(lcCentrality) << "Iterate over inbound edges of " << v1; enabledInEdges = (*it)->inEdgesEnabledHash(); hit = enabledInEdges->cbegin(); while (hit != enabledInEdges->cend()) { v2 = hit.key(); qCDebug(lcCentrality) << "inbound edge from" << v2; if (!edgeExists(v2, v1)) { // sanity check qCDebug(lcCentrality) << "Cannot verify inbound edge" << v2 << "CONTINUE"; ++hit; continue; } weight = hit.value(); if (considerWeights) { DP += weight; } else { DP++; } if (edgeExists(v1, v2) != weight) { m_graphIsSymmetric = false; } ++hit; } (*it)->setDP(DP); // Set DP sumDP += DP; qCDebug(lcCentrality) << "vertex " << (*it)->number() << " DP " << DP; } // Calculate std DP, min,max, mean for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { DP = (*it)->DP(); if (!considerWeights) { SDP = (DP / (N - 1.0)); // Set Standard InDegree } else { SDP = (DP / (sumDP)); } (*it)->setSDP(SDP); sumSDP += SDP; qCDebug(lcCentrality) << "vertex " << (*it)->number() << " DP " << DP << " SDP " << (*it)->SDP(); resolveClasses(SDP, discreteDPs, classesSDP); qCDebug(lcCentrality, "DP classes = %i ", classesSDP); if (maxSDP < SDP) { maxSDP = SDP; maxNodeDP = (*it)->number(); } if (minSDP > SDP) { minSDP = SDP; minNodeDP = (*it)->number(); } } if (minSDP == maxSDP) maxNodeDP = -1; meanSDP = sumSDP / (qreal)N; qCDebug(lcCentrality, "Graph: sumSDP = %f, meanSDP = %f", sumSDP, meanSDP); // Calculate Variance and the Degree Prestigation of the whole graph. :) for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (dropIsolates && (*it)->isIsolated()) { continue; } SDP = (*it)->SDP(); nom += maxSDP - SDP; varianceSDP += (SDP - meanSDP) * (SDP - meanSDP); } varianceSDP = varianceSDP / (qreal)N; if (m_graphIsSymmetric) denom = (N - 1.0) * (N - 2.0); else denom = (N - 1.0) * (N - 1.0); if (N < 3) denom = N - 1.0; if (!considerWeights) { groupDP = nom / denom; qCDebug(lcCentrality, "Graph: varianceSDP = %f, groupDP = %f", varianceSDP, groupDP); } delete enabledInEdges; calculatedDP = true; } /** * @brief Computes Proximity Prestige of each vertex * Also the mean value and the variance of it.. * * Meaning: how close everyone else who can reach this actor actually is, on average - * prestige's answer to closeness centrality. It rewards being easy to reach *and* being * reachable by a large share of the network, so an actor reachable by only a couple of very * close others still scores lower than one reachable, at moderate distance, by almost * everyone. * * When to use: directed networks where you want a refined "how sought-after is this actor" * score that accounts for distance, not just a raw inbound-tie count (see Degree Prestige for * the simpler version). * * Compare to: Influence Range Closeness Centrality (IRCC, see centralityClosenessIR()) is this * same idea from the centrality side (distance *to* others) rather than prestige's (distance * *from* others), and likewise works on disconnected graphs. * * Weights: shortest-path-based, same as CC/IRCC - if a weight represents value/strength, invert * it so a strong tie behaves like a short/cheap path. * * Math: for actor i, let I_i be the set of actors that can reach i (its influence domain). * PP(i) = [ |I_i| / (V-1) ] / [ (sum of d(j,i) for j in I_i) / |I_i| ] - the fraction of the * network that can reach i, divided by their average distance to i. */ void Graph::prestigeProximity(const bool considerWeights, const bool inverseWeights, const bool dropIsolates) { if (calculatedPP) { qCDebug(lcCentrality) << "Graph not changed - no need to recompute proximity prestige. Returning"; return; } qCDebug(lcCentrality) << "(Re)Computing Proximity prestige scores..."; graphDistancesGeodesic(false, considerWeights, inverseWeights, inverseWeights); if (progressCanceled()) { return; } // calculate centralities VList::const_iterator it, jt; qreal PP = 0; qreal dist = 0; qreal Ii = 0; qreal V = vertices(dropIsolates); classesPP = 0; discretePPs.clear(); sumPP = 0; maxPP = 0; minPP = V - 1; variancePP = 0; meanPP = 0; QString pMsg = tr("Computing Proximity Prestige scores. \nPlease wait ..."); progressStatus(pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (progressCanceled()) { return; } PP = 0; Ii = 0; if ((*it)->isIsolated()) { continue; } for (jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { if ((*it)->number() == (*jt)->number()) { continue; } if (!(*jt)->isEnabled()) { continue; } dist = apspDistance((*jt)->number(), (*it)->number()); if (dist != RAND_MAX) { PP += dist; Ii++; // compute |Ii| } } qCDebug(lcCentrality) << "vertex" << (*it)->number() << "actors in influence domain Ii" << Ii << "actors in network" << (V - 1) << "fraction of actors who reach i |Ii|/(V-1)=" << Ii / (V - 1) << "distance to actors in Ii" << PP << "average distance to actors in Ii" << PP / Ii << "PP= " << Ii / (V - 1) << " / " << PP / Ii << " = " << (Ii / (V - 1)) / (PP / Ii); // sanity check for PP=0 (=> node is disconnected) if (PP != 0) { PP /= Ii; PP = (Ii / (V - 1)) / PP; } sumPP += PP; (*it)->setPP(PP); (*it)->setSPP(PP); // PP is already stdized resolveClasses(PP, discretePPs, classesPP); if (maxPP < PP) { maxPP = PP; maxNodePP = (*it)->number(); } if (minPP > PP) { minPP = PP; minNodePP = (*it)->number(); } } if (minPP == maxPP) maxNodePP = -1; meanPP = sumPP / V; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (dropIsolates && (*it)->isIsolated()) { continue; } PP = (*it)->PP(); variancePP += (PP - meanPP) * (PP - meanPP); } variancePP = variancePP / V; qCDebug(lcCentrality) << "sumPP = " << sumPP << " meanPP = " << meanPP << " variancePP " << variancePP; calculatedPP = true; } /** * @brief Calculates the PageRank Prestige of each vertex * * Meaning: the same idea Google originally used to rank web pages - an actor is prestigious * if prestigious actors point to it. Unlike plain degree prestige (which treats every inbound * tie equally), a link from someone with few outbound ties and high prestige counts for much * more than one of a hundred outbound ties from someone unremarkable, and that prestige keeps * circulating until every score stabilizes. * * When to use: directed networks where endorsement quality matters, not just quantity - web * links, citation networks, recommendation/referral graphs - anywhere "an endorsement from * someone important should count for more." * * Compare to: Degree Prestige (DP) counts inbound ties equally; PageRank weighs each one by * the endorser's own prestige divided by their out-degree. Eigenvector Centrality (EVC, see * centralityEigenvector()) is the closest centrality-side analogue - both are "importance * feeds back on itself" measures - but EVC is built for undirected/symmetric graphs while * PageRank is built for directed graphs with an explicit damping factor. * * Weights: no considerWeights/inverseWeights choice at all - every inbound link always counts * as weight 1, normalized by the endorser's out-degree (see Math below), regardless of any * edge weight set on the graph. * * Math: iteratively, PRP(i) = (1-d)/N + d * Sum_j( PRP(j) / outLinks(j) ) for every j linking * to i, where d is the damping factor (0.85, matching Google's original choice) and N is the * number of actors. Repeated until scores stop changing by more than a small delta. * * @param dropIsolates */ void Graph::prestigePageRank(const bool &dropIsolates) { if (calculatedPRP) { qCDebug(lcCentrality) << "Graph not changed - no need to recompute Pagerank scores. Return "; return; } qCDebug(lcCentrality) << "(Re)Computing PageRank prestige scores..."; discretePRPs.clear(); sumPRP = 0; t_sumPRP = 0; maxPRP = 0; minPRP = RAND_MAX; classesPRP = 0; variancePRP = 0; // The parameter d is a damping factor which can be set between 0 and 1. // Google creators set d to 0.85. d_factor = 0.85; qreal PRP = 0, oldPRP = 0; qreal SPRP = 0; int iterations = 1; // a counter int referrer; qreal delta = 0.00001; // The delta where we will stop the iterative calculation qreal maxDelta = RAND_MAX; qreal sumInLinksPR = 0; // temporary var for inlinks sum PR qreal transferedPRP = 0; qreal inLinks = 0; // temporary var qreal outLinks = 0; // temporary var qreal t_variance = 0; int N = vertices(dropIsolates); VList::const_iterator it; H_edges::const_iterator jt; int relation = 0; bool edgeStatus = false; QString pMsg = tr("Computing PageRank Prestige scores. \nPlease wait ..."); progressStatus(pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { // At first, PR scores have probability distribution // from 0 to 1, so each one is set to 1/N (*it)->setPRP(1.0 / (qreal)N); // compute inEdgesCount() to warm up inEdgesConst for everyone inLinks = (*it)->inEdgesCount(); outLinks = (*it)->outEdgesCount(); qCDebug(lcCentrality) << "node " << (*it)->number() << " PR = " << (*it)->PRP() << " inLinks (set const): " << inLinks << " outLinks (set const): " << outLinks; } if (edgesEnabled() == 0) { qCDebug(lcCentrality) << "all vertices are isolated and of equal PR. Stop"; return; } if (progressCanceled()) { return; } // begin iteration - continue until we reach our desired delta while (maxDelta > delta) { qCDebug(lcCentrality) << "ITERATION : " << iterations; sumPRP = 0; maxDelta = 0; maxPRP = 0; minPRP = RAND_MAX; maxNodePRP = 0; minNodePRP = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { sumInLinksPR = 0; oldPRP = (*it)->PRP(); qCDebug(lcCentrality) << "computing PR for node: " << (*it)->number() << " current PR " << oldPRP; if ((*it)->isIsolated()) { // isolates have constant PR = 1/N qCDebug(lcCentrality) << "isolated - CONTINUE "; continue; } jt = (*it)->m_inEdges.cbegin(); qCDebug(lcCentrality) << "Iterate over inEdges of " << (*it)->number(); while (jt != (*it)->m_inEdges.cend()) { relation = jt.value().first; if (relation != relationCurrent()) { ++jt; continue; } edgeStatus = jt.value().second.second; if (edgeStatus != true) { ++jt; continue; } referrer = jt.key(); qCDebug(lcCentrality) << "Node " << (*it)->number() << " inLinked from neighbor " << referrer << " vpos " << vpos[referrer]; if (edgeExists(referrer, (*it)->number())) { inLinks = m_graph[vpos[referrer]]->inEdgesCountConst(); outLinks = m_graph[vpos[referrer]]->outEdgesCountConst(); PRP = m_graph[vpos[referrer]]->PRP(); transferedPRP = (outLinks != 0) ? (PRP / outLinks) : PRP; qCDebug(lcCentrality) << "neighbor " << referrer << " has PR = " << PRP << " and outLinks = " << outLinks << " will transfer " << transferedPRP; sumInLinksPR += transferedPRP; } ++jt; } PRP = (1 - d_factor) / (qreal)N + d_factor * sumInLinksPR; (*it)->setPRP(PRP); sumPRP += PRP; qCDebug(lcCentrality) << "Node " << (*it)->number() << " new PR = " << PRP << " old PR was = " << oldPRP << " diff = " << fabs(PRP - oldPRP); // calculate diff from last PageRank value for this vertex // and set it to minDelta if the latter is bigger. if (maxDelta < fabs(PRP - oldPRP)) { maxDelta = fabs(PRP - oldPRP); qCDebug(lcCentrality) << "Setting new maxDelta = " << maxDelta; } } // normalize in every iteration qCDebug(lcCentrality) << "sumPRP for this iteration " << sumPRP; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { PRP = (*it)->PRP(); if (PRP > maxPRP) { maxPRP = PRP; maxNodePRP = (*it)->number(); } if (PRP < minPRP) { minPRP = PRP; minNodePRP = (*it)->number(); } } iterations++; } if (progressCanceled()) { return; } if (N != 0) { meanPRP = sumPRP / (qreal)N; } else { meanPRP = SPRP; } qCDebug(lcCentrality) << "sumPRP = " << sumPRP << " N = " << N << " meanPRP = " << meanPRP; // calculate std and min/max PRPs for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (dropIsolates && (*it)->isIsolated()) { continue; } PRP = (*it)->PRP(); resolveClasses(PRP, discretePRPs, classesPRP); SPRP = PRP / maxPRP; (*it)->setSPRP(SPRP); qCDebug(lcCentrality) << "vertex: " << (*it)->number() << " PR = " << PRP << " standard PR = " << SPRP << " t_sumPRP " << t_sumPRP; t_variance = (PRP - meanPRP); t_variance *= t_variance; qCDebug(lcCentrality) << "PRP " << (*it)->PRP() << " t_variance " << PRP - meanPRP << " t_variance^2" << t_variance; variancePRP += t_variance; } qCDebug(lcCentrality) << "PRP' Variance " << variancePRP << " N " << N; variancePRP = variancePRP / (qreal)N; qCDebug(lcCentrality) << "PRP' Variance: " << variancePRP; calculatedPRP = true; return; } SocNetV-3.7/src/graph/clustering/000077500000000000000000000000001524402210300167445ustar00rootroot00000000000000SocNetV-3.7/src/graph/clustering/graph_clustering_coefficients.cpp000066400000000000000000000253121524402210300255340ustar00rootroot00000000000000/** * @file graph_clustering_coefficients.cpp * @brief Implements clustering coefficient calculations for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Returns the local clustering coefficient (CLUCOF) of vertex v1. * * Meaning: not a measure of how prominent an actor is, but of how tightly-knit its * neighborhood is - do this actor's friends also know each other, or is the actor the only * thing connecting a set of strangers? A score near 1 means the neighborhood is a tight * clique; near 0 means the actor bridges otherwise-unconnected people. * * When to use: studying local cohesion and "small world" structure - identifying tightly-knit * cliques, triadic closure, or (as the actor-level building block of the graph-wide average) * assessing how clustered a network is overall. Not a prominence/ranking measure like the * centrality and prestige indices above - a high-CLC actor isn't necessarily important, just * embedded in a dense neighborhood. * * For undirected (symmetric) graphs, uses the Watts–Strogatz formula: * * C_i = 2 * |{e_jk : v_j, v_k ∈ N_i, e_jk ∈ E}| / ( k_i * (k_i - 1) ) * * where N_i is the set of direct neighbours of v_i and k_i = |N_i|. * Because the graph is symmetric every undirected edge is counted once * (the reverse-edge dedup guard in the inner loop is preserved). * * For directed (asymmetric) graphs, uses the generalisation described in * the SocNetV manual (equivalent to Watts–Strogatz extended to digraphs): * * C_i = |{e_jk : v_j, v_k ∈ N_i, e_jk ∈ E}| / ( k_i * (k_i - 1) ) * * where N_i is the UNION of in-neighbours and out-neighbours of v_i * (excluding v_i itself), and each directed edge e_jk is counted * independently – i.e. e_jk and e_kj are distinct. * * Bug fix (issue #58): the previous implementation built N_i from * reciprocalEdgesHash() (only mutual ties), which gave C_i = 0 for nodes * whose in- and out-neighbourhood were not identical. N_i must be the * full combined neighbourhood for directed networks. * * @param v1 The vertex number whose local CLUCOF is requested. * @return The local clustering coefficient in [0, 1], or 0 for isolates * and vertices with fewer than 2 neighbours. */ qreal Graph::clusteringCoefficientLocal(const int &v1) { if (!isModified() && (m_graph[vpos[v1]]->hasCLC())) { qreal clucof = m_graph[vpos[v1]]->CLC(); qCDebug(lcClustering) << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "Not modified. Returning cached clucof =" << clucof; return clucof; } qCDebug(lcClustering) << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "Graph changed or clucof not yet calculated."; const bool isSymmetric = this->isSymmetric(); qreal clucof = 0, denom = 0, nom = 0; int u1 = 0, u2 = 0, k = 0; H_StrToBool neighborhoodEdges; // tracks directed edges found among N_i neighborhoodEdges.clear(); // ------------------------------------------------------------------ // Build the neighbourhood N_i. // // Undirected graph: N_i = direct neighbours (same as before, via // reciprocalEdgesHash which equals outEdgesEnabledHash on a symmetric // graph). // // Directed graph: N_i = union of out-neighbours and in-neighbours. // We use a QSet to deduplicate nodes that are both. // ------------------------------------------------------------------ QSet neighborhood; if (isSymmetric) { // For undirected graphs the existing reciprocalEdgesHash() path is // correct – every neighbour appears in both directions. QHash reciprocal = m_graph[vpos[v1]]->reciprocalEdgesHash(); for (auto it = reciprocal.cbegin(); it != reciprocal.cend(); ++it) { // Fix #30: zero-weight edges are visual-only; excluding them keeps // k (neighbourhood size) correct, preventing an inflated denominator // k*(k-1) that would make CC artificially too small. if (it.key() != v1 && it.value() != 0) neighborhood.insert(it.key()); } } else { // Directed graph: collect out-neighbours. QHash outN = m_graph[vpos[v1]]->outEdgesEnabledHash(); for (auto it = outN.cbegin(); it != outN.cend(); ++it) { // Fix #30: same zero-weight guard as the undirected case above. if (it.key() != v1 && it.value() != 0) neighborhood.insert(it.key()); } // Collect in-neighbours (heap-allocated – take ownership). QHash *inN = m_graph[vpos[v1]]->inEdgesEnabledHash(); for (auto it = inN->cbegin(); it != inN->cend(); ++it) { // Fix #30: same zero-weight guard as the undirected case above. if (it.key() != v1 && it.value() != 0) neighborhood.insert(it.key()); } delete inN; inN = nullptr; } k = neighborhood.size(); qCDebug(lcClustering) << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "neighbourhood N_i size k =" << k << "members:" << neighborhood; if (k < 2) { // A node with 0 or 1 neighbour cannot form any triangle. qCDebug(lcClustering) << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "k < 2, returning 0."; m_graph[vpos[v1]]->setCLC(0); return 0; } // ------------------------------------------------------------------ // Count edges that exist among the members of N_i. // // For undirected graphs we deduplicate {u1,u2} / {u2,u1} pairs so // that each undirected edge is counted once, matching the formula // nom = |{e_jk}| (unordered pairs). // // For directed graphs every ordered pair (u1β†’u2) is a distinct edge, // so we only skip exact duplicates of the same ordered string, which // the QHash insert naturally handles. // ------------------------------------------------------------------ for (int u1_node : neighborhood) { for (int u2_node : neighborhood) { if (u1_node == u2_node) continue; u1 = u1_node; u2 = u2_node; if (edgeExists(u1, u2) != 0) { QString edge = QString::number(u1) + "->" + QString::number(u2); QString revedge = QString::number(u2) + "->" + QString::number(u1); if (isSymmetric) { // Count each undirected edge once. if (!neighborhoodEdges.contains(edge) && !neighborhoodEdges.contains(revedge)) { neighborhoodEdges.insert(edge, true); qCDebug(lcClustering) << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "Undirected edge added:" << edge; } } else { // Count each directed edge independently. if (!neighborhoodEdges.contains(edge)) { neighborhoodEdges.insert(edge, true); qCDebug(lcClustering) << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "Directed edge added:" << edge; } } } } } nom = neighborhoodEdges.size(); qCDebug(lcClustering) << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "edges in neighbourhood =" << nom; if (nom == 0) { qCDebug(lcClustering) << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "No edges in neighbourhood, returning 0."; m_graph[vpos[v1]]->setCLC(0); return 0; } // ------------------------------------------------------------------ // Denominator: maximum possible edges among k neighbours. // // Undirected: k*(k-1)/2 (unordered pairs) // Directed: k*(k-1) (ordered pairs; both uβ†’v and vβ†’u are valid) // ------------------------------------------------------------------ if (isSymmetric) { denom = k * (k - 1.0) / 2.0; qCDebug(lcClustering) << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "Undirected graph. Max neighbourhood edges =" << denom; } else { denom = k * (k - 1.0); qCDebug(lcClustering) << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "Directed graph. Max neighbourhood edges =" << denom; } clucof = nom / denom; qCDebug(lcClustering) << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "CLUCOF =" << clucof; m_graph[vpos[v1]]->setCLC(clucof); return clucof; } /** * @brief Computes local clustering coefficients and returns * the network average Clustering Coefficient * @param updateProgress * @return */ qreal Graph::clusteringCoefficient() { qCDebug(lcClustering) << "Graph::clusteringCoefficient()"; averageCLC = 0; varianceCLC = 0; maxCLC = 0; minCLC = 1; classesCLC = 0; discreteCLCs.clear(); qreal temp = 0; qreal x = 0; qreal N = vertices(); VList::const_iterator vertex; QString pMsg = tr("Computing Clustering Coefficient. \n" "Please wait..."); progressStatus(pMsg); for (vertex = m_graph.cbegin(); vertex != m_graph.cend(); ++vertex) { if (progressCanceled()) { return averageCLC; } temp = clusteringCoefficientLocal((*vertex)->number()); resolveClasses(temp, discreteCLCs, classesCLC); if (temp > maxCLC) { maxCLC = temp; maxNodeCLC = (*vertex)->number(); } if (temp < minCLC) { minNodeCLC = (*vertex)->number(); minCLC = temp; } averageCLC += temp; } averageCLC = averageCLC / N; qCDebug(lcClustering) << "Graph::clusteringCoefficient() network average " << averageCLC; for (vertex = m_graph.cbegin(); vertex != m_graph.cend(); ++vertex) { x = ((*vertex)->CLC() - averageCLC); x *= x; varianceCLC += x; } varianceCLC /= N; return averageCLC; } SocNetV-3.7/src/graph/clustering/graph_clustering_hierarchical.cpp000066400000000000000000000330431524402210300255110ustar00rootroot00000000000000/** * @file graph_clustering_hierarchical.cpp * @brief Implements hierarchical clustering methods for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Performs an hierarchical clustering process (Johnson, 1967) on a given * NxN distance/dissimilarity matrix. The input matrix can be the * the adjacency matrix, the geodesic distance matrix or a derived from them * dissimilarities matrix using a user-specified metric, i.e. euclidean distance. * The method parameter defines how to compute distances (similarities) between * a new cluster the old clusters. Valid values can be: * - Clustering::Single_Linkage: "single-link" or "connectedness" or "minimum" * - Clustering::Complete_Linkage: "complete-link" or "diameter" or "maximum" * - Clustering::Average_Linkage: "average-link" or UPGMA * @param matrix * @param metric * @param method * @param considerWeights * @param inverseWeights * @param dropIsolates */ bool Graph::graphClusteringHierarchical(Matrix &STR_EQUIV, const QString &varLocation, const int &metric, const int &method, const bool &diagonal, const bool &diagram, const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates) { Q_UNUSED(inverseWeights); qCDebug(lcClustering) << "Graph::graphClusteringHierarchical() - " << "metric" << metric << "method" << graphClusteringMethodTypeToString(method) << "diagonal" << diagonal << "diagram" << diagram << "dropIsolates" << dropIsolates; qCDebug(lcClustering) << "Graph::graphClusteringHierarchical() - STR_EQUIV matrix:"; // STR_EQUIV.printMatrixConsole(true); qreal min = RAND_MAX; qreal max = 0; int imin, jmin, imax, jmax, mergedClusterIndex, deletedClusterIndex; qreal distanceNewCluster; // temporarily stores cluster members at each clustering level QList clusteredItems; // maps original and clustered items per their DSM matrix index // so that we know that at Level X the matrix index 0 corresponds to the cluster i.e. { 1,2,4} QMap m_clustersIndex; QMap::iterator it; QMap::iterator prev; QMap::const_iterator sit; // variables for diagram computation QList clusterPairNames; QString cluster1, cluster2; Matrix DSM; // dissimilarities matrix. Note: will be destroyed in the end. // TODO: needs fix when distances matrix with -1 (infinity) elements is used. // compute, if needed, the dissimilarities matrix switch (metric) { case METRIC_NONE: DSM = STR_EQUIV; break; case METRIC_JACCARD_INDEX: createMatrixDissimilarities(STR_EQUIV, DSM, metric, varLocation, diagonal, considerWeights); STR_EQUIV = DSM; break; case METRIC_MANHATTAN_DISTANCE: createMatrixDissimilarities(STR_EQUIV, DSM, metric, varLocation, diagonal, considerWeights); STR_EQUIV = DSM; break; case METRIC_HAMMING_DISTANCE: createMatrixDissimilarities(STR_EQUIV, DSM, metric, varLocation, diagonal, considerWeights); STR_EQUIV = DSM; break; case METRIC_EUCLIDEAN_DISTANCE: createMatrixDissimilarities(STR_EQUIV, DSM, metric, varLocation, diagonal, considerWeights); STR_EQUIV = DSM; break; case METRIC_CHEBYSHEV_MAXIMUM: createMatrixDissimilarities(STR_EQUIV, DSM, metric, varLocation, diagonal, considerWeights); STR_EQUIV = DSM; break; default: break; } int N = DSM.rows(); qCDebug(lcClustering) << "Graph::graphClusteringHierarchical() -" << "initial matrix DSM contents:"; // DSM.printMatrixConsole(); if (DSM.illDefined()) { // DSM.clear(); // STR_EQUIV.clear(); progressStatus("ERROR computing dissimilarities matrix"); return false; } clusteredItems.reserve(N); if (diagram) { clusterPairNames.reserve(N); } m_clustersIndex.clear(); m_clustersPerSequence.clear(); m_clusteringLevel.clear(); m_clustersByName.clear(); m_clusterPairNamesPerSeq.clear(); // // Step 1: Assign each of the N items to its own cluster. // We have N unit clusters // int clustersLeft = N; int seq = 1; // clustering stage/level sequence number VList::const_iterator vit; int i = 0; for (vit = m_graph.cbegin(); vit != m_graph.cend(); ++vit) { // if (dropIsolates) { // if ((*vit)->isIsolated()) { // continue; // } // } // if (!(*vit)->isEnabled()) { // continue; // } if ((*vit)->isEnabled() && (!(*vit)->isIsolated())) { clusteredItems.clear(); clusteredItems << (*vit)->number(); m_clustersIndex[i] = clusteredItems; if (diagram) { m_clustersByName.insert(QString::number(i + 1), clusteredItems); } i++; } } QString pMsg = tr("Computing Hierarchical Clustering. \nPlease wait..."); progressStatus(pMsg); while (clustersLeft > 1) { if (progressCanceled()) { return false; } qCDebug(lcClustering) << "matrix DSM contents now:"; // DSM.printMatrixConsole(); // // Step 2. Find the most similar pair of clusters. // Merge them into a single new cluster. // DSM.NeighboursNearestFarthest(min, max, imin, jmin, imax, jmax); mergedClusterIndex = (imin < jmin) ? imin : jmin; deletedClusterIndex = (mergedClusterIndex == imin) ? jmin : imin; m_clusteringLevel << min; clusteredItems.clear(); clusteredItems = m_clustersIndex[mergedClusterIndex] + m_clustersIndex[deletedClusterIndex]; qCDebug(lcClustering) << "Graph::graphClusteringHierarchical() -" << "level" << min << "seq" << seq << "clusteredItems in level" << clusteredItems; m_clustersPerSequence.insert(seq, clusteredItems); if (diagram) { cluster1.clear(); cluster2.clear(); clusterPairNames.clear(); for (sit = m_clustersByName.constBegin(); sit != m_clustersByName.constEnd(); ++sit) { if (sit.value() == m_clustersIndex[mergedClusterIndex]) { cluster1 = sit.key(); } else if (sit.value() == m_clustersIndex[deletedClusterIndex]) { cluster2 = sit.key(); } } if (cluster1.isNull() && m_clustersIndex[mergedClusterIndex].size() == 1) { cluster1 = QString::number(m_clustersIndex[mergedClusterIndex].first()); } if (cluster2.isNull() && m_clustersIndex[deletedClusterIndex].size() == 1) { cluster1 = QString::number(m_clustersIndex[deletedClusterIndex].first()); } clusterPairNames.append(cluster1); clusterPairNames.append(cluster2); m_clusterPairNamesPerSeq.insert(seq, clusterPairNames); m_clustersByName.insert("c" + QString::number(seq), clusteredItems); qCDebug(lcClustering) << "Computing diagram variables..." << "\n" << "cluster1" << cluster1 << "\n" << "cluster2" << cluster2 << "\n" << "clusterPairNames" << clusterPairNames << "\n" << "m_clusterPairNamesPerSeq" << m_clusterPairNamesPerSeq << "\n" << "m_clustersByName" << m_clustersByName; } // end if diagram // map new cluster to a matrix index m_clustersIndex[mergedClusterIndex] = clusteredItems; qCDebug(lcClustering) << " Clustering seq:" << seq << "\n" << " Level:" << min << "\n" << " Neareast neighbors: (" << imin + 1 << "," << jmin + 1 << ")" << "Minimum/distance:" << min << "\n" << " Farthest neighbors: (" << imax + 1 << "," << jmax + 1 << ")" << "Maximum/distance:" << max << "\n" << " Merge nearest neighbors into a single new cluster:" << mergedClusterIndex + 1 << "\n" << " m_clustersPerSequence" << m_clustersPerSequence; qCDebug(lcClustering) << " Remove key" << deletedClusterIndex << "and shift next values to left... "; it = m_clustersIndex.find(deletedClusterIndex); while (it != m_clustersIndex.end()) { prev = it; ++it; if (it != m_clustersIndex.end()) { prev.value() = it.value(); } } m_clustersIndex.erase(--it); // erase the last element in map qCDebug(lcClustering) << "Finished. " << "\n" << " m_clustersIndex now" << m_clustersIndex << "\n" << " Compute distances " "between the new cluster and the old ones"; // // Step 3. Compute distances (or similarities) between // the single new cluster and the old clusters // int j = mergedClusterIndex; qCDebug(lcClustering) << "j = mergedClusterIndex " << mergedClusterIndex + 1; for (int i = 0; i < clustersLeft; i++) { if (i == deletedClusterIndex) { continue; } switch (method) { case Clustering::Single_Linkage: //"single-linkage": if (i == j) { distanceNewCluster = 0; } else { distanceNewCluster = (DSM.item(i, imin) < DSM.item(i, jmin)) ? DSM.item(i, imin) : DSM.item(i, jmin); } qCDebug(lcClustering) << "Graph::graphClusteringHierarchical() - " << " DSM(" << i + 1 << "," << imin + 1 << ") =" << DSM.item(i, imin) << " DSM(" << i + 1 << "," << jmin + 1 << ") =" << DSM.item(i, jmin) << " ? minimum DSM(" << i + 1 << "," << j + 1 << " =" << distanceNewCluster; break; case Clustering::Complete_Linkage: // "complete-linkage": if (i == j) { distanceNewCluster = 0; } else { distanceNewCluster = (DSM.item(i, imin) > DSM.item(i, jmin)) ? DSM.item(i, imin) : DSM.item(i, jmin); } qCDebug(lcClustering) << "Graph::graphClusteringHierarchical() - " << " DSM(" << i + 1 << "," << imin + 1 << ") =" << DSM.item(i, imin) << " DSM(" << i + 1 << "," << jmin + 1 << ") =" << DSM.item(i, jmin) << " ? maximum DSM(" << i + 1 << "," << j + 1 << " =" << distanceNewCluster; break; case Clustering::Average_Linkage: // mean or "average-linkage" or UPGMA if (i == j) { distanceNewCluster = 0; } else { distanceNewCluster = (DSM.item(i, imin) + DSM.item(i, jmin)) / 2; } qCDebug(lcClustering) << "Graph::graphClusteringHierarchical() - " << " DSM(" << i + 1 << "," << imin + 1 << ") =" << DSM.item(i, imin) << " DSM(" << i + 1 << "," << jmin + 1 << ") =" << DSM.item(i, jmin) << " ? average DSM(" << i + 1 << "," << j + 1 << " =" << distanceNewCluster; break; default: distanceNewCluster = (DSM.item(i, imin) < DSM.item(i, jmin)) ? DSM.item(i, imin) : DSM.item(i, jmin); break; } DSM.setItem(i, j, distanceNewCluster); DSM.setItem(j, i, distanceNewCluster); // DSM.setItem(deletedClusterIndex, j, RAND_MAX); // DSM.setItem(j, deletedClusterIndex, RAND_MAX); } qCDebug(lcClustering) << "Graph::graphClusteringHierarchical() - Finished." << "Resizing old DSM matrix"; // DSM.printMatrixConsole(); DSM.deleteRowColumn(deletedClusterIndex); clustersLeft--; seq++; // // Step 4. Repeat steps 2 and 3 until all remaining items/clusters // are clustered into a single cluster of size N // } // end while clustersLeft clusteredItems.clear(); m_clustersIndex.clear(); qCDebug(lcClustering) << "m_clustersByName" << m_clustersByName; return true; } SocNetV-3.7/src/graph/clustering/graph_triad_census.cpp000066400000000000000000000265611524402210300233260ustar00rootroot00000000000000/** * @file graph_triad_census.cpp * @brief Implements triad census analysis for the Graph class, including MAN classification and triad type labeling. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Conducts a triad census and updates QList::triadTypeFreqs, * which is the list carrying all triad type frequencies * Complexity: O(nΒ³) β€” three nested loops each bounded by N. * @return */ bool Graph::graphTriadCensus() { int mut = 0, asy = 0, nul = 0; int temp_mut = 0, temp_asy = 0, temp_nul = 0, counter_021 = 0; int ver1, ver2, ver3; VList::const_iterator v1; VList::const_iterator v2; VList::const_iterator v3; qCDebug(lcClustering) << "Graph::graphTriadCensus()"; /* * QList::triadTypeFreqs stores triad type frequencies with the following order: * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 * 003 012 102 021D 021U 021C 111D 111U 030T 030C 201 120D 120U 120C 210 300 */ triadTypeFreqs.clear(); for (int i = 0; i <= 15; ++i) { triadTypeFreqs.append(0); qCDebug(lcClustering) << " initializing triadTypeFreqs[" << i << "] = " << triadTypeFreqs[i]; } QString pMsg = tr("Computing Triad Census. \nPlease wait..."); progressStatus(pMsg); for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); v1++) { if (progressCanceled()) { calculatedTriad = false; return false; } for (v2 = (v1 + 1); v2 != m_graph.cend(); v2++) { ver1 = (*v1)->number(); ver2 = (*v2)->number(); temp_mut = 0, temp_asy = 0, temp_nul = 0; if ((*v1)->hasEdgeTo(ver2)) { if ((*v2)->hasEdgeTo(ver1)) temp_mut++; else temp_asy++; } else if ((*v2)->hasEdgeTo(ver1)) temp_asy++; else temp_nul++; for (v3 = (v2 + 1); v3 != m_graph.cend(); v3++) { mut = temp_mut; asy = temp_asy; nul = temp_nul; ver3 = (*v3)->number(); if ((*v1)->hasEdgeTo(ver3)) { if ((*v3)->hasEdgeTo(ver1)) mut++; else asy++; } else if ((*v3)->hasEdgeTo(ver1)) asy++; else nul++; if ((*v2)->hasEdgeTo(ver3)) { if ((*v3)->hasEdgeTo(ver2)) mut++; else asy++; } else if ((*v3)->hasEdgeTo(ver2)) asy++; else nul++; qCDebug(lcClustering) << "triad of (" << ver1 << "," << ver2 << "," << ver3 << ") = (" << mut << "," << asy << "," << nul << ")"; triadType_examine_MAN_label(mut, asy, nul, (*v1), (*v2), (*v3)); if (mut == 3 && asy == 0 && nul == 0) { counter_021++; } } // end 3rd for } // end 2rd for } // end 1rd for qCDebug(lcClustering) << " ****** 003 COUNTER: " << counter_021; calculatedTriad = true; return true; } /** Examines the triad type (in Mutual-Asymmetric-Null label format) and increases by one the proper frequency element inside QList::triadTypeFreqs */ void Graph::triadType_examine_MAN_label(int mut, int asy, int nul, GraphVertex *vert1, GraphVertex *vert2, GraphVertex *vert3) { VList m_triad; bool isDown = false, isUp = false, isCycle = false, isTrans = false; bool isOutLinked = false, isInLinked = false; qCDebug(lcClustering) << "Graph::triadType_examine_MAN_label() " << " adding (" << vert1->number() << "," << vert2->number() << "," << vert3->number() << ") to m_triad "; m_triad << vert1 << vert2 << vert3; switch (mut) { case 0: switch (asy) { case 0: //"003"; triadTypeFreqs[0]++; break; case 1: //"012"; triadTypeFreqs[1]++; break; case 2: // "021?" - find out! foreach (GraphVertex *source, m_triad) { isOutLinked = false; isInLinked = false; foreach (GraphVertex *target, m_triad) { if (source->number() == target->number()) continue; if (source->hasEdgeTo(target->number())) { if (isOutLinked) { triadTypeFreqs[3]++; //"021D" break; } else if (isInLinked) { triadTypeFreqs[5]++; //"021C" break; } else { isOutLinked = true; } } else if (target->hasEdgeTo(source->number())) { if (isInLinked) { triadTypeFreqs[4]++; //"021U" break; } else if (isOutLinked) { triadTypeFreqs[5]++; //"021C" break; } else { isInLinked = true; } } } } break; case 3: qCDebug(lcClustering) << "triad vertices: ( " << vert1->number() << ", " << vert2->number() << ", " << vert3->number() << " ) = (" << mut << "," << asy << "," << nul << ")"; isTrans = false; foreach (GraphVertex *source, m_triad) { qCDebug(lcClustering) << " vertex " << source->number(); isOutLinked = false; foreach (GraphVertex *target, m_triad) { if (source->number() == target->number()) continue; if (source->hasEdgeTo(target->number())) { if (isOutLinked) { triadTypeFreqs[8]++; //"030T" isTrans = true; break; } else { isOutLinked = true; } } } } if (!isTrans) { //"030C" triadTypeFreqs[9]++; } break; } break; case 1: switch (asy) { case 0: //"102"; triadTypeFreqs[2]++; break; case 1: isUp = false; foreach (GraphVertex *source, m_triad) { isInLinked = false; foreach (GraphVertex *target, m_triad) { if (source->number() == target->number()) continue; if (target->hasEdgeTo(source->number())) { if (isInLinked) { triadTypeFreqs[6]++; //"030T" isUp = true; break; } else { isInLinked = true; } } } } if (!isUp) { //"111U" triadTypeFreqs[7]++; } break; case 2: isDown = false; isUp = false; isCycle = true; qCDebug(lcClustering) << "triad vertices: ( " << vert1->number() << ", " << vert2->number() << ", " << vert3->number() << " ) = (" << mut << "," << asy << "," << nul << ")"; foreach (GraphVertex *source, m_triad) { isOutLinked = false; isInLinked = false; foreach (GraphVertex *target, m_triad) { if (source->number() == target->number()) continue; if (source->hasEdgeTo(target->number())) { if (target->hasEdgeTo(source->number())) { isInLinked = true; isOutLinked = true; continue; } else if (isOutLinked && !isInLinked) { triadTypeFreqs[11]++; //"120D" isDown = true; isCycle = false; break; } else { isOutLinked = true; } } else if (target->hasEdgeTo(source->number())) { if (source->hasEdgeTo(target->number())) { isOutLinked = true; isInLinked = true; continue; } else if (isInLinked && !isOutLinked) { triadTypeFreqs[12]++; //"120U" isUp = true; isCycle = false; break; } else { isInLinked = true; } } } if (isUp || isDown) break; } if (isCycle) { //"120C" triadTypeFreqs[13]++; } break; case 3: // nothing here! break; } break; case 2: switch (asy) { case 0: // "201" triadTypeFreqs[10]++; break; case 1: // "210" triadTypeFreqs[14]++; break; } break; case 3: // "300" if (asy == 0 && nul == 0) triadTypeFreqs[15]++; break; } } SocNetV-3.7/src/graph/cohesion/000077500000000000000000000000001524402210300163745ustar00rootroot00000000000000SocNetV-3.7/src/graph/cohesion/graph_cliques.cpp000066400000000000000000000305171524402210300217340ustar00rootroot00000000000000/** * @file graph_cliques.cpp * @brief Implements clique detection and cohesion-related algorithms for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Called from Graph::graphCliques to add a new clique (list of vertices) * Adds clique info to each clique member and updates co-membership matrix CLQM . * @param list * @return */ void Graph::graphCliqueAdd(const QList &clique) { m_cliques.insert(clique.size(), clique); qCDebug(lcCohesion) << "Graph::graphCliqueAdd() - Added clique:" << clique << "of size" << clique.size() << "Total cliques:" << m_cliques.size(); int index1 = 0, index2 = 0, cliqueCount = 0; foreach (int actor1, clique) { index1 = vpos[actor1]; qCDebug(lcCohesion) << "Graph::graphCliqueAdd() - Updating cliques in actor1:" << actor1 << "vpos:" << index1; m_graph[index1]->cliqueAdd(clique); foreach (int actor2, clique) { index2 = vpos[actor2]; cliqueCount = CLQM.item(index1, index2); CLQM.setItem(index1, index2, (cliqueCount + 1)); qCDebug(lcCohesion) << "Graph::graphCliqueAdd() - Updated co-membership matrix CLQM" << "actor1:" << actor1 << "actor2:" << actor2 << "old matrix element: (" << index1 << "," << index2 << ")=" << cliqueCount << " -- updated to:" << CLQM.item(index1, index2); } } } /** * @brief Finds all maximal cliques in the graph using the Bron–Kerbosch algorithm * with Tomita et al. (2006) pivot selection. * * --- Algorithm overview --- * * The Bron–Kerbosch algorithm [1] is a recursive backtracking procedure that * maintains three disjoint vertex sets at each call: * * R β€” the clique built so far (all vertices in R are mutually adjacent). * P β€” candidate vertices that can still extend R (each is adjacent to all of R). * X β€” vertices already processed that are also adjacent to all of R * (used to avoid reporting the same clique more than once). * * When both P and X are empty, R cannot be extended and no super-set of R was * reported before β€” so R is a maximal clique. * * --- Pivot selection (Tomita et al., 2006) --- * * Without pivoting the algorithm iterates over every vertex in P at each level, * leading to a worst-case exponential blow-up even for graphs with few cliques. * * Tomita, Tanaka & Takahashi [2] proved that choosing a pivot vertex u ∈ PβˆͺX * that maximises |N(u) ∩ P| (the size of u's neighbourhood intersected with P) * allows the main loop to enumerate only the vertices in P \ N(u) β€” the * non-neighbours of u inside the candidate set. * * Why this is correct: any maximal clique that extends R must contain at least * one vertex from P \ N(u), because if a clique contained only neighbours of u * it could be extended by u itself (since u is adjacent to all of them and to * all of R), contradicting maximality. So we lose no cliques by restricting * the loop to P \ N(u). * * Why this is faster: the pivot u was chosen to maximise |N(u) ∩ P|, which * minimises |P \ N(u)|. In the best case (a dense graph) |P \ N(u)| β‰ˆ 1, * reducing each level of recursion to a single branch. For sparse graphs the * improvement is smaller but still significant in practice. * * References: * [1] Bron, C. & Kerbosch, J. (1973). "Algorithm 457: Finding all cliques of * an undirected graph." Commun. ACM, 16(9), 575–577. * [2] Tomita, E., Tanaka, A. & Takahashi, H. (2006). "The worst-case time * complexity for generating all maximal cliques and computational * experiments." Theoretical Computer Science, 363(1), 28–42. * https://doi.org/10.1016/j.tcs.2006.06.015 * * @param R Current clique under construction (vertices already chosen). * @param P Candidate vertices that can extend R. * @param X Excluded vertices (already processed at this level). */ void Graph::graphCliques(QSet R, QSet P, QSet X) { csRecDepth++; qCDebug(lcCohesion) << "Graph::graphCliques() - STARTS HERE. csRecDepth:" << csRecDepth << " - Check if we are at initialization step"; QList myNeightbors; // ----------------------------------------------------------------------- // Initialisation step (first call only): R, P and X are all empty. // Build the full candidate set P = V(G) and pre-compute the neighbour // sets for all vertices into neighboursHash so that every recursive call // can perform O(1) set lookups instead of traversing edge lists. // ----------------------------------------------------------------------- if (R.isEmpty() && P.isEmpty() && X.isEmpty()) { int V = vertices(); P.reserve(V); R.reserve(V); X.reserve(V); P = verticesSet(); // P starts as the full vertex set qCDebug(lcCohesion) << "Graph::graphCliques() - initialization step. R, X empty and P=V(G): " << P; CLQM.zeroMatrix(V, V); // co-membership matrix reset m_cliques.clear(); VList::const_iterator it; int vertex = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { vertex = (*it)->number(); // reciprocalNeighborhoodList() returns neighbours connected by edges // in BOTH directions (i.e. mutual ties), which is the correct notion // of adjacency for maximal clique detection in undirected graphs. myNeightbors = (*it)->reciprocalNeighborhoodList(); neighboursHash[vertex] = QSet(myNeightbors.constBegin(), myNeightbors.constEnd()); qCDebug(lcCohesion) << "Graph::graphCliques() - init. NeighborhoodList of v" << vertex << ": " << neighboursHash[vertex]; (*it)->clearCliques(); } } qCDebug(lcCohesion) << "Graph::graphCliques() - check if P and X are both empty..."; // ----------------------------------------------------------------------- // Base case: P and X are both empty. // R is a maximal clique β€” record it and return. // ----------------------------------------------------------------------- if (P.isEmpty() && X.isEmpty()) { qCDebug(lcCohesion) << "Graph::graphCliques() - P and X are both empty. MAXIMAL clique R=" << R; QList clique = R.values(); graphCliqueAdd(clique); csRecDepth--; return; } // ----------------------------------------------------------------------- // Pivot selection (Tomita et al., 2006 β€” see header comment). // // Scan every vertex u in PβˆͺX and compute |N(u) ∩ P|. // Keep the u that maximises this count. Ties are broken arbitrarily // (we just keep the first maximum found). // ----------------------------------------------------------------------- int pivot = -1; int bestCoverage = -1; // tracks max |N(u) ∩ P| seen so far // Combine P and X into a single candidate pool for pivot search. const QSet PunionX = P | X; for (int u : PunionX) { // |N(u) ∩ P|: count how many candidate vertices u is adjacent to. const int coverage = (neighboursHash[u] & P).size(); if (coverage > bestCoverage) { bestCoverage = coverage; pivot = u; } } // P \ N(pivot): the vertices we actually need to branch on. // Every maximal clique must contain at least one vertex from this set // (see header comment for the correctness argument). const QSet candidates = P - neighboursHash[pivot]; qCDebug(lcCohesion) << "Graph::graphCliques() - pivot:" << pivot << " |N(pivot)∩P|:" << bestCoverage << " |P\\N(pivot)|:" << candidates.size() << " (saved" << (P.size() - candidates.size()) << "branches)"; // ----------------------------------------------------------------------- // Main loop: iterate over candidates = P \ N(pivot) only. // ----------------------------------------------------------------------- QSet NBS; QSet Rnext, Pnext, Xnext; // We need a stable copy to iterate because P is mutated inside the loop // (v is moved from P to X after its recursive subtree is explored). const QList candidateList = candidates.values(); qCDebug(lcCohesion) << "Graph::graphCliques() - Start looping over candidates P\\N(pivot)"; for (int v : candidateList) { qCDebug(lcCohesion) << "Graph::graphCliques() - CURRENT v:" << v << " P:" << P << " P.count=" << P.size() << " R:" << R << " X:" << X; NBS = neighboursHash[v]; // neighbours of v (pre-computed at init) // Skip self-loops: a vertex with only a tie to itself cannot join any clique. if (NBS.size() == 1 && NBS.contains(v)) { qCDebug(lcCohesion) << "Graph::graphCliques() - v:" << v << "has only a self-tie, skip"; // Move v from P to X so it is not re-visited. P.remove(v); X.insert(v); continue; } // Build the arguments for the recursive call: // R βˆͺ {v} β€” extend the current clique with v // P ∩ N(v) β€” restrict candidates to neighbours of v (they can still extend the clique) // X ∩ N(v) β€” restrict excluded set to neighbours of v Rnext = R; Rnext << v; // R βˆͺ {v} Pnext = P & NBS; // P ∩ N(v) Xnext = X & NBS; // X ∩ N(v) qCDebug(lcCohesion) << "Graph::graphCliques() - v:" << v << "RECURSIVE CALL: R⋃{v}=" << Rnext << " Pβ‹‚N(v)=" << Pnext << " Xβ‹‚N(v)=" << Xnext; // Emit progress only at recursion depth 1 (top-level branches) to // avoid flooding the event loop on deep recursions. if (csRecDepth == 1) { progressStatus(tr("Finding cliques: Recursive backtracking for actor ") + QString::number(v)); if (progressCanceled()) { csRecDepth--; return; } } try { graphCliques(Rnext, Pnext, Xnext); } catch (...) { qCDebug(lcCohesion) << "Graph::graphCliques() - ERROR in recursive call"; return; } // After exploring all cliques that contain v, move v from P to X. // X records that v has been processed at this level; it blocks future // candidates from forming a clique with exactly the same members as Rβˆͺ{v}. P.remove(v); X.insert(v); qCDebug(lcCohesion) << "Graph::graphCliques() - v:" << v << " returned from recursion. Moved to X." << " P=" << P << " X=" << X; } // end for candidateList qCDebug(lcCohesion) << "Graph::graphCliques() - FINISHED candidate loop at csRecDepth:" << csRecDepth; csRecDepth--; } /** Returns the number of maximal cliques which include a given actor */ int Graph::graphCliquesContaining(const int &actor, const int &size) { qCDebug(lcCohesion) << "*** Graph::graphCliquesContaining(" << actor << ")"; int cliqueCounter = 0; foreach (QList clique, m_cliques) { if (size != 0) { if (clique.size() != size) continue; } if (clique.contains(actor)) { cliqueCounter++; } } return cliqueCounter; } /** * @brief Graph::graphCliquesOfSize * Returns the number of maximal cliques of a given size * @param size * @return */ int Graph::graphCliquesOfSize(const int &size) { qCDebug(lcCohesion) << "Graph::graphCliquesOfSize()"; return m_cliques.values(size).size(); } SocNetV-3.7/src/graph/cohesion/graph_connectivity.cpp000066400000000000000000000375761524402210300230210ustar00rootroot00000000000000/** * @file graph_connectivity.cpp * @brief Implements local and global vertex connectivity (Menger's theorem via max-flow) for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include "graphvertex.h" #include #include #include #include #include #include #include #include #include namespace { // ---------------------------------------------------------------------------- // Adjacency helpers, shared by every function below. "respectDirection" is the same // weak/strong choice Graph::graphStronglyConnectedComponents() and the GUI's Connectedness // prompt use: true respects the graph's actual edge directions (directed reachability, matching // graphDistanceGeodesic()); false treats every edge as bidirectional (matching // graphWeaklyConnectedComponents()'s "weak" convention). Undirected graphs only ever call these // with respectDirection == false in practice (there's nothing to ask), which naturally collapses // to plain undirected adjacency since every edge already exists in both directions. // ---------------------------------------------------------------------------- QSet outNeighborsOf(Graph &g, int v, int currentRelation) { QSet result; const int vi = g.vertexIndexByNumber(v); if (vi < 0) return result; const GraphVertex *gv = g.vertexAtIndex(vi); for (auto eit = gv->outEdges().cbegin(); eit != gv->outEdges().cend(); ++eit) { if (eit.value().first != currentRelation) continue; if (!eit.value().second.second) continue; result.insert(eit.key()); } return result; } QSet inNeighborsOf(Graph &g, int v, int currentRelation) { QSet result; const int vi = g.vertexIndexByNumber(v); if (vi < 0) return result; const GraphVertex *gv = g.vertexAtIndex(vi); for (auto eit = gv->inEdges().cbegin(); eit != gv->inEdges().cend(); ++eit) { if (eit.value().first != currentRelation) continue; if (!eit.value().second.second) continue; result.insert(eit.key()); } return result; } // Union of out- and in-neighbors when respectDirection is false, so a reciprocated or // one-directional pair still counts as exactly one neighbor - this is the "underlying simple // undirected graph" adjacency that weak-mode connectivity is defined over. QSet neighborsOf(Graph &g, int v, int currentRelation, bool respectDirection) { QSet result = outNeighborsOf(g, v, currentRelation); if (!respectDirection) result.unite(inNeighborsOf(g, v, currentRelation)); return result; } bool adjacent(Graph &g, int u, int v, int currentRelation, bool respectDirection) { return outNeighborsOf(g, u, currentRelation).contains(v) || (!respectDirection && inNeighborsOf(g, u, currentRelation).contains(v)); } QList enabledVertexNumbers(Graph &g) { QList verts; for (auto it = g.verticesBegin(); it != g.verticesEnd(); ++it) { if ((*it)->isEnabled()) verts.append((*it)->number()); } return verts; } // ---------------------------------------------------------------------------- // Vertex-split max-flow, implementing local vertex connectivity via Menger's theorem. // // Menger's theorem: for non-adjacent s,t, the maximum number of internally vertex-disjoint // s-to-t paths equals the minimum number of vertices (other than s,t) whose removal disconnects // them - exactly the local vertex connectivity kappa(s,t) we want. "Internally vertex-disjoint" // paths turn into a max-flow problem via vertex splitting: every vertex v becomes two flow-graph // nodes v_in and v_out joined by an edge of capacity 1 (routing a unit of flow through v now // costs exactly one unit of the thing we're counting: vertex removals), and every real graph edge // u->v becomes an edge u_out->v_in of effectively infinite capacity (so only vertex splits, never // edges, constrain the flow). s and t get an infinite-capacity split instead of capacity 1, since // they can never be part of their own separating set. The max-flow value from s_out to t_in is // then exactly kappa(s,t). // // Max-flow is computed via Edmonds-Karp (repeated BFS shortest augmenting path). This is not the // asymptotically fastest max-flow algorithm in general (Dinic's or push-relabel do better on // dense graphs with large capacities), but it's the right fit here specifically: every capacity // in this flow graph is either 1 (vertex splits) or effectively infinite (real edges and s/t's // splits), and s,t are non-adjacent by construction (checked by the caller), so every s-to-t path // must cross at least one capacity-1 split edge - meaning every augmenting path found has // bottleneck exactly 1. The number of BFS rounds therefore equals the final flow value directly, // which is bounded by min(|out-neighbors of s|, |in-neighbors of t|) <= n-2. At SocNetV's network // sizes this is a handful to a few hundred BFS passes at most, each O(V+E) - trivially fast, and // far simpler to get right than the algorithms that exist to shave complexity that doesn't matter // at this scale. struct FlowEdge { int to; qint64 cap; }; void addFlowEdge(QVector &edges, QVector> &adjList, int from, int to, qint64 cap) { adjList[from].append(edges.size()); edges.append(FlowEdge{to, cap}); adjList[to].append(edges.size()); edges.append(FlowEdge{from, 0}); // paired reverse residual edge, capacity 0 until flow uses the forward one } // One Edmonds-Karp round: BFS for a shortest s->t path over edges with remaining capacity, then // push flow equal to the path's bottleneck capacity. Because edges[i] and edges[i^1] are always // the forward/reverse pair for the same underlying connection (addFlowEdge() always appends them // together), pushing flow on edges[i] and crediting it back on edges[i^1] is all the residual- // capacity bookkeeping max-flow needs - no separate "flow used" structure required. Returns the // amount of flow pushed, or 0 once no augmenting path remains (max-flow reached). qint64 augmentOnce(int numFlowNodes, const QVector> &adjList, QVector &edges, int s, int t) { QVector viaEdge(numFlowNodes, -1); QVector visited(numFlowNodes, false); QQueue queue; queue.enqueue(s); visited[s] = true; while (!queue.isEmpty() && !visited[t]) { const int u = queue.dequeue(); for (int eid : adjList[u]) { const FlowEdge &e = edges[eid]; if (!visited[e.to] && e.cap > 0) { visited[e.to] = true; viaEdge[e.to] = eid; queue.enqueue(e.to); } } } if (!visited[t]) return 0; qint64 bottleneck = std::numeric_limits::max(); for (int v = t; v != s; ) { const int eid = viaEdge[v]; bottleneck = std::min(bottleneck, edges[eid].cap); v = edges[eid ^ 1].to; } for (int v = t; v != s; ) { const int eid = viaEdge[v]; edges[eid].cap -= bottleneck; edges[eid ^ 1].cap += bottleneck; v = edges[eid ^ 1].to; } return bottleneck; } // Builds the vertex-split flow graph over `verts` and returns the max-flow from source to // target, i.e. kappa(source, target). Caller guarantees source != target and they are not // adjacent (Menger's theorem doesn't apply otherwise - see Graph::graphNodeConnectivity()). int localVertexConnectivityFlow(Graph &g, const QList &verts, int source, int target, int currentRelation, bool respectDirection) { const int n = verts.size(); QHash nodeIndex; nodeIndex.reserve(n); for (int i = 0; i < n; ++i) nodeIndex[verts[i]] = i; constexpr qint64 INF_CAP = std::numeric_limits::max() / 4; const int numFlowNodes = 2 * n; // vertex i -> in-node 2*i, out-node 2*i+1 QVector> adjList(numFlowNodes); QVector edges; edges.reserve(4 * n); for (int i = 0; i < n; ++i) { const int v = verts[i]; const qint64 splitCap = (v == source || v == target) ? INF_CAP : 1; addFlowEdge(edges, adjList, 2 * i, 2 * i + 1, splitCap); } for (int i = 0; i < n; ++i) { const int u = verts[i]; const QSet nbrs = outNeighborsOf(g, u, currentRelation); QSet allNbrs = nbrs; if (!respectDirection) allNbrs.unite(inNeighborsOf(g, u, currentRelation)); for (int w : allNbrs) { const auto jt = nodeIndex.constFind(w); if (jt == nodeIndex.constEnd()) continue; // neighbor outside the enabled-vertex set - not part of this computation addFlowEdge(edges, adjList, 2 * i + 1, 2 * jt.value(), INF_CAP); } } const int sOut = 2 * nodeIndex.value(source) + 1; const int tIn = 2 * nodeIndex.value(target); qint64 totalFlow = 0; qint64 pushed; while ((pushed = augmentOnce(numFlowNodes, adjList, edges, sOut, tIn)) > 0) totalFlow += pushed; return static_cast(totalFlow); } } // namespace /** * @brief Local vertex connectivity kappa(source, target): the minimum number of nodes, other * than source and target themselves, whose removal disconnects target from source. * * Computed via Menger's theorem / vertex-split max-flow - see the detailed algorithm comment on * localVertexConnectivityFlow() above. * * source and target being directly adjacent is reported as NodeConnectivityStatus::Adjacent, not * as a number: Menger's theorem requires non-adjacency (an edge is a "path" no vertex removal can * ever break), so there is no finite, meaningful cut size to report - reporting some sentinel * number here would silently misrepresent "cannot be separated by removing other nodes" as an * ordinary connectivity value. * * @param source * @param target * @param respectDirection true for the "strong" reading (directed reachability, source must * reach target via directed paths); false for "weak" (every edge treated as bidirectional). * Only meaningful to vary on a directed graph - see the GUI's Connectedness/Node * Connectivity prompts, which only ask when isDirected() is true. * @return NodeConnectivityResult - see graph.h. Ok.value == 0 is a normal, valid answer (target * is simply unreachable from source under the chosen direction mode). */ Graph::NodeConnectivityResult Graph::graphNodeConnectivity(int source, int target, bool respectDirection) { NodeConnectivityResult result; if (source == target || vertexIndexByNumber(source) < 0 || vertexIndexByNumber(target) < 0) { result.status = NodeConnectivityStatus::Invalid; return result; } const int currentRelation = relationCurrent(); if (adjacent(*this, source, target, currentRelation, respectDirection)) { result.status = NodeConnectivityStatus::Adjacent; return result; } const QList verts = enabledVertexNumbers(*this); result.status = NodeConnectivityStatus::Ok; result.value = localVertexConnectivityFlow(*this, verts, source, target, currentRelation, respectDirection); return result; } /** * @brief Global vertex connectivity kappa(G): the minimum, over every non-adjacent pair of * vertices, of their local vertex connectivity (graphNodeConnectivity()) - the network's * worst-case robustness to node removal, i.e. the fewest nodes that would need to be removed to * disconnect the network at its weakest point. * * Naive pairwise-minimum algorithm: iterate all non-adjacent pairs, tracking the minimum local * connectivity seen so far, starting that minimum at the cheap degree bound below and pruning * with an early exit once it can't go any lower. This is O(n^2) local-connectivity computations * in the worst case (O(n) for directed "strong" mode, since kappa(s,t) can differ from kappa(t,s) * so ordered pairs must be tested) - deliberately the simple approach rather than the smarter * O(n) algorithm (Even 1975, which fixes one vertex and reuses far fewer max-flow computations): * SocNetV's networks and this feature's usage pattern (an occasional, user-triggered analysis, * not a hot path) don't call for that extra complexity yet, and the two pruning steps below * already cut the common cases down a lot in practice. * * Two pruning steps, both used here: * - Fast path: if the graph is already disconnected (per graphWeaklyConnectedComponents() / * graphStronglyConnectedComponents(), whichever matches respectDirection), kappa(G) = 0 * immediately, with no max-flow computation at all. This is purely an optimization, not a * correctness requirement: the pair loop below would reach the same answer on its own the * moment it tests the unreachable pair, since local connectivity between an unreachable pair is * 0 by construction (max-flow finds no augmenting path) - but checking the already-cached * component count first avoids running any flow computation for the common case of an obviously * fragmented network. * - Degree bound (Whitney's inequality): kappa(G) <= delta(G), the minimum vertex degree (its * directed analogue: kappa(D) <= min over v of min(indeg(v), outdeg(v))). Seeding the running * minimum with this bound and only ever lowering it means a complete graph - where no * non-adjacent pair exists at all to test - needs no special case: the pair loop simply never * executes, leaving the initial degree bound as the final answer, which is exactly correct * (kappa(K_n) = n-1, the minimum degree of K_n). * * @param respectDirection true for strong connectivity (ordered pairs, directed reachability); * false for weak (unordered pairs, every edge treated as bidirectional). Only meaningful * to vary on a directed graph. * @return kappa(G). * * @note Precondition: at least 2 enabled vertices. Callers (the GUI's Graph Connectivity action) * special-case 0/1-vertex networks the same way Connectedness already does, so this is * never invoked otherwise. */ int Graph::graphConnectivity(bool respectDirection) { const int currentRelation = relationCurrent(); const QList verts = enabledVertexNumbers(*this); const int n = verts.size(); const bool graphIsConnected = respectDirection ? (graphStronglyConnectedComponents() == 1) : (graphWeaklyConnectedComponents() == 1); if (!graphIsConnected) { qCDebug(lcCohesion) << "Graph::graphConnectivity() - graph disconnected, kappa(G)=0"; return 0; } int best = INT_MAX; for (int v : verts) { int deg; if (respectDirection) deg = std::min(outNeighborsOf(*this, v, currentRelation).size(), inNeighborsOf(*this, v, currentRelation).size()); else deg = neighborsOf(*this, v, currentRelation, false).size(); best = std::min(best, deg); } qCDebug(lcCohesion) << "Graph::graphConnectivity() - degree bound:" << best; for (int i = 0; i < n && best > 0; ++i) { const int jStart = respectDirection ? 0 : i + 1; for (int j = jStart; j < n && best > 0; ++j) { if (i == j) continue; const int u = verts[i]; const int w = verts[j]; if (adjacent(*this, u, w, currentRelation, respectDirection)) continue; const int k = localVertexConnectivityFlow(*this, verts, u, w, currentRelation, respectDirection); best = std::min(best, k); } } qCDebug(lcCohesion) << "Graph::graphConnectivity() - kappa(G):" << best; return best; } SocNetV-3.7/src/graph/core/000077500000000000000000000000001524402210300155155ustar00rootroot00000000000000SocNetV-3.7/src/graph/core/graph_metadata.cpp000066400000000000000000000157201524402210300211670ustar00rootroot00000000000000/** * @file graph_metadata.cpp * @brief Implements graph metadata and modification state methods for the Graph * class (name/filename/format, modified/loaded/saved flags). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" /** * @brief Returns the name of the current graph * * If graph name is empty, then returns current relation name. * If no relation exists, returns "noname" * * @return QString */ QString Graph::getName() const { if (m_graphName.isEmpty()) { if (!(relationCurrentName().isEmpty())) { return relationCurrentName(); } else { return "noname"; } } return m_graphName; } /** * @brief Sets the name of the current graph * * @param graphName */ void Graph::setName(const QString &graphName) { qCDebug(lcGraphCore) << "Setting graph name to:" << graphName; m_graphName = graphName; } /** * @brief Returns the file name of the current graph, if any. * * @return QString */ QString Graph::getFileName() const { return m_fileName; } /** * @brief Sets the file name of the current graph * * @param fileName */ void Graph::setFileName(const QString &fileName) { qCDebug(lcGraphCore) << "Setting graph filename to:" << fileName; m_fileName = fileName; } /** * @brief Returns the format of the last file opened * * @return int */ int Graph::getFileFormat() const { return m_fileFormat; } void Graph::setFileFormat(const int &fileFormat) { qCDebug(lcGraphCore) << "Setting graph file format to:" << fileFormat; m_fileFormat = fileFormat; } /** * @brief Returns true if the fileFormat is supported for saving * @param fileFormat * @return */ bool Graph::isFileFormatExportSupported(const int &fileFormat) const { if (m_graphFileFormatExportSupported.contains(fileFormat)) { return true; } return false; } /** * @brief Sets the graph modification status. * * If there are major changes or new network, it signals to MW to update the UI. * * @param int graphNewStatus * @param bool signalMW */ void Graph::setModStatus(const int &graphNewStatus, const bool &signalMW) { if (m_graphModStatus == ModStatus::NewNet && isEmpty()) { // New network, no vertices. Don't change status. qCDebug(lcGraphCore) << "This is a empty new network. Will not change status."; emit signalGraphModified(isDirected(), 0, 0, 0, false); return; } else if (graphNewStatus == ModStatus::NewNet) { qCDebug(lcGraphCore) << "This is a new network. Setting graph as new..."; m_graphModStatus = graphNewStatus; emit signalGraphModified(isDirected(), m_totalVertices, edgesEnabled(), graphDensity(), false); return; } else if (graphNewStatus == ModStatus::SavedUnchanged) { // this is called after loading or saving a file qCDebug(lcGraphCore) << "Setting graph as saved/unchanged..."; m_graphModStatus = graphNewStatus; emit signalGraphSavedStatus(true); return; } else if (graphNewStatus > ModStatus::MajorChanges) { // This is called from any method that alters the graph structure, // thus all prior computations are invalidated m_graphModStatus = graphNewStatus; // Init all calculated* flags to false, // to force all relevant methods to recompute calculatedGraphReciprocity = false; calculatedGraphSymmetry = false; calculatedGraphWeighted = false; calculatedGraphDensity = false; calculatedEdges = false; calculatedVertices = false; calculatedVerticesList = false; calculatedVerticesSet = false; calculatedIsolates = false; calculatedTriad = false; calculatedAdjacencyMatrix = false; calculatedDistances = false; calculatedCentralities = false; m_graphWeaklyConnectedComponents = 0; m_graphStronglyConnectedComponents = 0; m_vertexComponentId.clear(); calculatedDP = false; calculatedDC = false; calculatedPP = false; calculatedIRCC = false; calculatedIC = false; calculatedEVC = false; calculatedKC = false; calculatedBPC = false; calculatedPRP = false; if (signalMW) { emit signalGraphModified(isDirected(), m_totalVertices, edgesEnabled(), graphDensity(), true); return; } } else if (graphNewStatus > ModStatus::MinorOptions) { // this is called from Graph methods that inflict minor changes, // i.e. changing vertex positions, labels, etc if (m_graphModStatus < ModStatus::MajorChanges) { // Do not change status if current status is > MajorChanges m_graphModStatus = graphNewStatus; } emit signalGraphSavedStatus(false); return; } else { qCritical() << "Strange. I should not reach this code..."; m_graphModStatus = graphNewStatus; } } /** * @brief Returns true of graph is modified (edges/vertices added/removed) * @return */ bool Graph::isModified() const { if (m_graphModStatus > ModStatus::MajorChanges) { qCDebug(lcGraphCore) << "Graph::isModified() - isModified: true"; return true; } qCDebug(lcGraphCore) << "Graph::isModified() - isModified: false"; return false; } /** * @brief Returns true if a graph has been loaded from a file. * @return */ bool Graph::isLoaded() const { if (!getFileName().isEmpty() && getFileFormat() != FileType::UNRECOGNIZED) { qCDebug(lcGraphCore) << "isLoaded: true "; return true; } qCDebug(lcGraphCore) << "isLoaded: false "; return false; } /** * @brief Returns true if the graph is saved. * @return */ bool Graph::isSaved() const { if (m_graphModStatus == ModStatus::NewNet) { qCDebug(lcGraphCore) << "isSaved: true (new net)"; return true; } else if (m_graphModStatus == ModStatus::SavedUnchanged) { qCDebug(lcGraphCore) << "isSaved: true"; return true; } qCDebug(lcGraphCore) << "isSaved: false"; return false; } SocNetV-3.7/src/graph/core/graph_state_flags.cpp000066400000000000000000000222271524402210300217030ustar00rootroot00000000000000/** * @file graph_state_flags.cpp * @brief Implements graph-level state flags and toggles for the Graph class * (weighted/symmetric/directed/undirected). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" /** * @brief Returns true if the **current relation** has at least one edge * with weight other than 0 or 1 (i.e. the relation is valued/weighted). * * The result is cached via @c calculatedGraphWeighted and invalidated on * every relation switch (@see relationSet()). Consequently, for multi-relation * graphs this method only reflects the active relation β€” switching to a * non-weighted relation after a weighted one will return false. * * When you need to know whether *any* relation is weighted, use * @see isAnyRelationWeighted() instead. * * Complexity: O(nΒ²) on cache miss, O(1) on hit. */ bool Graph::isWeighted() { if (calculatedGraphWeighted) { qCDebug(lcGraphCore) << "graph not modified. Returning isWeighted: " << m_graphIsWeighted; return m_graphIsWeighted; } // Reset before scan: only set true if a non-unit weight is found. // Without this, a stale true from a previous relation's edges would persist. m_graphIsWeighted = false; qreal m_weight = 0; VList::const_iterator it, it1; QString pMsg = tr("Checking if the graph edges are valued. \nPlease wait..."); progressStatus(pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { for (it1 = m_graph.cbegin(); it1 != m_graph.cend(); ++it1) { m_weight = edgeExists((*it1)->number(), (*it)->number()); if (m_weight != 1 && m_weight != 0) { setWeighted(true); break; } } if (m_graphIsWeighted) { break; } } calculatedGraphWeighted = true; qCDebug(lcGraphCore) << "graph is weighted:" << m_graphIsWeighted; return m_graphIsWeighted; } /** * @brief Returns true if any relation in the graph has at least one edge * with weight other than 0 or 1. * * Unlike isWeighted(), which only scans the current relation, this method * iterates every relation. It is used to report overall graph weightedness * independently of which relation is currently active. */ bool Graph::isAnyRelationWeighted() { const int savedRel = m_curRelation; const int numRels = relations(); bool anyWeighted = false; for (int r = 0; r < numRels && !anyWeighted; ++r) { relationSet(r, false); anyWeighted = isWeighted(); } if (m_curRelation != savedRel) relationSet(savedRel, false); return anyWeighted; } /** * @brief Sets the graph to be weighted ( valued edges ). * @param toggle */ void Graph::setWeighted(const bool &toggle) { m_graphIsWeighted = toggle; } /** * @brief Returns TRUE if the adjacency matrix of the current relation is symmetric * @return bool */ bool Graph::isSymmetric() { qCDebug(lcGraphCore) << "Graph::isSymmetric() "; if (calculatedGraphSymmetry) { qCDebug(lcGraphCore) << "Graph::isSymmetric() - graph not modified and " "already calculated symmetry. Returning previous result: " << m_graphIsSymmetric; return m_graphIsSymmetric; } m_graphIsSymmetric = true; int v2 = 0, v1 = 0; qreal weight = 0; QHash enabledOutEdges; QHash::const_iterator hit; VList::const_iterator lit; for (lit = m_graph.cbegin(); lit != m_graph.cend(); ++lit) { v1 = (*lit)->number(); if (!(*lit)->isEnabled()) continue; enabledOutEdges = (*lit)->outEdgesEnabledHash(); hit = enabledOutEdges.cbegin(); while (hit != enabledOutEdges.cend()) { v2 = hit.key(); weight = hit.value(); if (edgeExists(v2, v1) != weight) { m_graphIsSymmetric = false; break; } ++hit; } } // delete enabledOutEdges; qCDebug(lcGraphCore) << "Graph: isSymmetric() - Finished. Result:" << m_graphIsSymmetric; calculatedGraphSymmetry = true; return m_graphIsSymmetric; } /** * @brief Transforms the graph to symmetric (all edges reciprocal) */ void Graph::setSymmetric() { qCDebug(lcGraphCore) << "Tranforming graph to symmetric..."; VList::const_iterator it; int v2 = 0, v1 = 0, weight; qreal invertWeight = 0; QHash enabledOutEdges; QHash::const_iterator it1; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { v1 = (*it)->number(); enabledOutEdges = (*it)->outEdgesEnabledHash(); it1 = enabledOutEdges.cbegin(); while (it1 != enabledOutEdges.cend()) { v2 = it1.key(); weight = it1.value(); invertWeight = edgeExists(v2, v1); if (invertWeight == 0) { edgeCreate(v2, v1, weight, initEdgeColor, false, true, false, QString(), false); } else { if (weight != invertWeight) edgeWeightSet(v2, v1, weight); } ++it1; } } // delete enabledOutEdges; m_graphIsSymmetric = true; setModStatus(ModStatus::EdgeCount); } /** * @brief Toggles the graph directed or undirected * * @param toggle * @param signalMW */ void Graph::setDirected(const bool &toggle, const bool &signalMW) { qCDebug(lcGraphCore) << "Graph::setDirected - Setting graph directed to:" << toggle; if (!toggle) { setUndirected(true, signalMW); return; } if (toggle == isDirected()) { qCDebug(lcGraphCore) << "Graph::setDirected - Same as now, nothing to do."; return; } m_graphIsDirected = true; if (m_curRelation >= 0 && m_curRelation < m_relationsDirected.size()) m_relationsDirected[m_curRelation] = true; setModStatus(ModStatus::EdgeCount, signalMW); } /** * @brief Makes the graph undirected or directed. * * @param toggle * @param signalMW */ void Graph::setUndirected(const bool &toggle, const bool &signalMW) { qCDebug(lcGraphCore) << "Graph::setUndirected - Toggling graph undirected to" << toggle; qCDebug(lcGraphCore) << "Graph::setUndirected - m_graphIsSymmetric:" << m_graphIsSymmetric << "m_graphIsDirected:" << m_graphIsDirected << "m_totalEdges:" << m_totalEdges << "calculatedEdges:" << calculatedEdges; if (!toggle) { setDirected(true); return; } if (toggle == isUndirected()) { qCDebug(lcGraphCore) << "Graph::setUndirected - Same as now, nothing to do."; return; } // NOTE: We set m_graphIsDirected = false BEFORE the loop so that // edgeTypeSet() and other callers see the correct state immediately. m_graphIsDirected = false; if (m_curRelation >= 0 && m_curRelation < m_relationsDirected.size()) m_relationsDirected[m_curRelation] = false; // Only add reverse arcs if the graph was previously directed. // If the graph was already loaded with symmetric (undirected) arcs // (e.g. from a DOT 'graph' file), the reverse arcs already exist // and this loop would double them (see issue #187). if (!m_graphIsSymmetric) { VList::const_iterator it; int v2 = 0, v1 = 0; qreal weight; QHash enabledOutEdges; QHash::const_iterator it1; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { v1 = (*it)->number(); qCDebug(lcGraphCore) << "Graph::setUndirected - Iterating over edges of v1 " << v1; enabledOutEdges = (*it)->outEdgesEnabledHash(); it1 = enabledOutEdges.cbegin(); while (it1 != enabledOutEdges.cend()) { v2 = it1.key(); weight = it1.value(); qCDebug(lcGraphCore) << "edge" << "v1" << v1 << "->" << v2 << " = " << "weight" << weight; edgeTypeSet(v1, v2, weight, EdgeType::Undirected); ++it1; } } } else { qCDebug(lcGraphCore) << "Graph::setUndirected -Graph already has symmetric arcs (m_graphIsSymmetric=true); skipping reverse-arc addition."; } m_graphIsSymmetric = true; setModStatus(ModStatus::EdgeCount, signalMW); } /** * @brief Returns true if graph is directed * * @return bool */ bool Graph::isDirected() { qCDebug(lcGraphCore) << "Graph::isDirected m_graphIsDirected" << m_graphIsDirected; return m_graphIsDirected; } /** * @brief Returns true if graph is undirected * * @return bool */ bool Graph::isUndirected() { return !m_graphIsDirected; } SocNetV-3.7/src/graph/core/graph_structure_metrics.cpp000066400000000000000000000323431524402210300231750ustar00rootroot00000000000000/** * @file graph_structure_metrics.cpp * @brief Implements structural queries and basic metrics for the Graph class * (degrees, neighborhoods, density, reciprocity, dichotomization). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" /** * @brief Returns the outDegree (sum of outbound edge weights) of vertex v1 * * @param v1 * @return */ int Graph::vertexDegreeOut(int v1) { qCDebug(lcGraphCore) << "Returning outDegree of " << v1; return m_graph[vpos[v1]]->degreeOut(); } /** * @brief Returns the inDegree (sum of inbound edge weights) of vertex v1 * * @param v1 * @return */ int Graph::vertexDegreeIn(int v1) { qCDebug(lcGraphCore) << "Returning inDegree of " << v1; return m_graph[vpos[v1]]->degreeIn(); } /** * @brief Returns a list of all vertices reciprocally connected to vertex v1 * in the current relation. * * A vertex is included only if there is an enabled, reciprocal edge between * v1 and that vertex in the current relation. For general 1-hop neighbors * (out-edges only), iterate m_outEdges directly. * * @param v1 The vertex number to query. * @return QList of reciprocally connected vertex numbers. */ QList Graph::vertexReciprocalNeighborsList(const int &v1) { return m_graph[vpos[v1]]->reciprocalNeighborhoodList(); } /** * @brief Returns the set of all vertices reciprocally connected to vertex v1 * in the current relation. * * A vertex is included only if there is an enabled, reciprocal edge between * v1 and that vertex in the current relation. For general 1-hop neighbors * (out-edges only), iterate m_outEdges directly. * * @param v1 The vertex number to query. * @return QSet of reciprocally connected vertex numbers. */ QSet Graph::vertexReciprocalNeighborsSet(const int &v1) { const QList neighbors = m_graph[vpos[v1]]->reciprocalNeighborhoodList(); return QSet(neighbors.constBegin(), neighbors.constEnd()); } /** * @brief Returns the set of all 1-hop neighbors of vertex v1 in the current relation. * * By default returns only out-neighbors (vertices reachable via enabled out-edges). * When @p includeInEdges is true, also includes in-neighbors (vertices that have * an enabled out-edge to v1), giving the full set of adjacent nodes regardless * of edge direction. * * Only edges in the current relation that are enabled are considered. * * @param v1 The vertex number to query. * @param includeInEdges If true, union of out- and in-neighbors is returned. * Default: false (out-neighbors only). * @return QSet of neighbor vertex numbers. */ QSet Graph::vertexOutNeighborsSet(const int &v1, const bool includeInEdges) { QSet neighbors; const GraphVertex *v = m_graph[vpos[v1]]; // Out-edges: always included H_edges::const_iterator it = v->m_outEdges.constBegin(); while (it != v->m_outEdges.constEnd()) { if (it.value().first == m_curRelation && it.value().second.second == true) neighbors.insert(it.key()); ++it; } // In-edges: included only when requested if (includeInEdges) { it = v->m_inEdges.constBegin(); while (it != v->m_inEdges.constEnd()) { if (it.value().first == m_curRelation && it.value().second.second == true) neighbors.insert(it.key()); ++it; } } return neighbors; } /** * @brief Gets the graph density (if computed) or computes it again. * * The graph density is the ratio of present ties to total possible ties * for the current relation. * * IMPORTANT: edgesEnabled() semantics in SocNetV: * - If the graph is UNDIRECTED, edgesEnabled() returns E (undirected edges), * even though internally each undirected edge is stored as two symmetric arcs. * - If the graph is DIRECTED, edgesEnabled() returns A (directed arcs). * * Therefore: * - Undirected density: 2E / (V*(V-1)) * - Directed density: A / (V*(V-1)) * * TODO / THINK: Self-loops (v->v) * - The denominator V*(V-1) assumes loops are not allowed/considered. * - If self-loops can exist and be enabled, decide whether to: * (a) exclude loops from the numerator for density, or * (b) use a loop-aware denominator (e.g., V*V for directed with loops). * * @return qreal */ qreal Graph::graphDensity() { if (calculatedGraphDensity) { return m_graphDensity; } const int V = vertices(); if (V != 0 && V != 1) { const int enabledEdges = edgesEnabled(); // E (undirected) or A (directed) m_graphDensity = (isUndirected()) ? (qreal)2 * enabledEdges / (qreal)(V * (V - 1.0)) : (qreal)enabledEdges / (qreal)(V * (V - 1.0)); } else { m_graphDensity = 0; } calculatedGraphDensity = true; return m_graphDensity; } /** * @brief Returns the sum of vertices having edgesOutbound * @return */ int Graph::verticesWithOutboundEdges() { return outboundEdgesVert; } /** * @brief Returns the sum of vertices having edgesInbound * @return */ int Graph::verticesWithInboundEdges() { return inboundEdgesVert; } /** * @brief Returns the sum of vertices having reciprocal edges * @return */ int Graph::verticesWithReciprocalEdges() { return reciprocalEdgesVert; } /** * @brief Gets the arc reciprocity of the graph. * * Also computes the dyad reciprocity and fills parameters with values. * @return qreal */ qreal Graph::graphReciprocity() { qCDebug(lcGraphCore) << "Graph::graphReciprocity()"; if (calculatedGraphReciprocity) { qCDebug(lcGraphCore) << "Graph::graphReciprocity() - graph not modified and " "already calculated reciprocity. Returning previous result: " << m_graphReciprocityArc; return m_graphReciprocityArc; } qCDebug(lcGraphCore) << "Graph::graphReciprocity() - Computing..."; progressStatus((tr("Calculating the Arc Reciprocity of the graph..."))); m_graphReciprocityArc = 0; m_graphReciprocityDyad = 0; m_graphReciprocityTiesReciprocated = 0; m_graphReciprocityTiesNonSymmetric = 0; m_graphReciprocityTiesTotal = 0; m_graphReciprocityPairsReciprocated = 0; m_graphReciprocityPairsTotal = 0; qreal weight = 0, reciprocalWeight = 0; int y = 0, v2 = 0, v1 = 0; QHash enabledOutEdges; QHash::const_iterator hit; VList::const_iterator it; H_StrToBool totalDyads; H_StrToBool reciprocatedDyads; QString pair, reversePair; // initialize counters for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setOutEdgesReciprocated(0); (*it)->setOutEdgesNonSym(0); (*it)->setInEdgesNonSym(0); } // Compute "arc" reciprocity // the number of ties that are involved in reciprocal relations // relative to the total number of actual ties (not possible ties) for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { v1 = (*it)->number(); if (!(*it)->isEnabled()) continue; enabledOutEdges = (*it)->outEdgesEnabledHash(); hit = enabledOutEdges.cbegin(); while (hit != enabledOutEdges.cend()) { v2 = hit.key(); y = vpos[v2]; weight = hit.value(); // Fix #30: skip zero-weight edges β€” they are visual-only and must // not be counted as ties. Without this guard, weight==0 would also // make the edgeExists(v2,v1)==weight check below match absent // reverse edges (edgeExists returns 0 for missing edges), producing // false reciprocations and a 0/0 arc-reciprocity ratio. if (weight == 0) { ++hit; continue; } m_graphReciprocityTiesTotal += weight; // Compute "dyad" reciprocity pair = QString::number(v1) + ">" + QString::number(v2); reversePair = QString::number(v2) + ">" + QString::number(v1); if (!totalDyads.contains(pair) && !totalDyads.contains(reversePair)) { totalDyads[pair] = true; } qCDebug(lcGraphCore) << pair << "totalTies" << m_graphReciprocityTiesTotal << "totalDyads" << totalDyads.size(); if ((reciprocalWeight = edgeExists(v2, v1)) == weight) { (*it)->setOutEdgesReciprocated(); // increase reciprocated ties for ego (*it)->setOutEdgesReciprocated(); m_graphReciprocityTiesReciprocated += reciprocalWeight; pair = QString::number(v2) + ">" + QString::number(v1); reversePair = QString::number(v1) + ">" + QString::number(v2); if (!reciprocatedDyads.contains(pair) && !reciprocatedDyads.contains(reversePair)) { reciprocatedDyads[pair] = true; } qCDebug(lcGraphCore) << pair << "reciprocal!" << "reciprocatedTies" << m_graphReciprocityTiesReciprocated << "reciprocatedDyads" << reciprocatedDyads.size(); } else { (*it)->setOutEdgesNonSym(); m_graph[y]->setInEdgesNonSym(); m_graphReciprocityTiesNonSymmetric++; } ++hit; } } // delete enabledOutEdges; m_graphReciprocityArc = (qreal)m_graphReciprocityTiesReciprocated / (qreal)m_graphReciprocityTiesTotal; m_graphReciprocityPairsReciprocated = reciprocatedDyads.size(); m_graphReciprocityPairsTotal = totalDyads.size(); m_graphReciprocityDyad = (qreal)m_graphReciprocityPairsReciprocated / (qreal)m_graphReciprocityPairsTotal; qCDebug(lcGraphCore) << "Graph: graphReciprocity() - Finished. Arc reciprocity:" << m_graphReciprocityTiesReciprocated << "/" << m_graphReciprocityTiesTotal << "=" << m_graphReciprocityArc << "\n" << m_graphReciprocityPairsReciprocated << "/" << m_graphReciprocityPairsTotal << "=" << m_graphReciprocityDyad; calculatedGraphReciprocity = true; return m_graphReciprocityArc; } /** * @brief Creates a new binary relation in a valued network using edge * dichotomization according to the threshold parameter. * @param threshold */ void Graph::graphDichotomization(const qreal threshold) { qCDebug(lcGraphCore) << "Graph::graphDichotomization()" << "initial relations" << relations(); int v2 = 0, v1 = 0; qreal weight = 0; VList::const_iterator it; QHash outEdgesAll; QHash::const_iterator it1; QHash *binaryTies = new QHash; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { v1 = (*it)->number(); qCDebug(lcGraphCore) << "Graph::graphDichotomization() - v" << v1 << "iterate over outEdges in all relations"; outEdgesAll = (*it)->outEdgesEnabledHash(false); it1 = outEdgesAll.cbegin(); while (it1 != outEdgesAll.cend()) { v2 = it1.key(); weight = it1.value(); qCDebug(lcGraphCore) << v1 << "->" << v2 << "=" << weight << "Checking opposite."; if (weight > threshold) { if (!binaryTies->contains(QString::number(v1) + "--" + QString::number(v2))) { qCDebug(lcGraphCore) << v1 << "--" << v2 << " over threshold. Adding"; binaryTies->insert(QString::number(v1) + "--" + QString::number(v2), 1); } else { qCDebug(lcGraphCore) << v1 << "--" << v2 << " exists. Binary Tie already found. Continue"; } } ++it1; } } relationAdd("Binary-" + QString::number(threshold), true); QHash::const_iterator it2; it2 = binaryTies->constBegin(); QStringList vertices; qCDebug(lcGraphCore) << "creating binary tie edges"; while (it2 != binaryTies->constEnd()) { vertices = it2.key().split("--"); qCDebug(lcGraphCore) << "binary tie " << it2.key() << "vertices.at(0)" << vertices.at(0) << "vertices.at(1)" << vertices.at(1); v1 = (vertices.at(0)).toInt(); v2 = (vertices.at(1)).toInt(); qCDebug(lcGraphCore) << "calling edgeCreate for" << v1 << "--" << v2; edgeCreate(v1, v2, 1, initEdgeColor, EdgeType::Undirected, true, false, QString(), false); ++it2; } // delete outEdgesAll; delete binaryTies; m_graphIsSymmetric = true; setModStatus(ModStatus::EdgeCount); qCDebug(lcGraphCore) << "final relations" << relations(); } SocNetV-3.7/src/graph/crawler/000077500000000000000000000000001524402210300162245ustar00rootroot00000000000000SocNetV-3.7/src/graph/crawler/graph_crawler.cpp000066400000000000000000000164401524402210300215550ustar00rootroot00000000000000/** * @file graph_crawler.cpp * @brief Implements web crawling and network construction logic for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include #include #include #include /** * @brief called from Graph, when closing network, to terminate all crawler processes * Also called indirectly when wc_spider finishes * @param reason */ void Graph::webCrawlTerminateThreads(QString reason) { qCDebug(lcGraphCrawler) << "Terminating webCrawler threads - reason " << reason << "Checking webcrawlerThread..."; while (webcrawlerThread.isRunning()) { qCDebug(lcGraphCrawler) << "webcrawlerThread running. " "Calling webcrawlerThread.quit()"; webcrawlerThread.requestInterruption(); webcrawlerThread.quit(); webcrawlerThread.wait(); } } /** * @brief * Creates a new WebCrawler, that will parse the downloaded HTML code of each webpage * we download. Moves the WebCrawler to a new thread and starts the thread. * Then creates the fist node (initial url), * and starts the web spider to download the first HTML page. * Called by MW with user options. * @param startUrl * @param urlPatternsIncluded * @param urlPatternsExcluded * @param linkClasses * @param maxNodes * @param maxLinksPerPage * @param intLinks * @param childLinks * @param parentLinks * @param selfLinks * @param extLinksIncluded * @param extLinksCrawl * @param socialLinks * @param delayedRequests */ void Graph::startWebCrawler( const QUrl &startUrl, const QStringList &urlPatternsIncluded, const QStringList &urlPatternsExcluded, const QStringList &linkClasses, const int &maxNodes, const int &maxLinksPerPage, const bool &intLinks, const bool &childLinks, const bool &parentLinks, const bool &selfLinks, const bool &extLinksIncluded, const bool &extLinksCrawl, const bool &socialLinks, const bool &delayedRequests) { qCDebug(lcGraphCrawler) << "Setting up a new WebCrawler for url:" << startUrl.toString() << "graph thread:" << thread(); // Rename current relation relationCurrentRename(tr("web"), true); // Initialize variables m_crawler_max_urls = maxNodes; // Store maximum urls we'll visit (max nodes in the resulted network) m_crawler_visited_urls = 0; // Init counter of visited urls // Check if we need to add delay between requests int delayBetween = 0; if (delayedRequests) { delayBetween = 500; // half second } // Create our url queue urlQueue = new QQueue; // Enqueue the start QUrl urlQueue->enqueue(startUrl); qCDebug(lcGraphCrawler) << "Creating new WebCrawler..."; // Create the WebCrawler web_crawler = new WebCrawler( urlQueue, startUrl, urlPatternsIncluded, urlPatternsExcluded, linkClasses, maxNodes, maxLinksPerPage, intLinks, childLinks, parentLinks, selfLinks, extLinksIncluded, extLinksCrawl, socialLinks, delayBetween); // Just in case, we reach this place and the thread is still running if (webcrawlerThread.isRunning()) { qCDebug(lcGraphCrawler) << "webcrawlerThread is already running - calling requestInterruption()..."; webCrawlTerminateThreads("startWebCrawler() to start a new WebCrawler but webcrawlerThread is running..."); } // Move the crawler to another thread web_crawler->moveToThread(&webcrawlerThread); qCDebug(lcGraphCrawler) << "WebCrawler created and moved to its own thread:" << web_crawler->thread(); // Connect signals and slots qCDebug(lcGraphCrawler) << "Connect signals/slots with WebCrawler..."; connect(this, &Graph::signalWebCrawlParse, web_crawler, &WebCrawler::parse); connect(web_crawler, &WebCrawler::signalStartSpider, this, &Graph::webSpider); connect(web_crawler, &WebCrawler::signalCreateNode, this, &Graph::vertexCreateAtPosRandomWithLabel); connect(web_crawler, &WebCrawler::signalCreateEdge, this, &Graph::edgeCreateWebCrawler); connect(web_crawler, &WebCrawler::finished, this, &Graph::webCrawlTerminateThreads); connect(&webcrawlerThread, &QThread::finished, web_crawler, &QObject::deleteLater); // Start the crawler thread... qCDebug(lcGraphCrawler) << "Starting WebCrawler thread..."; webcrawlerThread.start(); // Create the initial vertex for the starting url qCDebug(lcGraphCrawler) << "Creating initial node 1, initialUrlStr:" << startUrl.toString(); vertexCreateAtPosRandomWithLabel(1, startUrl.toString(), false); // Call the spider to download the html code of the starting url . qCDebug(lcGraphCrawler) << "Calling webSpider()..."; this->webSpider(); qCDebug(lcGraphCrawler, "web crawler and spider started. See the thread running? "); } /** * @brief * A loop, that takes urls awaiting in front of the urlQueue, * and signals to the MW to make the network request */ void Graph::webSpider() { // repeat while urlQueue has items do { // Until we crawl all urls in urlQueue. if (urlQueue->size() == 0) { qCDebug(lcGraphCrawler) << "webSpider - urlQueue is empty. Break for now... "; break; } // or until we have reached m_maxNodes if (m_crawler_max_urls > 0 && m_crawler_visited_urls == m_crawler_max_urls) { qCDebug(lcGraphCrawler) << "webSpider - reached m_crawler_max_urls. Break."; break; } // Take the first url awaiting in the queue qCDebug(lcGraphCrawler) << "webSpider - urlQueue size: " << urlQueue->size() << " - Taking the first url from the urlQueue "; QUrl currentUrl = urlQueue->dequeue(); qCDebug(lcGraphCrawler) << "webSpider - url to download: " << currentUrl << "Increasing m_crawler_visited_urls to:" << m_crawler_visited_urls + 1 << "and emitting signal signalNetworkManagerRequest to MW..."; // Signal MW to make the network request emit signalNetworkManagerRequest(currentUrl, NetworkRequestType::Crawler); // increase visited urls counter m_crawler_visited_urls++; } while (urlQueue->size()); } /** * @brief * Gets the reply of a MW network request made by Web Crawler, * and emits that reply as is to the Web Crawler. */ void Graph::slotHandleCrawlerRequestReply() { qCDebug(lcGraphCrawler) << "Got reply from MW network manager request. Emitting signal to Web Crawler to parse the reply..."; // Get network reply from the sender QNetworkReply *reply = qobject_cast(sender()); // Emit signal to web crawler to parse the reply emit signalWebCrawlParse(reply); } SocNetV-3.7/src/graph/distances/000077500000000000000000000000001524402210300165425ustar00rootroot00000000000000SocNetV-3.7/src/graph/distances/graph_distance_cache.cpp000066400000000000000000000174741524402210300233610ustar00rootroot00000000000000/** * @file graph_distance_cache.cpp * @brief Implements distance matrix creation, geodesic aggregation, SSSP helpers, and caching logic for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include "engine/distance_engine.h" #include // // Distance matrices / wrapper hub // /** * @brief Computes the geodesic distances between all vertices: * In the process, it also computes many other centrality/prestige metrics: * * The so-called sigma matrix, where the (i,j) element is the number of shortest paths * from vertex i to vertex j, called sigma(i,j). * * The Diameter of the graph, m_graphDiameter, which is the length of the longest * shortest path between every (i,j) * * The Eccentricity of every node i which is the length of the longest shortest * path from i to every other node j * * The InfluenceRange and InfluenceDomain of each node. * * The centralities for every u in V (if centralities=true): * - Betweenness: BC(u) = Sum ( sigma(i,j,u)/sigma(i,j) ) for every s,t in V * Meaning: how often u sits "in between" on the shortest routes connecting other pairs of * actors - a broker/gatekeeper measure. High betweenness means removing u would disrupt * many people's shortest path to each other. * When to use: finding brokers, bottlenecks, or single points of failure in a * communication/supply network - who, if removed, would fragment the network's shortest * routes the most. * Weights: shortest-path-based - if a weight represents value/strength (interaction * frequency, trust), invert it (a strong tie should behave like a short/cheap path) so * routing favors the strongest ties, not the weakest. * Compare to: Stress (SC) below counts the same "sits on a shortest path" event without * dividing by how many alternative shortest paths existed - use BC when you want "share of * control" over each pair's routing, SC when you want raw path traffic. * - Stress: SC(u) = Sum ( sigma(i,j) ) for every s,t in V * Meaning: like betweenness, but simply counts how many shortest paths pass through u, * without dividing by how many alternative shortest paths existed for that pair - so it * also rewards actors on many paths even when those paths weren't a pair's *only* shortest * route. * When to use: estimating raw path/traffic load through a node (e.g. network routing, * load-bearing infrastructure) rather than its exclusive control over routing. * Weights: same shortest-path reasoning as BC above - invert a strength-type weight so * strong ties route like short paths. * - Eccentricity: EC(u) = 1/maxDistance(u,t) for some t in V * Meaning: a worst-case reachability measure - how far away is u's single most distant * counterpart? High eccentricity centrality means even u's "hardest to reach" other actor * is nearby. * When to use: worst-case reasoning - e.g. picking a broadcast/facility location that * minimizes the longest anyone has to wait to be reached, rather than the average case. * Weights: same shortest-path reasoning as BC/SC above - invert a strength-type weight. * Compare to: Closeness (CC) below is this same distance-based idea using the *average* * distance instead of the worst case. * - Closeness: CC(u) = 1 / Sum( d(u,t) ) for every t in V * Meaning: how close u is, on average, to everyone else - a low total distance to others * gives a high closeness score. Only meaningful on a fully connected graph, since an * unreachable actor has undefined distance. * When to use: identifying actors who can spread something (information, disease, an * influence campaign) to the whole network fastest, on a graph known to be connected. * Weights: same shortest-path reasoning as BC/SC/EC above - invert a strength-type weight * so a strong tie behaves like a short/cheap path. * Compare to: Influence Range Closeness Centrality (IRCC, see centralityClosenessIR()) * is this same idea adapted to work on disconnected graphs too. * - Power (Gil-Schmidt): PC(s) = [1/(N-1)] * Sum_i( nthOrder[i] / i ), where nthOrder[i] is * the number of nodes at distance i from s (computed in DistanceEngine::compute()). * Meaning: a generalized degree measure that gives (shrinking) credit for nodes several * steps away too, not just direct neighbors - similar in spirit to eigenvector centrality, * but computed directly from how many nodes sit at each distance rather than via * eigen-decomposition. * When to use: a cheaper, degree-based alternative to Eigenvector Centrality (EVC) for * rewarding both direct and indirect reach, when a full eigen-decomposition isn't needed. * Weights: same shortest-path reasoning as BC/SC/EC/CC above (nthOrder is computed from * DistanceEngine's shortest-path distances) - invert a strength-type weight. * Compare to: Eigenvector Centrality (EVC, see centralityEigenvector()) captures a related * "reach plus indirect reach" idea via eigen-decomposition instead. Not to be confused with * Bonacich's differently-named, unrelated "Power Centrality" measure (BPC, see * centralityBonacich()). * @param centralities * @param considerWeights * @param inverseWeights * @param dropIsolates */ void Graph::graphDistancesGeodesic(const bool &computeCentralities, const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates) { DistanceEngine engine(*this); engine.compute(computeCentralities, considerWeights, inverseWeights, dropIsolates); } // // DISCONNECTED PAIRS CACHE // During SSSP, we may find pairs of vertices that are not connected. // We store these in a hash for quick lookup, so that if we encounter the same pair again, // we can immediately return "not connected" without recalculating. // void Graph::notConnectedPairsClear() { m_vertexPairsNotConnected.clear(); } void Graph::notConnectedPairsInsert(int from, int to) { m_vertexPairsNotConnected.insert(from, to); } int Graph::notConnectedPairsSize() const { return m_vertexPairsNotConnected.size(); } // // DISTANCE CENTRALITY CACHE FLAGS // void Graph::resetDistanceCentralityCacheFlags() { calculatedDistances = false; calculatedCentralities = false; m_graphWeaklyConnectedComponents = 0; m_graphStronglyConnectedComponents = 0; m_vertexComponentId.clear(); } void Graph::setSymmetricCached(bool v) { m_graphIsSymmetric = v; } bool Graph::symmetricCached() const { return m_graphIsSymmetric; } void Graph::setConnectedCached(bool v) { m_graphIsConnected = v; } int Graph::graphWeaklyConnectedComponentsCached() const { return m_graphWeaklyConnectedComponents; } int Graph::graphStronglyConnectedComponentsCached() const { return m_graphStronglyConnectedComponents; } void Graph::setDiameterCached(int v) { m_graphDiameter = v; } void Graph::resetDistanceAggregates() { m_graphDiameter = 0; m_graphAverageDistance = 0; m_graphSumDistance = 0; m_graphGeodesicsCount = 0; } void Graph::addToDistanceSum(qreal delta) { m_graphSumDistance += delta; } void Graph::incGeodesicsCount() { ++m_graphGeodesicsCount; } void Graph::addGeodesicsCount(int n) { m_graphGeodesicsCount += n; } void Graph::setAverageDistanceCached(qreal v) { m_graphAverageDistance = v; } SocNetV-3.7/src/graph/distances/graph_distance_facade.cpp000066400000000000000000000714061524402210300235140ustar00rootroot00000000000000/** * @file graph_distance_facade.cpp * @brief Implements faΓ§ade-level distance and connectivity accessors of the Graph class, delegating computations to DistanceEngine and exposing cached aggregates. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include #include #include #include #include #include #include // std::min #include // RAND_MAX // PUBLIC DISTANCE API FACADE /** * @brief Returns the geodesic distance (length of shortest path) * from vertex v1 to vertex v2 * @param v1 * @param v2 * @param considerWeights * @param inverseWeights * @return */ int Graph::graphDistanceGeodesic(const int &v1, const int &v2, const bool &considerWeights, const bool &inverseWeights) { qCDebug(lcDistances) << "Graph::graphDistanceGeodesic()"; graphDistancesGeodesic(false, considerWeights, inverseWeights, false); return apspDistance(v1, v2); } /** * @brief Returns the already-computed geodesic distance from vertex v1 to vertex v2, for the * current relation, without triggering a recompute (unlike graphDistanceGeodesic() above). * RAND_MAX if either vertex is unknown or nothing has been computed yet for this relation. */ qreal Graph::apspDistance(const int &v1, const int &v2) { const int i = vertexIndexByNumber(v1); const int j = vertexIndexByNumber(v2); if (i < 0 || j < 0) return RAND_MAX; auto it = m_apspDist.find(relationCurrent()); if (it == m_apspDist.end()) return RAND_MAX; return it->item(i, j); } /** * @brief Returns the already-computed number of shortest paths from vertex v1 to vertex v2, * for the current relation, without triggering a recompute. 0 if either vertex is unknown or * nothing has been computed yet for this relation. */ int Graph::apspShortestPaths(const int &v1, const int &v2) { const int i = vertexIndexByNumber(v1); const int j = vertexIndexByNumber(v2); if (i < 0 || j < 0) return 0; auto it = m_apspSigma.find(relationCurrent()); if (it == m_apspSigma.end()) return 0; return static_cast(it->item(i, j)); } /** * @brief Returns a histogram of geodesic distances across all ordered vertex pairs. * * Ensures the full APSP result is available (uses cache if graph is unchanged), * then iterates all enabled vertex pairs and groups them by their geodesic distance * into a QMap where key = distance and value = number of ordered pairs * at that distance. Unreachable pairs (distance == RAND_MAX) are excluded. * * This method is called ONLY on explicit user request β€” it is never triggered as a * side-effect of centrality computation or other internal analysis paths. * * @param considerWeights Pass through to graphDistancesGeodesic; selects BFS vs Dijkstra. * @param inverseWeights Pass through to graphDistancesGeodesic; inverts edge weights. * @return QMap Sorted map: distance bucket β†’ count of ordered pairs. */ QMap Graph::graphGeodesicDistanceDistribution(const bool &considerWeights, const bool &inverseWeights) { qCDebug(lcDistances) << "Graph::graphGeodesicDistanceDistribution()"; // Ensure the APSP result is available. graphDistancesGeodesic() is a no-op // when calculatedDistances is true (i.e. graph structure is unchanged). graphDistancesGeodesic(false, considerWeights, inverseWeights, false); QMap distribution; VList::const_iterator it1, it2; for (it1 = m_graph.cbegin(); it1 != m_graph.cend(); ++it1) { if (!(*it1)->isEnabled()) continue; const int src = (*it1)->number(); for (it2 = m_graph.cbegin(); it2 != m_graph.cend(); ++it2) { if (!(*it2)->isEnabled()) continue; const int tgt = (*it2)->number(); if (src == tgt) continue; // skip self-pairs const qreal d = apspDistance(src, tgt); // Exclude unreachable pairs (RAND_MAX is the sentinel used by DistanceEngine). if (d <= 0 || d >= static_cast(RAND_MAX)) continue; // Round to nearest integer: distances from BFS are exact integers stored // as qreal; Dijkstra distances are real-valued but are still grouped by // integer bucket for the histogram. distribution[qRound(d)]++; } } qCDebug(lcDistances) << "Graph::graphGeodesicDistanceDistribution() - distribution:" << distribution; return distribution; } /** * @brief Writes a geodesic distance distribution report to an HTML file. * * Computes the distribution via graphGeodesicDistanceDistribution() (cache-aware), * then writes a sortable HTML table (distance | pair count | % | cumulative %) * to @p fileName. * * @param fileName Full path of the output HTML file. * @param considerWeights Passed through to graphGeodesicDistanceDistribution(). * @param inverseWeights Passed through to graphGeodesicDistanceDistribution(). * @return true on success; false if the file could not be opened. */ bool Graph::writeGeodesicDistribution(const QString &fileName, const bool &considerWeights, const bool &inverseWeights) { qCDebug(lcDistances) << "Graph::writeGeodesicDistribution() ->" << fileName; QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcDistances) << "Graph::writeGeodesicDistribution() - cannot open file"; return false; } QTextStream out(&file); const QMap dist = graphGeodesicDistanceDistribution(considerWeights, inverseWeights); // Compute the total number of connected ordered pairs for percentage columns. int totalPairs = 0; for (int cnt : dist) totalPairs += cnt; out << htmlHead; out << "

" << tr("GEODESIC DISTANCE DISTRIBUTION") << "

"; out << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << vertices() << "

"; if (dist.isEmpty()) { out << "

" << tr("No connected pairs found.") << "

"; out << htmlEnd; file.close(); return true; } out << "

" << tr("Number of ordered pairs of connected nodes separated by each " "geodesic distance d. " "Unreachable pairs are excluded. " "Diameter of this network: %1.") .arg(graphDiameterCached()) << "

"; out << "
" << "" << "" << "" << "" << "" << ""; int cumulative = 0; for (auto it = dist.cbegin(); it != dist.cend(); ++it) { const int d = it.key(); const int count = it.value(); cumulative += count; const double pct = 100.0 * count / totalPairs; const double cpct = 100.0 * cumulative / totalPairs; out << "" << "" << "" << QString("").arg(pct, 0, 'f', 2) << QString("").arg(cpct, 0, 'f', 2) << ""; } out << "
" << tr("Distance (d)") << "" << tr("Pairs") << "" << tr("% of connected pairs") << "" << tr("Cumulative %") << "
" << d << "" << count << "%1%%1%
"; out << htmlEnd; file.close(); return true; } /** * @brief Reconstructs one shortest path from vertex v1 to vertex v2. * * Runs a single-source BFS (unweighted) or Dijkstra (weighted) from v1, * keeping a predecessor array, then traces the path back from v2. * This is an on-demand, interactive call β€” it does NOT touch the APSP cache * and is safe to call at any time regardless of calculatedDistances. * * @param v1 Source vertex number. * @param v2 Target vertex number. * @param considerWeights If true, uses Dijkstra with edge weights; otherwise BFS. * @param inverseWeights If true, uses 1/weight as the edge cost (for closeness-style paths). * @return QList Ordered list of vertex numbers on the path, inclusive of v1 and v2. * Returns an empty list when v1 == v2 or no path exists. */ QList Graph::graphGeodesicShortestPath(const int &v1, const int &v2, const bool &considerWeights, const bool &inverseWeights) { qCDebug(lcDistances) << "Graph::graphGeodesicShortestPath()" << v1 << "->" << v2; if (v1 == v2 || !vpos.contains(v1) || !vpos.contains(v2)) return QList(); const int N = m_graph.size(); const int currentRelation = relationCurrent(); // pred[i] = vertex NUMBER of the predecessor of m_graph[i] on the shortest // path from v1. Initialised to -1 (no predecessor known yet). QVector pred(N, -1); // dist[i] = best distance from v1 to m_graph[i] found so far. // RAND_MAX is the sentinel meaning "not yet reached". QVector dist(N, static_cast(RAND_MAX)); const int srcIdx = vpos[v1]; dist[srcIdx] = 0; if (!considerWeights) { // ----------------------------------------------------------------------- // Unweighted BFS. Each edge has cost 1, so the first time a vertex is // reached it is via the shortest path β€” no relaxation needed. // Time: O(V + E). // ----------------------------------------------------------------------- QQueue queue; // stores vertex positions (indices into m_graph) queue.enqueue(srcIdx); while (!queue.isEmpty()) { const int u = queue.dequeue(); // Iterate over all out-edges of vertex u in the current relation. for (auto eit = m_graph[u]->outEdges().cbegin(); eit != m_graph[u]->outEdges().cend(); ++eit) { if (eit.value().first != currentRelation) continue; // wrong relation if (!eit.value().second.second) continue; // edge disabled const int wNum = eit.key(); // target vertex number if (!vpos.contains(wNum)) continue; const int w = vpos[wNum]; // In BFS, the first visit is always via the shortest path. if (dist[w] == static_cast(RAND_MAX)) { dist[w] = dist[u] + 1; pred[w] = m_graph[u]->number(); // record predecessor queue.enqueue(w); } } } } else { // ----------------------------------------------------------------------- // Weighted Dijkstra. Edge weights are used as costs (or their inverses // when inverseWeights is true). Uses a QMap> as a // simple priority queue ordered by distance. // Time: O((V + E) log V) due to QMap insertion/lookup. // ----------------------------------------------------------------------- QMap> pq; // distance β†’ list of vertex positions at that distance pq[0.0].append(srcIdx); while (!pq.isEmpty()) { // Extract the entry with the smallest distance. auto it = pq.begin(); const qreal d = it.key(); const int u = it.value().takeFirst(); if (it.value().isEmpty()) pq.erase(it); // Skip stale entries (a shorter path to u was already settled). if (d > dist[u]) continue; // Relax all out-edges of u. for (auto eit = m_graph[u]->outEdges().cbegin(); eit != m_graph[u]->outEdges().cend(); ++eit) { if (eit.value().first != currentRelation) continue; if (!eit.value().second.second) continue; const int wNum = eit.key(); if (!vpos.contains(wNum)) continue; const int w = vpos[wNum]; qreal edgeCost = eit.value().second.first; // raw edge weight if (inverseWeights && edgeCost != 0.0) edgeCost = 1.0 / edgeCost; // invert for closeness-style cost const qreal newDist = dist[u] + edgeCost; if (newDist < dist[w]) { dist[w] = newDist; pred[w] = m_graph[u]->number(); // record predecessor pq[newDist].append(w); // enqueue with new distance } } } } // ----------------------------------------------------------------------- // Path reconstruction: walk the predecessor chain from v2 back to v1. // ----------------------------------------------------------------------- const int tgtIdx = vpos[v2]; if (dist[tgtIdx] == static_cast(RAND_MAX)) return QList(); // v2 is unreachable from v1 QList path; int cur = v2; while (cur != v1) { path.prepend(cur); const int curPred = pred[vpos[cur]]; if (curPred == -1) return QList(); // corrupted predecessor β€” should not happen cur = curPred; } path.prepend(v1); // prepend source last so path reads v1 β†’ … β†’ v2 qCDebug(lcDistances) << "Graph::graphGeodesicShortestPath() - path:" << path; return path; } /** * @brief Returns the diameter of the graph, aka the largest geodesic distance * between any two vertices * @param considerWeights * @param inverseWeights * @return */ int Graph::graphDiameter(const bool considerWeights, const bool inverseWeights) { qCDebug(lcDistances) << "Graph::graphDiameter()"; graphDistancesGeodesic(false, considerWeights, inverseWeights, false); return m_graphDiameter; } /** * @brief Returns the average distance of the graph * @param considerWeights * @param inverseWeights * @param dropIsolates * @return */ qreal Graph::graphDistanceGeodesicAverage(const bool considerWeights, const bool inverseWeights, const bool dropIsolates) { qCDebug(lcDistances) << "Graph::graphDistanceGeodesicAverage() - Computing distances..."; graphDistancesGeodesic(false, considerWeights, inverseWeights, dropIsolates); qCDebug(lcDistances) << "Graph::graphDistanceGeodesicAverage() - " << "average distance:" << m_graphAverageDistance; return m_graphAverageDistance; } /** * @brief Returns the number of geodesics (shortest-paths) in the graph. * * @return int */ int Graph::getGeodesicsCount() { qCDebug(lcDistances) << "Graph::getGeodesicsCount()"; graphDistancesGeodesic(false, false, false, false); qCDebug(lcDistances) << "Graph::getGeodesicsCount() - geodesics:" << m_graphGeodesicsCount; return m_graphGeodesicsCount; } /** * @brief Checks if the graph is connected, in the sense of a topological space, * i.e., there is a path from any vertex to any other vertex in the graph. * @return bool */ bool Graph::isConnected() { qCDebug(lcDistances) << "Graph::isConnected() "; if (calculatedDistances) { qCDebug(lcDistances) << "Graph::isConnected() - graph unmodified. Returning:" << m_graphIsConnected; return m_graphIsConnected; } graphDistancesGeodesic(false, false, false, false); return m_graphIsConnected; } // DISTANCE CACHE GETTERS - these return cached values without recalculating anything. // They are used by the UI and reporting engines to get distance metrics without triggering recalculations. /** * @brief Returns the average geodesic distance of the graph, without recalculating it. * @return qreal */ qreal Graph::graphDistanceGeodesicAverageCached() const { return m_graphAverageDistance; } /** * @brief Returns the number of geodesics (shortest paths) in the graph, without recalculating it. * @return int */ int Graph::graphDiameterCached() const { return m_graphDiameter; } /** * @brief Returns true if the graph is connected, without recalculating it. * @return bool */ bool Graph::isConnectedCached() const { return m_graphIsConnected; } /** * @brief Counts weakly connected components using BFS. * * Weak connectivity treats every edge as undirected regardless of graph type: * - Undirected: equivalent to ordinary connected components. * - Directed: two nodes are in the same component when there is an undirected * path between them (ignoring arrow direction). Answers the practical * "how many disconnected islands?" question consistently for both types. * Does NOT imply strong connectivity (all-pairs directed reachability); * use isConnected() / graphDistancesGeodesic() for that. * * m_graphIsConnected is NOT touched here; that flag belongs to the SSSP engine. * Component IDs (1-based) are cached in m_vertexComponentId keyed by vertex * number, ready for the colorize-by-component layout action. * * @return Number of weakly connected components. */ int Graph::graphWeaklyConnectedComponents() { if (m_graphWeaklyConnectedComponents > 0) { qCDebug(lcDistances) << "Graph::graphWeaklyConnectedComponents() - cached:" << m_graphWeaklyConnectedComponents; return m_graphWeaklyConnectedComponents; } qCDebug(lcDistances) << "Graph::graphWeaklyConnectedComponents() - computing"; const int currentRelation = relationCurrent(); QHash visited; int componentId = 0; for (auto it = verticesBegin(); it != verticesEnd(); ++it) { const int v = (*it)->number(); if (!(*it)->isEnabled() || visited.contains(v)) continue; ++componentId; QQueue queue; queue.enqueue(v); visited[v] = true; while (!queue.isEmpty()) { const int u = queue.dequeue(); const int ui = vertexIndexByNumber(u); if (ui < 0) continue; const GraphVertex *uv = vertexAtIndex(ui); // Follow out-edges for (auto eit = uv->outEdges().cbegin(); eit != uv->outEdges().cend(); ++eit) { if (eit.value().first != currentRelation) continue; if (!eit.value().second.second) continue; const int w = eit.key(); if (!visited.contains(w)) { visited[w] = true; queue.enqueue(w); } } // Follow in-edges (makes traversal undirected β†’ weak connectivity) for (auto eit = uv->inEdges().cbegin(); eit != uv->inEdges().cend(); ++eit) { if (eit.value().first != currentRelation) continue; if (!eit.value().second.second) continue; const int w = eit.key(); if (!visited.contains(w)) { visited[w] = true; queue.enqueue(w); } } m_vertexComponentId[u] = componentId; } } m_graphWeaklyConnectedComponents = componentId; qCDebug(lcDistances) << "Graph::graphWeaklyConnectedComponents() -" << componentId << "component(s)"; return m_graphWeaklyConnectedComponents; } /** * @brief Counts strongly connected components using Tarjan's algorithm. * * Strong connectivity respects edge direction: two nodes are in the same Strongly Connected * Component (SCC) only if each is reachable from the other via directed edges. This is a strictly * finer partition than weak connectivity (graphWeaklyConnectedComponents(), which treats every * edge as undirected) - a graph can be a single weak component while having many strong * components, e.g. a directed path a->b->c is one weak component but three strong components, * since c cannot reach a. For an undirected graph the two notions coincide (every edge is * effectively reciprocal), so this still returns the right answer, just via a slightly more * roundabout route than graphWeaklyConnectedComponents(). * * Algorithm (Tarjan 1972): a single Depth First Search (DFS) assigns each vertex a discovery "index" * (the order it was first visited) and a "lowlink" - the smallest index reachable from that vertex * by following zero or more tree edges and then at most one edge back into an ancestor still on the * DFS stack. Vertices are pushed onto an explicit stack as they're discovered and popped once a SCC * is found. A vertex is the *root* of an SCC exactly when its lowlink equals its own index - meaning * nothing below it on the DFS stack can reach back above it - and popping the stack down to and * including that root yields exactly the members of one SCC. This is O(V+E), a single DFS pass, * with no graph transpose needed - unlike Kosaraju's algorithm, which gets the same complexity but * needs two passes over a transposed graph. Tarjan's is the better fit here since SocNetV already * has cheap out-edge iteration and no existing transpose-graph structure to reuse. * * Implemented as an explicit-stack simulation of the textbook recursive DFS, not recursion itself: * SocNetV networks can have a directed path of a few thousand nodes (a plausible worst case for a * citation or hierarchy network), which would recurse that deep and risk overflowing the real call * stack. Each simulated stack frame remembers which out-edge it was in the middle of examining * (Frame::cursor), which is exactly the resumption point a real call stack would give for free * after a simulated recursive call "returns" (its frame is popped). * * m_vertexComponentId (weak-component IDs, used by the colorize-by-component layout action) is * NOT touched here; strong components are not currently exposed per-vertex, only as a count. * * @return Number of strongly connected components. */ int Graph::graphStronglyConnectedComponents() { if (m_graphStronglyConnectedComponents > 0) { qCDebug(lcDistances) << "Graph::graphStronglyConnectedComponents() - cached:" << m_graphStronglyConnectedComponents; return m_graphStronglyConnectedComponents; } qCDebug(lcDistances) << "Graph::graphStronglyConnectedComponents() - computing"; const int currentRelation = relationCurrent(); struct Frame { int v = 0; // vertex this frame represents (a simulated "call" to visit v) QList neighbors; // v's out-neighbors, computed once when the frame is pushed int cursor = 0; // index into neighbors examined so far - the explicit-stack // stand-in for the resumption point a real call stack would // remember automatically when a nested recursive call returns }; auto outNeighborsOf = [this, currentRelation](int v) { QList neighbors; const int vi = vertexIndexByNumber(v); const GraphVertex *gv = vertexAtIndex(vi); for (auto eit = gv->outEdges().cbegin(); eit != gv->outEdges().cend(); ++eit) { if (eit.value().first != currentRelation) continue; if (!eit.value().second.second) continue; neighbors.append(eit.key()); } return neighbors; }; // Two distinct "stacks" are in play here, easy to conflate: // - callStack (declared per-root below): the explicit simulation of the recursive DFS call // stack itself. One Frame per vertex currently in progress along the current root-to-here // DFS path; pushing a frame simulates a recursive call, popping one simulates that call // returning. Only ever holds vertices on the *current* DFS path from this loop's root. // - tarjanStack: Tarjan's own algorithm-level bookkeeping stack (unrelated to the call // stack), holding every vertex visited so far that hasn't yet been assigned to a finished // SCC. A vertex is pushed here once, on first discovery, and popped only when the SCC it // belongs to is finalized (see the SCC-root branch below) - it can outlive several // callStack frames being pushed and popped above it. QHash index, lowlink; QHash onStack; QList tarjanStack; int nextIndex = 0; int componentCount = 0; for (auto it = verticesBegin(); it != verticesEnd(); ++it) { const int start = (*it)->number(); if (!(*it)->isEnabled() || index.contains(start)) continue; QList callStack; { Frame f; f.v = start; f.neighbors = outNeighborsOf(start); index[start] = nextIndex; lowlink[start] = nextIndex; ++nextIndex; tarjanStack.append(start); onStack[start] = true; callStack.append(f); } // Standard iterative-DFS-via-explicit-stack shape: look at the frame on top of // callStack. If it still has an unexamined out-neighbor, examine the next one - this is // the body of the recursive version's "for each neighbor w of v" loop - and either // descend (push a new frame, simulating a recursive call on an undiscovered w) or update // this frame's lowlink (w was already discovered and is still on tarjanStack, i.e. a // back/cross edge). Once every neighbor of the top frame has been examined, "return": // pop it and propagate its finished lowlink up into its caller's, exactly as a real // return value would. while (!callStack.isEmpty()) { const int top = callStack.size() - 1; if (callStack[top].cursor < callStack[top].neighbors.size()) { const int w = callStack[top].neighbors[callStack[top].cursor]; ++callStack[top].cursor; if (!index.contains(w)) { // Tree edge: simulate a recursive call by pushing a new frame. Note we don't // touch callStack[top] again after this append - QList::append() may // reallocate the backing array, which would invalidate any reference taken // before the call, so every access below is by fresh index, never by // reference held across a mutation. Frame nf; nf.v = w; nf.neighbors = outNeighborsOf(w); index[w] = nextIndex; lowlink[w] = nextIndex; ++nextIndex; tarjanStack.append(w); onStack[w] = true; callStack.append(nf); } else if (onStack.value(w, false)) { // Back/cross edge into a vertex still on the stack: it's part of the same // SCC-in-progress, so it can lower this vertex's lowlink. lowlink[callStack[top].v] = std::min(lowlink[callStack[top].v], index[w]); } } else { // All of this vertex's out-edges are examined - "return" from the simulated call. const int v = callStack[top].v; if (lowlink[v] == index[v]) { // v is an SCC root: everything above it on the Tarjan stack, plus v itself, // is exactly one strongly connected component. ++componentCount; int w; do { w = tarjanStack.takeLast(); onStack[w] = false; } while (w != v); } callStack.removeLast(); if (!callStack.isEmpty()) { const int parentTop = callStack.size() - 1; lowlink[callStack[parentTop].v] = std::min(lowlink[callStack[parentTop].v], lowlink[v]); } } } } m_graphStronglyConnectedComponents = componentCount; qCDebug(lcDistances) << "Graph::graphStronglyConnectedComponents() -" << componentCount << "component(s)"; return m_graphStronglyConnectedComponents; } /** * @brief Returns the sum of all finite geodesic distances accumulated by DistanceEngine, * without recalculating anything. */ qreal Graph::graphSumDistanceCached() const { return m_graphSumDistance; } /** * @brief Returns the number of geodesics (shortest paths) accumulated by DistanceEngine, * without recalculating anything. */ qreal Graph::graphGeodesicsCountCached() const { return m_graphGeodesicsCount; } SocNetV-3.7/src/graph/filters/000077500000000000000000000000001524402210300162355ustar00rootroot00000000000000SocNetV-3.7/src/graph/filters/filter_condition.h000066400000000000000000000056231524402210300217470ustar00rootroot00000000000000/** * @file filter_condition.h * @brief Defines the FilterCondition struct shared by all attribute-based filters. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #pragma once #include /** * @brief Describes a single attribute-based filter condition. * * Shared by Graph::vertexFilterByAttribute(), Graph::edgeFilterByAttribute(), * DialogFilterByAttribute, and the future filter bar (#219). */ struct FilterCondition { enum class Scope { Nodes, Edges, Both }; enum class Op { Eq, Neq, Gt, Lt, Gte, Lte, Contains }; Scope scope = Scope::Nodes; QString key; Op op = Op::Eq; QString value; /** Returns true if @p attrValue satisfies this condition. */ bool matches(const QString &attrValue) const { switch (op) { case Op::Eq: return attrValue == value; case Op::Neq: return attrValue != value; case Op::Contains: return attrValue.contains(value, Qt::CaseInsensitive); default: break; } bool okA = false, okB = false; const double a = attrValue.toDouble(&okA); const double b = value.toDouble(&okB); if (okA && okB) { switch (op) { case Op::Gt: return a > b; case Op::Lt: return a < b; case Op::Gte: return a >= b; case Op::Lte: return a <= b; default: break; } } switch (op) { case Op::Gt: return attrValue > value; case Op::Lt: return attrValue < value; case Op::Gte: return attrValue >= value; case Op::Lte: return attrValue <= value; default: break; } return false; } /** Short human-readable label for a filter bar chip. */ QString label() const { QString scopeStr; switch (scope) { case Scope::Nodes: scopeStr = QStringLiteral("Nodes"); break; case Scope::Edges: scopeStr = QStringLiteral("Edges"); break; case Scope::Both: scopeStr = QStringLiteral("Nodes+Edges"); break; } QString opStr; switch (op) { case Op::Eq: opStr = QStringLiteral("="); break; case Op::Neq: opStr = QStringLiteral("β‰ "); break; case Op::Gt: opStr = QStringLiteral(">"); break; case Op::Lt: opStr = QStringLiteral("<"); break; case Op::Gte: opStr = QStringLiteral("β‰₯"); break; case Op::Lte: opStr = QStringLiteral("≀"); break; case Op::Contains: opStr = QStringLiteral("contains"); break; } return scopeStr + QLatin1String(": ") + key + QLatin1Char(' ') + opStr + QLatin1Char(' ') + value; } }; SocNetV-3.7/src/graph/filters/filter_spec.h000066400000000000000000000052371524402210300207140ustar00rootroot00000000000000/** * @file filter_spec.h * @brief Replay descriptor stored alongside each GraphVisibilitySnapshot. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #pragma once #include #include "filter_condition.h" #include "../../global.h" // IndexType /** * @brief Records how to re-apply one filter operation from scratch. * * One FilterSpec is embedded in every GraphVisibilitySnapshot. * When the user removes an arbitrary filter chip, Graph::vertexFilterRemoveAt() * restores to the pre-first-filter base state and replays the remaining specs * in order via vertexFilterReplaySpec(). * * Every filter operation (node OR edge) pushes exactly one snapshot, so the * filter bar's chip order maps 1:1 to the snapshot stack (barIndex == stackIndex). * This invariant is maintained by chipCloseRequested using barIndex directly. * * Centrality replay requires the relevant index to still be computed; * if it is not, vertexFilterByCentrality() returns early with a status message * and that step is silently skipped during replay. */ struct FilterSpec { enum class Type { None, Attribute, // node attribute β€” replay via vertexFilterByAttribute(condition) Selection, // node selection β€” replay via vertexFilterBySelection(selection) Ego, // ego network β€” replay via vertexFilterByEgoNetwork(egoVertex, egoDepth) Centrality, // node centrality β€” replay via vertexFilterByCentrality(...) EdgeAttribute, // edge attribute β€” replay via edgeFilterByAttribute(condition) EdgeWeight, // edge weight β€” replay via edgeFilterByWeight(threshold, overThreshold) Query, // node AND-query β€” replay via vertexFilterByQuery(queryConditions) EdgeQuery // edge AND-query β€” replay via edgeFilterByQuery(queryConditions) }; Type type = Type::None; // Type::Attribute / Type::EdgeAttribute FilterCondition condition; // Type::Ego int egoVertex = 0; int egoDepth = 1; // Type::Selection QList selection; // Type::Centrality float centralityThreshold = 0.0f; bool centralityOverThreshold = false; SocNetV::IndexType centralityIndex = SocNetV::IndexType::DC; // Type::EdgeWeight float edgeWeightThreshold = 0.0f; bool edgeWeightOverThreshold = false; // Type::Query / Type::EdgeQuery QList queryConditions; }; SocNetV-3.7/src/graph/filters/graph_edge_filters.cpp000066400000000000000000000313571524402210300225670ustar00rootroot00000000000000/** * @file graph_edge_filters.cpp * @brief Implements edge filtering operations for the Graph class * (weight threshold filters, unilateral filters, relation-based filters). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include "filter_condition.h" #include "filter_spec.h" /** * @brief Hides edges whose weight does not satisfy the threshold condition. * * Non-destructive: pushes a GraphVisibilitySnapshot onto m_visibilityHistory * before making any changes, so the filter can be undone via vertexFilterRemoveAt(). * * @param m_threshold The weight threshold. * @param overThreshold If true, keep edges with weight >= threshold (hide the rest); * if false, keep edges with weight <= threshold. */ void Graph::edgeFilterByWeight(const qreal m_threshold, const bool overThreshold) { QString words; if (overThreshold) { qCDebug(lcFilters) << "filtering edges with weight over or equal" << m_threshold; words = "equal or over"; } else { qCDebug(lcFilters) << "Filtering edges with weight below or equal" << m_threshold; words = "equal or under"; } // Snapshot BEFORE making changes so the filter can be undone via vertexFilterRemoveAt(). GraphVisibilitySnapshot snapshot; snapshot.active = true; snapshot.spec.type = FilterSpec::Type::EdgeWeight; snapshot.spec.edgeWeightThreshold = static_cast(m_threshold); snapshot.spec.edgeWeightOverThreshold = overThreshold; VList::const_iterator si; for (si = m_graph.cbegin(); si != m_graph.cend(); ++si) { const int vnum = (*si)->number(); snapshot.nodeVisible.insert(vnum, (*si)->isEnabled()); H_edges::const_iterator ei = (*si)->m_outEdges.constBegin(); while (ei != (*si)->m_outEdges.constEnd()) { if (ei.value().first == m_curRelation) snapshot.arcVisible.insert(QPair(vnum, ei.key()), ei.value().second.second); ++ei; } } m_visibilityHistory.push(snapshot); VList::const_iterator it; int source, target = 0; qreal weight = 0, reverseEdgeWeight = 0; bool preserveReverseEdge = false; H_edges::iterator ed; // Loop over all vertices for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { source = (*it)->number(); // Loop over all out edges of source for (ed = (*it)->m_outEdges.begin(); ed != (*it)->m_outEdges.end(); ++ed) { // Init preserve reserve edge status to false preserveReverseEdge = false; if (ed.value().first != m_curRelation) { // This edge does not belong to this relation continue; } target = ed.key(); weight = ed.value().second.first; // Check the filtering type: over or under if (overThreshold) { // We should enable only edges with weight >= threshold if (weight < m_threshold) { // this outedge must be disabled - check reverse edge (a self-loop is // trivially "its own reverse" via hasEdgeFrom, so exclude it - see the // matching comment in graph_node_filters.cpp) reverseEdgeWeight = (source != target) ? (*it)->hasEdgeFrom(target) : 0; if (reverseEdgeWeight != 0 && reverseEdgeWeight >= m_threshold) { // reverse edge exists and doesn't match. It must be preserved. preserveReverseEdge = true; } // Disable the edge ed.value() = pair_i_fb(m_curRelation, pair_f_b(weight, false)); // Disable the inedge of the target vertex too (needed for inDegree) this->edgeInboundStatusSet(target, source, false); emit signalSetEdgeVisibility(m_curRelation, source, target, false, preserveReverseEdge, weight, reverseEdgeWeight); } else { // Enable the edge ed.value() = pair_i_fb(m_curRelation, pair_f_b(weight, true)); // Enable the inedge of the target vertex too (needed for inDegree) this->edgeInboundStatusSet(target, source, true); emit signalSetEdgeVisibility(m_curRelation, source, target, true, preserveReverseEdge); } } else { // We should enable edges with weight <= the threshold if (weight > m_threshold) { // this outedge must be disabled - check reverse edge (a self-loop is // trivially "its own reverse" via hasEdgeFrom, so exclude it - see the // matching comment in graph_node_filters.cpp) reverseEdgeWeight = (source != target) ? (*it)->hasEdgeFrom(target) : 0; if (reverseEdgeWeight != 0 && reverseEdgeWeight <= m_threshold) { // reverse edge exists and doesn't match. It must be preserved. preserveReverseEdge = true; } // Disable the edge ed.value() = pair_i_fb(m_curRelation, pair_f_b(weight, false)); // Disable the inedge of the target vertex too (needed for inDegree) this->edgeInboundStatusSet(target, source, false); emit signalSetEdgeVisibility(m_curRelation, source, target, false, preserveReverseEdge, weight, reverseEdgeWeight); } else { // Enable the edge ed.value() = pair_i_fb(m_curRelation, pair_f_b(weight, true)); // Enable the inedge of the target vertex too (needed for inDegree) this->edgeInboundStatusSet(target, source, true); emit signalSetEdgeVisibility(m_curRelation, source, target, true, preserveReverseEdge); } } } } // Update graph mod status setModStatus(ModStatus::EdgeCount); // Emit a status message progressStatus(tr("Edges with weight %1 %2 have been filtered.").arg(words).arg(m_threshold)); } /** * @brief Re-enables all edges in the current relation. * * Restores full edge visibility after a weight filter has been applied. * Non-destructive: only visibility is changed, no data is modified. */ void Graph::edgeFilterReset() { qCDebug(lcFilters) << "Graph::edgeFilterReset()"; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { const int source = (*it)->number(); H_edges::iterator ed; for (ed = (*it)->m_outEdges.begin(); ed != (*it)->m_outEdges.end(); ++ed) { if (ed.value().first != m_curRelation) continue; const qreal weight = ed.value().second.first; const qreal reverseWeight = (*it)->hasEdgeFrom(ed.key()); // See the matching comment in graph_node_filters.cpp: a self-loop is trivially // "its own reverse", which isn't a real distinct edge to preserve. const bool preserveReverse = (source != ed.key()) && (reverseWeight != 0); ed.value() = pair_i_fb(m_curRelation, pair_f_b(weight, true)); edgeInboundStatusSet(ed.key(), source, true); emit signalSetEdgeVisibility(m_curRelation, source, ed.key(), true, preserveReverse); } } setModStatus(ModStatus::EdgeCount); progressStatus(tr("All edges restored.")); } /** * @brief Enables or disables unilateral edges in current relationship. * * If toggle=true, all non-reciprocal edges are disabled, effectively making * the network symmetric. * * @param toggle */ void Graph::edgeFilterUnilateral(const bool &toggle) { qCDebug(lcFilters) << "Toggling unilateral edges:" << toggle; // Collected into one batch and dispatched once (WS3 M2) - see the matching comment in // relationSet(). QList visibilityChanges; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { visibilityChanges.append((*it)->setEnabledUnilateralEdges(toggle)); } notifyEdgesVisibilityBatch(visibilityChanges); setModStatus(ModStatus::EdgeCount); progressStatus(tr("Unilateral edges have been temporarily disabled.")); } /** * @brief Hides all edges whose custom attribute does not satisfy @p cond. * * Non-destructive: pushes a GraphVisibilitySnapshot onto m_visibilityHistory * before making any changes. Undo via vertexFilterRemoveAt() or vertexFilterRestoreAll(). * * Returns early with a status message if no edge satisfies the condition. * * @param cond The filter condition (key, operator, value). */ void Graph::edgeFilterByAttribute(const FilterCondition &cond) { qCDebug(lcFilters) << "Graph::edgeFilterByAttribute() key:" << cond.key << "op:" << static_cast(cond.op) << "value:" << cond.value; // Count matching edges (for early-exit guard). int matchCount = 0; VList::const_iterator vi; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { H_edges::const_iterator ei = (*vi)->m_outEdges.constBegin(); while (ei != (*vi)->m_outEdges.constEnd()) { if (ei.value().first == m_curRelation && ei.value().second.second) { const QHash attrs = (*vi)->outEdgeCustomAttributes(ei.key()); if (attrs.contains(cond.key) && cond.matches(attrs.value(cond.key))) ++matchCount; } ++ei; } } if (matchCount == 0) { progressStatus(tr("No edges found matching: %1.").arg(cond.label())); return; } // Snapshot BEFORE making changes. GraphVisibilitySnapshot snapshot; snapshot.active = true; snapshot.spec.type = FilterSpec::Type::EdgeAttribute; snapshot.spec.condition = cond; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int vnum = (*vi)->number(); snapshot.nodeVisible.insert(vnum, (*vi)->isEnabled()); H_edges::const_iterator ei = (*vi)->m_outEdges.constBegin(); while (ei != (*vi)->m_outEdges.constEnd()) { if (ei.value().first == m_curRelation) snapshot.arcVisible.insert(QPair(vnum, ei.key()), ei.value().second.second); ++ei; } } m_visibilityHistory.push(snapshot); // Apply: hide edges that do NOT match the condition. for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int source = (*vi)->number(); H_edges::iterator ei = (*vi)->m_outEdges.begin(); while (ei != (*vi)->m_outEdges.end()) { if (ei.value().first != m_curRelation) { ++ei; continue; } const int target = ei.key(); const qreal weight = ei.value().second.first; const qreal reverseWeight = (*vi)->hasEdgeFrom(target); // See the matching comment in graph_node_filters.cpp: a self-loop is trivially // "its own reverse", which isn't a real distinct edge to preserve. const bool preserveReverse = (source != target) && (reverseWeight != 0); const QHash attrs = (*vi)->outEdgeCustomAttributes(target); const bool condMet = attrs.contains(cond.key) && cond.matches(attrs.value(cond.key)); ei.value() = pair_i_fb(m_curRelation, pair_f_b(weight, condMet)); edgeInboundStatusSet(target, source, condMet); if (condMet) emit signalSetEdgeVisibility(m_curRelation, source, target, true, preserveReverse); else emit signalSetEdgeVisibility(m_curRelation, source, target, false, preserveReverse, weight, reverseWeight); ++ei; } } progressStatus(tr("Showing %1 edge(s) matching: %2.") .arg(matchCount).arg(cond.label())); } SocNetV-3.7/src/graph/filters/graph_node_filters.cpp000066400000000000000000000767271524402210300226220ustar00rootroot00000000000000/** * @file graph_node_filters.cpp * @brief Implements vertex filtering functions for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #include "graph.h" #include "filter_condition.h" #include "filter_spec.h" #include "graph_query.h" #include /** * @brief Filters vertices by their score on the given centrality or prestige index. * * Pushes a GraphVisibilitySnapshot onto m_visibilityHistory before making * any changes, so vertexFilterRestoreAll() can undo this filter. * * Performs two passes over the graph: * * - Pass 1: Enables or disables each vertex depending on whether its score * satisfies the threshold condition. Emits setVertexVisibility for every * vertex whose state changes. * * - Pass 2: Updates all edges. An edge is made visible only when both its * source and target vertices are enabled after pass 1. Emits * signalSetEdgeVisibility for every out-edge of every vertex. * * The two-pass design is required so that edge visibility can be determined * correctly: during pass 2 all vertices are already in their final enabled * state, so both endpoints of any edge can be queried reliably. * * Returns early with a status message if the requested index has not been * computed yet β€” the user should run the corresponding analysis first. * * @param threshold Score threshold to compare against. * @param overThreshold If true, disable vertices with score >= threshold; * if false, disable vertices with score <= threshold. * @param centralityIndex The centrality or prestige index to use, as defined * by the IndexType enum in global.h. * * @see isCentralityIndexComputed() * @see vertexFilterRestoreAll() * @see IndexType */ void Graph::vertexFilterByCentrality(const float threshold, const bool overThreshold, const IndexType centralityIndex) { qCDebug(lcFilters) << "Graph::vertexFilterByCentrality()" << "index:" << static_cast(centralityIndex) << "threshold:" << threshold << "overThreshold:" << overThreshold; if (!isCentralityIndexComputed(centralityIndex)) { qCDebug(lcFilters) << "Graph::vertexFilterByCentrality() - " "index" << static_cast(centralityIndex) << "not yet computed. Aborting."; progressStatus( tr("Please compute the selected centrality/prestige index first, " "then apply the filter.")); return; } // ------------------------------------------------------------------ // Snapshot current visibility state BEFORE making any changes. // Allows vertexFilterRestoreAll() to undo this filter. // ------------------------------------------------------------------ GraphVisibilitySnapshot snapshot; snapshot.active = true; VList::const_iterator si; for (si = m_graph.cbegin(); si != m_graph.cend(); ++si) { const int vnum = (*si)->number(); snapshot.nodeVisible.insert(vnum, (*si)->isEnabled()); H_edges::const_iterator ei = (*si)->m_outEdges.constBegin(); while (ei != (*si)->m_outEdges.constEnd()) { if (ei.value().first == m_curRelation) { snapshot.arcVisible.insert( QPair(vnum, ei.key()), ei.value().second.second); } ++ei; } } snapshot.spec.type = FilterSpec::Type::Centrality; snapshot.spec.centralityThreshold = threshold; snapshot.spec.centralityOverThreshold = overThreshold; snapshot.spec.centralityIndex = centralityIndex; m_visibilityHistory.push(snapshot); const QString condition = overThreshold ? QStringLiteral(">=") : QStringLiteral("<="); const qreal thresh = static_cast(threshold); // --------------------------------------------------------------- // PASS 1: Enable / disable vertices only, no edge changes yet. // After this pass every vertex is in its correct enabled state, // so the edge pass can safely query both endpoints. // --------------------------------------------------------------- bool anyVertexChanged = false; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { qreal score = 0.0; switch (centralityIndex) { case IndexType::DC: score = (*it)->DC(); break; case IndexType::CC: score = (*it)->CC(); break; case IndexType::IRCC: score = (*it)->IRCC(); break; case IndexType::BC: score = (*it)->BC(); break; case IndexType::SC: score = (*it)->SC(); break; case IndexType::EC: score = (*it)->EC(); break; case IndexType::PC: score = (*it)->PC(); break; case IndexType::IC: score = (*it)->IC(); break; case IndexType::EVC: score = (*it)->EVC(); break; case IndexType::DP: score = (*it)->DP(); break; case IndexType::PRP: score = (*it)->PRP(); break; case IndexType::PP: score = (*it)->PP(); break; case IndexType::CLC: score = (*it)->CLC(); break; case IndexType::KATZ: score = (*it)->KC(); break; case IndexType::BPC: score = (*it)->BPC(); break; default: score = (*it)->DC(); break; } const bool shouldDisable = overThreshold ? (score >= thresh) : (score <= thresh); const bool currentlyEnabled = (*it)->isEnabled(); qCDebug(lcFilters) << "PASS 1 - vertex" << (*it)->number() << "score=" << score << condition << threshold << "shouldDisable:" << shouldDisable << "currentlyEnabled:" << currentlyEnabled; if (currentlyEnabled == !shouldDisable) continue; // already in correct state (*it)->setEnabled(!shouldDisable); anyVertexChanged = true; emit setVertexVisibility((*it)->number(), !shouldDisable); } // PASS 2 β€” Update edge visibility. // Mirrors edgeFilterByWeight's proven re-enable pattern exactly. // An edge is visible only when both endpoints are enabled. for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { const int v = (*it)->number(); H_edges::iterator ed; for (ed = (*it)->m_outEdges.begin(); ed != (*it)->m_outEdges.end(); ++ed) { if (ed.value().first != m_curRelation) continue; const int target = ed.key(); const qreal weight = ed.value().second.first; const qreal reverseEdgeWeight = (*it)->hasEdgeFrom(target); // A self-loop is trivially "its own reverse" (hasEdgeFrom(v) on itself matches // its own out-edge), which would otherwise make the visibility-toggle code below // delete-then-immediately-redraw it instead of leaving it hidden. const bool preserveReverse = (v != target) && (reverseEdgeWeight != 0); const bool sourceEnabled = (*it)->isEnabled(); const bool targetEnabled = vertexAtIndex(vertexIndexByNumber(target))->isEnabled(); const bool edgeVisible = sourceEnabled && targetEnabled; qCDebug(lcFilters) << "PASS 2 - edge" << v << "->" << target << "sourceEnabled:" << sourceEnabled << "targetEnabled:" << targetEnabled << "edgeVisible:" << edgeVisible; // Update out-edge storage ed.value() = pair_i_fb(m_curRelation, pair_f_b(weight, edgeVisible)); // Update in-edge bookkeeping on target edgeInboundStatusSet(target, v, edgeVisible); if (edgeVisible) { // Re-enable: use the same short form as edgeFilterByWeight emit signalSetEdgeVisibility(m_curRelation, v, target, true, preserveReverse); } else { // Disable: pass weights so GW can decide on arc rendering emit signalSetEdgeVisibility(m_curRelation, v, target, false, preserveReverse, weight, reverseEdgeWeight); } } } // Refresh the Statistics Panel once, after both vertex and edge visibility are in // their final state - calling this during PASS 1 would emit edgesEnabled() before // PASS 2 ever touches edge visibility, leaving the panel showing a stale edge count. if (anyVertexChanged) { setModStatus(ModStatus::VertexCount); } progressStatus( tr("Filter applied: vertices with score %1 %2 are now hidden.") .arg(condition, QString::number(threshold))); } /** * @brief Saves current visibility state and shows only the ego network * of vertex v1 at the given depth. * * Non-destructive: pushes a GraphVisibilitySnapshot onto m_visibilityHistory * before making any changes. Call vertexFilterRestoreAll() to undo. * * Only out-edges in the current relation are used to determine neighbors. * Works correctly for both directed and undirected graphs. * * @param v1 The ego vertex (center of the neighborhood). * @param depth Neighborhood depth (currently only depth=1 supported). */ void Graph::vertexFilterByEgoNetwork(const int v1, const int depth) { Q_UNUSED(depth); // reserved for future k-hop support qCDebug(lcFilters) << "Graph::vertexFilterByEgoNetwork() - ego:" << v1; if (!vertexExists(v1)) { qCDebug(lcFilters) << "Graph::vertexFilterByEgoNetwork() - vertex" << v1 << "not found. Aborting."; return; } // ------------------------------------------------------------------ // Build the visible set: ego + its 1-hop neighbors via enabled // out-edges in the current relation. // ------------------------------------------------------------------ QSet visibleSet; visibleSet.insert(v1); const GraphVertex *ego = vertexAtIndex(vpos[v1]); H_edges::const_iterator it = ego->m_outEdges.constBegin(); while (it != ego->m_outEdges.constEnd()) { if (it.value().first == m_curRelation && it.value().second.second == true) { visibleSet.insert(it.key()); } ++it; } qCDebug(lcFilters) << "Graph::vertexFilterByEgoNetwork() - visible set:" << visibleSet; // ------------------------------------------------------------------ // Snapshot current visibility state BEFORE making any changes. // ------------------------------------------------------------------ GraphVisibilitySnapshot snapshot; snapshot.active = true; VList::const_iterator vi; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int vnum = (*vi)->number(); snapshot.nodeVisible.insert(vnum, (*vi)->isEnabled()); H_edges::const_iterator ei = (*vi)->m_outEdges.constBegin(); while (ei != (*vi)->m_outEdges.constEnd()) { if (ei.value().first == m_curRelation) { snapshot.arcVisible.insert( QPair(vnum, ei.key()), ei.value().second.second); } ++ei; } } snapshot.spec.type = FilterSpec::Type::Ego; snapshot.spec.egoVertex = v1; snapshot.spec.egoDepth = depth; m_visibilityHistory.push(snapshot); // ------------------------------------------------------------------ // PASS 1: Set vertex visibility. // ------------------------------------------------------------------ bool anyVertexChanged = false; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int vnum = (*vi)->number(); const bool shouldBeVisible = visibleSet.contains(vnum); if ((*vi)->isEnabled() != shouldBeVisible) { (*vi)->setEnabled(shouldBeVisible); anyVertexChanged = true; emit setVertexVisibility(vnum, shouldBeVisible); } } // ------------------------------------------------------------------ // PASS 2: Set edge visibility. // An edge is visible only if both endpoints are in the visible set. // ------------------------------------------------------------------ for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int source = (*vi)->number(); H_edges::iterator ei = (*vi)->m_outEdges.begin(); while (ei != (*vi)->m_outEdges.end()) { if (ei.value().first != m_curRelation) { ++ei; continue; } const int target = ei.key(); const qreal weight = ei.value().second.first; const qreal reverseWeight = (*vi)->hasEdgeFrom(target); // See the matching comment in vertexFilterByCentrality(): a self-loop is // trivially "its own reverse", which must not trigger the delete-then-redraw // path meant for genuinely distinct reciprocated edges. const bool preserveReverse = (source != target) && (reverseWeight != 0); const bool edgeShouldBeVisible = visibleSet.contains(source) && visibleSet.contains(target); ei.value() = pair_i_fb(m_curRelation, pair_f_b(weight, edgeShouldBeVisible)); edgeInboundStatusSet(target, source, edgeShouldBeVisible); if (edgeShouldBeVisible) { emit signalSetEdgeVisibility(m_curRelation, source, target, true, preserveReverse); } else { emit signalSetEdgeVisibility(m_curRelation, source, target, false, preserveReverse, weight, reverseWeight); } ++ei; } } // Refresh the Statistics Panel once, after both passes - see the matching // comment in vertexFilterByCentrality() for why this can't happen in PASS 1. if (anyVertexChanged) { setModStatus(ModStatus::VertexCount); } progressStatus(tr("Showing ego network of node %1 (%2 neighbors).") .arg(v1) .arg(visibleSet.size() - 1)); } /** * @brief Saves current visibility state and shows only the selected vertices * and the edges between them. * * Non-destructive: pushes a GraphVisibilitySnapshot onto m_visibilityHistory * before making any changes. Call vertexFilterRestoreAll() to undo. * * Implements the "Focus on Selection" mode (#210): all nodes not in * @p selectedVertices are hidden, and only edges whose both endpoints are * in the selection remain visible. * * @param selectedVertices List of vertex numbers that should remain visible. */ void Graph::vertexFilterBySelection(const QList &selectedVertices) { qCDebug(lcFilters) << "Graph::vertexFilterBySelection() - selection:" << selectedVertices; if (selectedVertices.isEmpty()) { qCDebug(lcFilters) << "Graph::vertexFilterBySelection() - empty selection, aborting."; progressStatus(tr("No nodes selected. Please select at least one node first.")); return; } const QSet visibleSet(selectedVertices.cbegin(), selectedVertices.cend()); // ------------------------------------------------------------------ // Snapshot current visibility state BEFORE making any changes. // ------------------------------------------------------------------ GraphVisibilitySnapshot snapshot; snapshot.spec.type = FilterSpec::Type::Selection; snapshot.spec.selection = selectedVertices; snapshot.active = true; VList::const_iterator vi; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int vnum = (*vi)->number(); snapshot.nodeVisible.insert(vnum, (*vi)->isEnabled()); H_edges::const_iterator ei = (*vi)->m_outEdges.constBegin(); while (ei != (*vi)->m_outEdges.constEnd()) { if (ei.value().first == m_curRelation) { snapshot.arcVisible.insert( QPair(vnum, ei.key()), ei.value().second.second); } ++ei; } } m_visibilityHistory.push(snapshot); // ------------------------------------------------------------------ // PASS 1: Set vertex visibility. // ------------------------------------------------------------------ bool anyVertexChanged = false; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int vnum = (*vi)->number(); const bool shouldBeVisible = visibleSet.contains(vnum); if ((*vi)->isEnabled() != shouldBeVisible) { (*vi)->setEnabled(shouldBeVisible); anyVertexChanged = true; emit setVertexVisibility(vnum, shouldBeVisible); } } // ------------------------------------------------------------------ // PASS 2: Set edge visibility. // An edge is visible only if both endpoints are in the selection. // ------------------------------------------------------------------ for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int source = (*vi)->number(); H_edges::iterator ei = (*vi)->m_outEdges.begin(); while (ei != (*vi)->m_outEdges.end()) { if (ei.value().first != m_curRelation) { ++ei; continue; } const int target = ei.key(); const qreal weight = ei.value().second.first; const qreal reverseWeight = (*vi)->hasEdgeFrom(target); // See the matching comment in vertexFilterByCentrality(): a self-loop is // trivially "its own reverse", which must not trigger the delete-then-redraw // path meant for genuinely distinct reciprocated edges. const bool preserveReverse = (source != target) && (reverseWeight != 0); const bool edgeShouldBeVisible = visibleSet.contains(source) && visibleSet.contains(target); ei.value() = pair_i_fb(m_curRelation, pair_f_b(weight, edgeShouldBeVisible)); edgeInboundStatusSet(target, source, edgeShouldBeVisible); if (edgeShouldBeVisible) { emit signalSetEdgeVisibility(m_curRelation, source, target, true, preserveReverse); } else { emit signalSetEdgeVisibility(m_curRelation, source, target, false, preserveReverse, weight, reverseWeight); } ++ei; } } // Refresh the Statistics Panel once, after both passes - see the matching // comment in vertexFilterByCentrality() for why this can't happen in PASS 1. if (anyVertexChanged) { setModStatus(ModStatus::VertexCount); } progressStatus(tr("Showing %1 selected node(s) and edges between them.") .arg(visibleSet.size())); } // --------------------------------------------------------------------------- // Node attribute filter // --------------------------------------------------------------------------- /** * @brief Shows only vertices whose custom attribute satisfies @p cond; * all other vertices are hidden. * * Non-destructive: pushes a GraphVisibilitySnapshot onto m_visibilityHistory * before making any changes. Call vertexFilterRestoreAll() to undo. * * Returns early with a status message if the resulting visible set is empty. * * @param cond The filter condition (key, operator, value). */ void Graph::vertexFilterByAttribute(const FilterCondition &cond) { qCDebug(lcFilters) << "Graph::vertexFilterByAttribute() key:" << cond.key << "op:" << static_cast(cond.op) << "value:" << cond.value; // Build visible set: vertices that satisfy the condition. QSet visibleSet; VList::const_iterator vi; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const QHash attrs = (*vi)->customAttributes(); if (attrs.contains(cond.key) && cond.matches(attrs.value(cond.key))) visibleSet.insert((*vi)->number()); } if (visibleSet.isEmpty()) { progressStatus(tr("No nodes found matching: %1.").arg(cond.label())); return; } // Snapshot current visibility state BEFORE making any changes. GraphVisibilitySnapshot snapshot; snapshot.active = true; snapshot.spec.type = FilterSpec::Type::Attribute; snapshot.spec.condition = cond; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int vnum = (*vi)->number(); snapshot.nodeVisible.insert(vnum, (*vi)->isEnabled()); H_edges::const_iterator ei = (*vi)->m_outEdges.constBegin(); while (ei != (*vi)->m_outEdges.constEnd()) { if (ei.value().first == m_curRelation) { snapshot.arcVisible.insert( QPair(vnum, ei.key()), ei.value().second.second); } ++ei; } } m_visibilityHistory.push(snapshot); // PASS 1: Set vertex visibility. bool anyVertexChanged = false; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int vnum = (*vi)->number(); const bool shouldBeVisible = visibleSet.contains(vnum); if ((*vi)->isEnabled() != shouldBeVisible) { (*vi)->setEnabled(shouldBeVisible); anyVertexChanged = true; emit setVertexVisibility(vnum, shouldBeVisible); } } // PASS 2: Set edge visibility (both endpoints must be visible). for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int source = (*vi)->number(); H_edges::iterator ei = (*vi)->m_outEdges.begin(); while (ei != (*vi)->m_outEdges.end()) { if (ei.value().first != m_curRelation) { ++ei; continue; } const int target = ei.key(); const qreal weight = ei.value().second.first; const qreal reverseWeight = (*vi)->hasEdgeFrom(target); // See the matching comment in vertexFilterByCentrality(): a self-loop is // trivially "its own reverse", which must not trigger the delete-then-redraw // path meant for genuinely distinct reciprocated edges. const bool preserveReverse = (source != target) && (reverseWeight != 0); const bool edgeShouldBeVisible = visibleSet.contains(source) && visibleSet.contains(target); ei.value() = pair_i_fb(m_curRelation, pair_f_b(weight, edgeShouldBeVisible)); edgeInboundStatusSet(target, source, edgeShouldBeVisible); if (edgeShouldBeVisible) emit signalSetEdgeVisibility(m_curRelation, source, target, true, preserveReverse); else emit signalSetEdgeVisibility(m_curRelation, source, target, false, preserveReverse, weight, reverseWeight); ++ei; } } // Refresh the Statistics Panel once, after both passes - see the matching // comment in vertexFilterByCentrality() for why this can't happen in PASS 1. if (anyVertexChanged) { setModStatus(ModStatus::VertexCount); } progressStatus(tr("Showing %1 node(s) matching: %2.") .arg(visibleSet.size()).arg(cond.label())); } /** * @brief Restores vertex and edge visibility from the top snapshot on the * history stack. * * If the stack is empty (no filter is active), this is a no-op. * Pops the snapshot after restoring, so repeated calls walk back through * the filter history one step at a time. */ void Graph::vertexFilterRestoreAll() { qCDebug(lcFilters) << "Graph::vertexFilterRestoreAll()"; if (m_visibilityHistory.isEmpty()) { qCDebug(lcFilters) << "Graph::vertexFilterRestoreAll() - history stack empty, nothing to restore."; progressStatus(tr("No active filter to restore.")); return; } applyVisibilitySnapshot(m_visibilityHistory.pop()); progressStatus(tr("Graph visibility restored.")); } /** * @brief Restores vertex and edge visibility to the state recorded in @p snap. * * Private helper shared by vertexFilterRestoreAll() and vertexFilterRemoveAt(). * Does not touch m_visibilityHistory β€” callers manage the stack. */ void Graph::applyVisibilitySnapshot(const GraphVisibilitySnapshot &snap) { // PASS 1: Restore vertex visibility. bool anyVertexChanged = false; VList::const_iterator vi; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int vnum = (*vi)->number(); const bool wasVisible = snap.nodeVisible.value(vnum, true); if ((*vi)->isEnabled() != wasVisible) { (*vi)->setEnabled(wasVisible); anyVertexChanged = true; emit setVertexVisibility(vnum, wasVisible); } } // PASS 2: Restore edge visibility. for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int source = (*vi)->number(); H_edges::iterator ei = (*vi)->m_outEdges.begin(); while (ei != (*vi)->m_outEdges.end()) { if (ei.value().first != m_curRelation) { ++ei; continue; } const int target = ei.key(); const qreal weight = ei.value().second.first; const qreal reverseWeight = (*vi)->hasEdgeFrom(target); // See the matching comment in vertexFilterByCentrality(): a self-loop is // trivially "its own reverse", which must not trigger the delete-then-redraw // path meant for genuinely distinct reciprocated edges. const bool preserveReverse = (source != target) && (reverseWeight != 0); const bool wasVisible = snap.arcVisible.value(QPair(source, target), true); ei.value() = pair_i_fb(m_curRelation, pair_f_b(weight, wasVisible)); edgeInboundStatusSet(target, source, wasVisible); if (wasVisible) emit signalSetEdgeVisibility(m_curRelation, source, target, true, preserveReverse); else emit signalSetEdgeVisibility(m_curRelation, source, target, false, preserveReverse, weight, reverseWeight); ++ei; } } // Refresh the Statistics Panel once, after both passes - see the matching // comment in vertexFilterByCentrality() for why this can't happen in PASS 1. if (anyVertexChanged) { setModStatus(ModStatus::VertexCount); } } /** * @brief Removes the filter at @p stackIndex (0 = oldest) and replays the rest. * * Drains m_visibilityHistory to a list, restores to the pre-first-filter base * state (the oldest snapshot), then re-applies every spec except the one at * @p stackIndex. Each replay call pushes a fresh snapshot, leaving the stack * correct for subsequent single-step restores. * * Centrality replay requires the relevant index to still be computed; if it is * not, vertexFilterByCentrality() returns early and that step is skipped. */ void Graph::vertexFilterRemoveAt(int stackIndex) { const int histSize = m_visibilityHistory.size(); if (stackIndex < 0 || stackIndex >= histSize) return; // Transfer stack to list in application order (index 0 = oldest). QList history; history.reserve(histSize); while (!m_visibilityHistory.isEmpty()) history.prepend(m_visibilityHistory.pop()); // Restore to the pre-first-filter state (history[0] was captured before // any filter was applied, so applying it shows all nodes again). applyVisibilitySnapshot(history[0]); // Replay every spec except the removed one. for (int i = 0; i < histSize; ++i) { if (i == stackIndex) continue; vertexFilterReplaySpec(history[i].spec); } progressStatus(tr("Filter removed; %1 filter(s) still active.") .arg(histSize - 1)); } /** * @brief Re-applies one filter from its stored replay parameters. * * Used by vertexFilterRemoveAt() during stack reconstruction. */ void Graph::vertexFilterReplaySpec(const FilterSpec &spec) { switch (spec.type) { case FilterSpec::Type::Attribute: vertexFilterByAttribute(spec.condition); break; case FilterSpec::Type::Ego: vertexFilterByEgoNetwork(spec.egoVertex, spec.egoDepth); break; case FilterSpec::Type::Selection: if (!spec.selection.isEmpty()) vertexFilterBySelection(spec.selection); break; case FilterSpec::Type::Centrality: vertexFilterByCentrality(spec.centralityThreshold, spec.centralityOverThreshold, spec.centralityIndex); break; case FilterSpec::Type::EdgeAttribute: edgeFilterByAttribute(spec.condition); break; case FilterSpec::Type::EdgeWeight: edgeFilterByWeight(static_cast(spec.edgeWeightThreshold), spec.edgeWeightOverThreshold); break; case FilterSpec::Type::Query: if (!spec.queryConditions.isEmpty()) vertexFilterByQuery(GraphQuery{spec.queryConditions}); break; case FilterSpec::Type::EdgeQuery: if (!spec.queryConditions.isEmpty()) edgeFilterByQuery(GraphQuery{spec.queryConditions}); break; default: break; } } /** * @brief Returns the FilterSpec list in application order (oldest first). * * Available for consumers that need to inspect the active filter sequence * (e.g. Phase 1 DialogQueryBuilder prefill). MainWindow currently tracks * chip labels separately via m_nodeFilterChips. * QStack inherits QVector, so at(0) is the bottom (oldest) entry. */ QList Graph::filterSpecList() const { QList result; result.reserve(m_visibilityHistory.size()); for (int i = 0; i < m_visibilityHistory.size(); ++i) result.append(m_visibilityHistory.at(i).spec); return result; } /** * @brief Returns true if the visibility history stack is empty. * * Used by the UI to determine whether a "Restore All" action should * be enabled. The stack holds one entry per non-destructive filter * operation (e.g. ego network focus). Each call to vertexFilterRestoreAll() * pops one entry; when the stack is empty, there is nothing left to restore. * * @return true if no filter snapshots are pending, false otherwise. * @see vertexFilterByEgoNetwork() * @see vertexFilterRestoreAll() */ bool Graph::visibilityHistoryEmpty() const { return m_visibilityHistory.isEmpty(); }SocNetV-3.7/src/graph/filters/graph_query.h000066400000000000000000000016321524402210300207360ustar00rootroot00000000000000/** * @file graph_query.h * @brief Defines GraphQuery β€” a multi-condition AND filter applied as one chip. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #pragma once #include #include "filter_condition.h" /** * @brief A compound AND-logic filter applied in one operation (one snapshot, one chip). * * All conditions must be satisfied for a node or edge to remain visible. * The scope (Nodes or Edges) is set on each FilterCondition; all conditions * in one GraphQuery carry the same scope (set by DialogQueryBuilder). * * Applied via Graph::vertexFilterByQuery() (Nodes) or Graph::edgeFilterByQuery() (Edges). */ struct GraphQuery { QList conditions; }; SocNetV-3.7/src/graph/filters/graph_query_filters.cpp000066400000000000000000000217541524402210300230300ustar00rootroot00000000000000/** * @file graph_query_filters.cpp * @brief Implements compound AND-query filters for nodes and edges (#221 Phase 1). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #include "graph.h" #include "graph_query.h" #include "filter_spec.h" #include /** * @brief Shows only vertices whose custom attributes satisfy ALL conditions in @p query. * * AND logic: a vertex must pass every condition to remain visible. * Non-destructive: pushes a GraphVisibilitySnapshot with FilterSpec::Type::Query * before making any changes. Undo via vertexFilterRemoveAt() or vertexFilterRestoreAll(). * * Follows the same two-pass pattern as vertexFilterByAttribute() so edge visibility * is resolved after all vertex states are final. * * @param query Compound query; all conditions should have Scope::Nodes. */ void Graph::vertexFilterByQuery(const GraphQuery &query) { if (query.conditions.isEmpty()) return; qCDebug(lcFilters) << "Graph::vertexFilterByQuery()" << query.conditions.size() << "condition(s)"; // Build visible set: vertices satisfying ALL conditions. QSet visibleSet; VList::const_iterator vi; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const QHash attrs = (*vi)->customAttributes(); bool allMatch = true; for (const auto &cond : std::as_const(query.conditions)) { if (!attrs.contains(cond.key) || !cond.matches(attrs.value(cond.key))) { allMatch = false; break; } } if (allMatch) visibleSet.insert((*vi)->number()); } if (visibleSet.isEmpty()) { progressStatus(tr("No nodes match all %1 condition(s).").arg(query.conditions.size())); return; } // Snapshot BEFORE making changes. GraphVisibilitySnapshot snapshot; snapshot.active = true; snapshot.spec.type = FilterSpec::Type::Query; snapshot.spec.queryConditions = query.conditions; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int vnum = (*vi)->number(); snapshot.nodeVisible.insert(vnum, (*vi)->isEnabled()); H_edges::const_iterator ei = (*vi)->m_outEdges.constBegin(); while (ei != (*vi)->m_outEdges.constEnd()) { if (ei.value().first == m_curRelation) snapshot.arcVisible.insert(QPair(vnum, ei.key()), ei.value().second.second); ++ei; } } m_visibilityHistory.push(snapshot); // PASS 1: Set vertex visibility. for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int vnum = (*vi)->number(); const bool shouldBeVisible = visibleSet.contains(vnum); if ((*vi)->isEnabled() != shouldBeVisible) { (*vi)->setEnabled(shouldBeVisible); setModStatus(ModStatus::VertexCount); emit setVertexVisibility(vnum, shouldBeVisible); } } // PASS 2: Set edge visibility (both endpoints must be visible). for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int source = (*vi)->number(); H_edges::iterator ei = (*vi)->m_outEdges.begin(); while (ei != (*vi)->m_outEdges.end()) { if (ei.value().first != m_curRelation) { ++ei; continue; } const int target = ei.key(); const qreal weight = ei.value().second.first; const qreal reverseWeight = (*vi)->hasEdgeFrom(target); // See the matching comment in graph_node_filters.cpp: a self-loop is trivially // "its own reverse", which isn't a real distinct edge to preserve. const bool preserveReverse = (source != target) && (reverseWeight != 0); const bool edgeVisible = visibleSet.contains(source) && visibleSet.contains(target); ei.value() = pair_i_fb(m_curRelation, pair_f_b(weight, edgeVisible)); edgeInboundStatusSet(target, source, edgeVisible); if (edgeVisible) emit signalSetEdgeVisibility(m_curRelation, source, target, true, preserveReverse); else emit signalSetEdgeVisibility(m_curRelation, source, target, false, preserveReverse, weight, reverseWeight); ++ei; } } progressStatus(tr("Query: showing %1 node(s) matching all %2 condition(s).") .arg(visibleSet.size()).arg(query.conditions.size())); } /** * @brief Shows only edges whose custom attributes satisfy ALL conditions in @p query. * * AND logic: an edge must pass every condition to remain visible. * Non-destructive: pushes a GraphVisibilitySnapshot with FilterSpec::Type::EdgeQuery. * Undo via vertexFilterRemoveAt() or vertexFilterRestoreAll(). * * @param query Compound query; all conditions should have Scope::Edges. */ void Graph::edgeFilterByQuery(const GraphQuery &query) { if (query.conditions.isEmpty()) return; qCDebug(lcFilters) << "Graph::edgeFilterByQuery()" << query.conditions.size() << "condition(s)"; // Count matching edges (for early-exit guard). int matchCount = 0; VList::const_iterator vi; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { H_edges::const_iterator ei = (*vi)->m_outEdges.constBegin(); while (ei != (*vi)->m_outEdges.constEnd()) { if (ei.value().first == m_curRelation && ei.value().second.second) { const QHash attrs = (*vi)->outEdgeCustomAttributes(ei.key()); bool allMatch = true; for (const auto &cond : std::as_const(query.conditions)) { if (!attrs.contains(cond.key) || !cond.matches(attrs.value(cond.key))) { allMatch = false; break; } } if (allMatch) ++matchCount; } ++ei; } } if (matchCount == 0) { progressStatus(tr("No edges match all %1 condition(s).").arg(query.conditions.size())); return; } // Snapshot BEFORE making changes. GraphVisibilitySnapshot snapshot; snapshot.active = true; snapshot.spec.type = FilterSpec::Type::EdgeQuery; snapshot.spec.queryConditions = query.conditions; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int vnum = (*vi)->number(); snapshot.nodeVisible.insert(vnum, (*vi)->isEnabled()); H_edges::const_iterator ei = (*vi)->m_outEdges.constBegin(); while (ei != (*vi)->m_outEdges.constEnd()) { if (ei.value().first == m_curRelation) snapshot.arcVisible.insert(QPair(vnum, ei.key()), ei.value().second.second); ++ei; } } m_visibilityHistory.push(snapshot); // Apply: hide edges not satisfying ALL conditions. for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int source = (*vi)->number(); H_edges::iterator ei = (*vi)->m_outEdges.begin(); while (ei != (*vi)->m_outEdges.end()) { if (ei.value().first != m_curRelation) { ++ei; continue; } const int target = ei.key(); const qreal weight = ei.value().second.first; const qreal reverseWeight = (*vi)->hasEdgeFrom(target); // See the matching comment in graph_node_filters.cpp: a self-loop is trivially // "its own reverse", which isn't a real distinct edge to preserve. const bool preserveReverse = (source != target) && (reverseWeight != 0); const QHash attrs = (*vi)->outEdgeCustomAttributes(target); bool condMet = true; for (const auto &cond : std::as_const(query.conditions)) { if (!attrs.contains(cond.key) || !cond.matches(attrs.value(cond.key))) { condMet = false; break; } } ei.value() = pair_i_fb(m_curRelation, pair_f_b(weight, condMet)); edgeInboundStatusSet(target, source, condMet); if (condMet) emit signalSetEdgeVisibility(m_curRelation, source, target, true, preserveReverse); else emit signalSetEdgeVisibility(m_curRelation, source, target, false, preserveReverse, weight, reverseWeight); ++ei; } } progressStatus(tr("Query: showing %1 edge(s) matching all %2 condition(s).") .arg(matchCount).arg(query.conditions.size())); } SocNetV-3.7/src/graph/generators/000077500000000000000000000000001524402210300167365ustar00rootroot00000000000000SocNetV-3.7/src/graph/generators/graph_random_networks.cpp000066400000000000000000000671761524402210300240600ustar00rootroot00000000000000/** * @file graph_random_networks.cpp * @brief Implements random network generation algorithms of the Graph class (e.g., ErdΕ‘s–RΓ©nyi, Watts–Strogatz and related models). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Adds a little universal randomness :) */ void Graph::randomizeThings() { time_t now; /* define 'now'. time_t is probably a typedef */ now = time((time_t *)NULL); /* Get the system time and put it * into 'now' as 'calender time' the number of seconds since 1/1/1970 */ srand((unsigned int)now); } /** * @brief Creates an ErdΕ‘s–RΓ©nyi random network. * * Supports two models: * - G(n,p): each possible edge is included independently with probability p. * - G(n,M): exactly M edges are placed at random among all possible pairs. * * @param N Number of nodes. * @param model "G(n,p)" or "G(n,M)". * @param m Number of edges (used in G(n,M) model; 0 for G(n,p)). * @param p Edge probability (used in G(n,p) model; 0 for G(n,M)). * @param mode "graph" for undirected, anything else for directed. * @param diag If true, self-loops are allowed. * @return true on success, false if the user cancelled. */ bool Graph::randomNetErdosCreate(const int &N, const QString &model, const int &m, const qreal &p, const QString &mode, const bool &diag) { qCDebug(lcGenerators) << "Creating Erdos-Renyi random network:" << "N" << N << "model" << model << "edges" << m << "edge probability" << p << "mode" << mode << "diag" << diag; if (mode == "graph") { setDirected(false); } vpos.reserve(N); randomizeThings(); int edgeCount = 0; // Not dispatched via runGraphOperationAsync's centralized reset when called from the // --interactive-script erdos/erdos-m benchmark harness (kept unwrapped deliberately, for // timing precision) - this is that path's only reset point. resetProgressCanceled(); // Create all nodes first for (int i = 0; i < N; i++) { int x = canvasRandomX(); int y = canvasRandomY(); vertexCreate( i + 1, initVertexSize, initVertexColor, initVertexNumberColor, initVertexNumberSize, QString::number(i + 1), initVertexLabelColor, initVertexLabelSize, QPoint(x, y), initVertexShape, initVertexIconPath, false); } qCDebug(lcGenerators) << "Nodes created. Creating edges using model" << model; if (model == "G(n,p)") { // Bernoulli trials: each pair (i,j) gets an edge with probability p. // rand()%100 gives 100 equally-likely values 0..99; dividing by 100 gives // {0.00, ..., 0.99}, of which exactly k are < k/100 - matching the requested // probability at every quantization level (Fix #267). for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (!diag && i == j) continue; if ((rand() % 100) / 100.0 < p) { edgeCount++; if (mode == "graph") { edgeCreate(i + 1, j + 1, 1, initEdgeColor, EdgeType::Undirected, false, false, QString(), false); } else { edgeCreate(i + 1, j + 1, 1, initEdgeColor, EdgeType::Directed, true, false, QString(), false); } } } if (progressCanceled()) { return false; } } } else { // G(n,M): place exactly m edges at random positions int source = 0, target = 0; bool cancelled = false; do { source = rand() % N + 1; target = rand() % N + 1; if (!diag && source == target) continue; if (edgeExists(source, target)) continue; edgeCount++; if (mode == "graph") { edgeCreate(source, target, 1, initEdgeColor, EdgeType::Undirected, false, false, QString(), false); } else { edgeCreate(source, target, 1, initEdgeColor, EdgeType::Directed, true, false, QString(), false); } if (progressCanceled()) { cancelled = true; break; } } while (edgeCount != m); if (cancelled) { return false; } } relationCurrentRename(tr("erdos-renyi"), true); setModStatus(ModStatus::VertexEdgeCount); emit signalLayoutFinished(); return true; } /** * @brief Creates a BarabΓ‘si–Albert scale-free random network. * * The algorithm works in two phases: * 1. Seed: build a fully connected clique of m0 initial nodes. * 2. Growth: add nodes one by one up to N, each connecting to m existing * nodes via preferential attachment β€” nodes with higher degree are more * likely to receive new edges (rich-get-richer effect). * * The attachment probability for node j is: * P(j) = (alpha + degree(j)^power) / sumDegrees * * @param N Total number of nodes in the final network. * @param power Exponent of the preferential attachment (typically 1). * @param m0 Number of nodes in the initial seed clique. * @param m Number of edges each new node attaches to existing nodes. * @param alpha Additive constant in the attachment probability (zero-appeal). * @param mode "graph" for undirected, anything else for directed. * @return true on success, false if the user cancelled. */ bool Graph::randomNetScaleFreeCreate(const int &N, const int &power, const int &m0, const int &m, const qreal &alpha, const QString &mode) { qCDebug(lcGenerators) << "Graph::randomNetScaleFreeCreate() -" << "N" << N << "power" << power << "m0" << m0 << "m" << m << "alpha" << alpha << "mode" << mode; randomizeThings(); if (mode == "graph") { setDirected(false); } int x = 0; int y = 0; int newEdges = 0; double sumDegrees = 0; double k_j; double x0 = canvasWidth / 2.0; double y0 = canvasHeight / 2.0; double radius = canvasMaxRadius(); double rad = (2.0 * M_PI / N); double prob_j = 0, prob = 0; vpos.reserve(N); QString pMsg = tr("Creating Scale-Free Random Network. \n" "Please wait..."); progressStatus(pMsg); // Phase 1: create the initial seed clique of m0 nodes qCDebug(lcGenerators) << "Graph::randomNetScaleFreeCreate() - creating seed clique of" << m0 << "nodes"; for (int i = 0; i < m0; ++i) { x = x0 + radius * cos(i * rad); y = y0 + radius * sin(i * rad); vertexCreate( i + 1, initVertexSize, initVertexColor, initVertexNumberColor, initVertexNumberSize, QString::number(i + 1), initVertexLabelColor, initVertexLabelSize, QPoint(x, y), initVertexShape, initVertexIconPath, false); } for (int i = 0; i < m0; ++i) { for (int j = i + 1; j < m0; ++j) { // Respect mode when creating seed clique edges, // consistent with the growth phase below if (mode == "graph") { edgeCreate(i + 1, j + 1, 1, initEdgeColor, EdgeType::Undirected, false, false, QString(), false); } else { edgeCreate(i + 1, j + 1, 1, initEdgeColor, EdgeType::Directed, true, false, QString(), false); } } if (progressCanceled()) { return false; } } // Phase 2: grow the network to N nodes via preferential attachment qCDebug(lcGenerators) << "Graph::randomNetScaleFreeCreate() - growing network to" << N << "nodes"; for (int i = m0; i < N; ++i) { x = x0 + radius * cos(i * rad); y = y0 + radius * sin(i * rad); vertexCreate( i + 1, initVertexSize, initVertexColor, initVertexNumberColor, initVertexNumberSize, QString::number(i + 1), initVertexLabelColor, initVertexLabelSize, QPoint(x, y), initVertexShape, initVertexIconPath, false); if (progressCanceled()) { return false; } // Sum of degrees used as denominator in attachment probability // Multiply by 2 for undirected graphs since edgesEnabled() counts each edge once sumDegrees = 2 * edgesEnabled(); newEdges = 0; // Repeat until this new node has attached to exactly m existing nodes for (;;) { for (int j = 0; j < i; ++j) { if (newEdges == m) break; k_j = vertexDegreeIn(j + 1); k_j = pow(k_j, power); // If no edges exist yet, connect with certainty; // otherwise use preferential attachment probability if (sumDegrees < 1) prob_j = 1; else prob_j = (alpha + k_j) / sumDegrees; prob = (rand() % 100 + 1) / 100.0; if (prob <= prob_j) { if (mode == "graph") { edgeCreate(i + 1, j + 1, 1, initEdgeColor, EdgeType::Undirected, false, false, QString(), false); } else { edgeCreate(i + 1, j + 1, 1, initEdgeColor, EdgeType::Directed, true, false, QString(), false); } newEdges++; } } if (newEdges == m) break; } qCDebug(lcGenerators) << "Graph::randomNetScaleFreeCreate() -" << m << "edges attached for node" << i + 1; } relationCurrentRename(tr("scale-free"), true); qCDebug(lcGenerators) << "Graph::randomNetScaleFreeCreate() - finished."; setModStatus(ModStatus::VertexEdgeCount); emit signalLayoutFinished(); return true; } /** * @brief Creates a Watts–Strogatz small-world random network. * * The algorithm works in two phases: * 1. Build a ring lattice of N nodes each connected to `degree` neighbours. * 2. Rewire each edge with probability `beta` to a randomly chosen node, * producing the characteristic short path lengths and high clustering. * * @param N Number of nodes. * @param degree Number of neighbours each node is initially connected to (must be even). * @param beta Rewiring probability in [0,1]. 0 = pure lattice, 1 = random graph. * @param mode "graph" for undirected, anything else for directed. * @return true on success, false if the user cancelled. */ bool Graph::randomNetSmallWorldCreate(const int &N, const int °ree, const double &beta, const QString &mode) { qCDebug(lcGenerators) << "Creating small-world random network. Vertices:" << N << "degree:" << degree << "beta:" << beta << "mode:" << mode; if (mode == "graph") { setDirected(false); } // Phase 1: build the underlying ring lattice. // We pass updateProgress=false so the lattice builder does not create its // own progress dialog β€” we own the single dialog for the whole operation. if (!randomNetRingLatticeCreate(N, degree, false)) { return false; } // Phase 2: rewire edges with probability beta (Watts-Strogatz). QString pMsg = tr("Creating Small-World Random Network. \n" "Please wait ..."); progressStatus(pMsg); qCDebug(lcGenerators) << "Rewiring starts..."; int candidate = 0; for (int i = 1; i < N; i++) { for (int j = i + 1; j < N; j++) { if (edgeExists(i, j)) { // Rewire this edge with probability beta if (rand() % 100 < (beta * 100)) { edgeRemove(i, j, true); // Find a valid rewiring target: not a self-loop, // not already a neighbour of i for (;;) { candidate = rand() % (N + 1); if (candidate == 0 || candidate == i) continue; if (edgeExists(i, candidate) != 0) continue; edgeCreate(i, candidate, 1, initEdgeColor, EdgeType::Undirected, false, false, QString(), false); break; } } } } if (progressCanceled()) { return false; } } relationCurrentRename(tr("small-world"), true); setModStatus(ModStatus::VertexEdgeCount); emit signalLayoutFinished(); return true; } /** * @brief Creates a pseudo-random d-regular network. * * Every node ends up with exactly `degree` neighbours. The algorithm: * 1. Build an initial ordered edge list where each node i connects to the * next degree/2 nodes (undirected) or degree nodes (directed). * 2. Repeatedly pick two edges at random and swap their endpoints, * ensuring no self-loops or duplicate edges result, until all edges * have been processed. This produces a random regular graph. * 3. Draw the resulting edges. * * @param N Number of nodes. * @param degree Desired degree of every node (must be even for undirected). * @param mode "graph" for undirected, anything else for directed. * @param diag Reserved, currently unused. * @return true on success, false if the user cancelled. */ bool Graph::randomNetRegularCreate(const int &N, const int °ree, const QString &mode, const bool &diag) { qCDebug(lcGenerators) << "Creating d-regular random network..." << "N" << N << "degree" << degree << "mode" << mode; Q_UNUSED(diag); if (mode == "graph") { setDirected(false); } int x = 0, y = 0; qreal progressCounter = 0; qreal progressFraction = (isUndirected()) ? 2 / (qreal)degree : 1 / (qreal)degree; int target = 0; QList m_edges; QStringList firstEdgeVertices, secondEdgeVertices, m_edge; QString firstEdge, secondEdge; randomizeThings(); vpos.reserve(N); QString pMsg = tr("Creating pseudo-random d-regular network. \n" "Please wait..."); progressStatus(pMsg); // Create all nodes for (int i = 0; i < N; i++) { x = canvasRandomX(); y = canvasRandomY(); vertexCreate( i + 1, initVertexSize, initVertexColor, initVertexNumberColor, initVertexNumberSize, QString::number(i + 1), initVertexLabelColor, initVertexLabelSize, QPoint(x, y), initVertexShape, initVertexIconPath, false); } // Build initial ordered edge list if (mode == "graph") { for (int i = 0; i < N; i++) { for (int j = 0; j < degree / 2; j++) { target = i + j + 1; if (target > (N - 1)) target = target - N; m_edges.append(QString::number(i + 1) + "->" + QString::number(target + 1)); } } } else { for (int i = 0; i < N; i++) { for (int j = 0; j < degree; j++) { target = i + j + 1; if (target > (N - 1)) target = target - N; m_edges.append(QString::number(i + 1) + "->" + QString::number(target + 1)); } } } qCDebug(lcGenerators) << "Edge list count:" << m_edges.size() << "Randomising by swapping edge endpoint pairs..."; // Randomise by repeatedly swapping endpoints of two randomly chosen edges, // ensuring the result has no self-loops or duplicate edges for (int i = 1; i < m_edges.size(); ++i) { firstEdgeVertices.clear(); secondEdgeVertices.clear(); firstEdgeVertices << "" << ""; secondEdgeVertices << "" << ""; while (firstEdgeVertices[0] == firstEdgeVertices[1] || firstEdgeVertices[0] == secondEdgeVertices[0] || firstEdgeVertices[0] == secondEdgeVertices[1] || firstEdgeVertices[1] == secondEdgeVertices[0] || firstEdgeVertices[1] == secondEdgeVertices[1] || secondEdgeVertices[0] == secondEdgeVertices[1] || m_edges.contains(firstEdgeVertices[0] + "->" + secondEdgeVertices[1]) || m_edges.contains(secondEdgeVertices[0] + "->" + firstEdgeVertices[1]) || (isUndirected() && m_edges.contains(secondEdgeVertices[1] + "->" + firstEdgeVertices[0])) || (isUndirected() && m_edges.contains(firstEdgeVertices[1] + "->" + secondEdgeVertices[0]))) { firstEdge = m_edges.at(rand() % m_edges.size()); firstEdgeVertices = firstEdge.split("->"); secondEdge = m_edges.at(rand() % m_edges.size()); secondEdgeVertices = secondEdge.split("->"); } m_edges.removeAll(firstEdge); m_edges.removeAll(secondEdge); m_edges.append(firstEdgeVertices[0] + "->" + secondEdgeVertices[1]); m_edges.append(secondEdgeVertices[0] + "->" + firstEdgeVertices[1]); } // Draw the randomised edges for (int i = 0; i < m_edges.size(); ++i) { m_edge = m_edges.at(i).split("->"); edgeCreate(m_edge[0].toInt(0), m_edge[1].toInt(0), 1, initEdgeColor, (isUndirected()) ? EdgeType::Undirected : EdgeType::Directed, (isUndirected()) ? false : true, false, QString(), false); progressCounter += progressFraction; if (fmod(progressCounter, 1.0) == 0) { if (progressCanceled()) { return false; } } } relationCurrentRename(tr("d-regular"), true); setModStatus(ModStatus::VertexEdgeCount); emit signalLayoutFinished(); return true; } /** * @brief Creates a random ring lattice network. * @param vert * @param degree * @param x0 * @param y0 * @param radius * @param updateProgress */ bool Graph::randomNetRingLatticeCreate(const int &N, const int °ree, const bool updateProgress) { qCDebug(lcGenerators) << "Creating ring lattice random network..."; int x = 0; int y = 0; double x0 = canvasWidth / 2.0; double y0 = canvasHeight / 2.0; double radius = canvasMaxRadius(); double rad = (2.0 * M_PI / N); setDirected(false); randomizeThings(); vpos.reserve(N); QString pMsg = tr("Creating ring-lattice network. \n" "Please wait..."); progressStatus(pMsg); for (int i = 0; i < N; i++) { x = x0 + radius * cos(i * rad); y = y0 + radius * sin(i * rad); vertexCreate(i + 1, initVertexSize, initVertexColor, initVertexNumberColor, initVertexNumberSize, QString::number(i + 1), initVertexLabelColor, initVertexLabelSize, QPoint(x, y), initVertexShape, initVertexIconPath, false); } int target = 0; for (int i = 0; i < N; i++) { for (int j = 0; j < degree / 2; j++) { target = i + j + 1; if (target > (N - 1)) target = target - N; edgeCreate(i + 1, target + 1, 1, initEdgeColor, EdgeType::Undirected, false, false, QString(), false); } if (progressCanceled()) { return false; } } // Always rename the relation, regardless of updateProgress (fix: was inside updateProgress guard) relationCurrentRename(tr("ring-lattice"), true); setModStatus(ModStatus::VertexEdgeCount, updateProgress); if (updateProgress) emit signalLayoutFinished(); return true; } /** * @brief Creates a lattice (mesh/grid) random network. * * Nodes are arranged in a lengthΓ—length grid. Each node connects to its * neighbours within a given neighbourhood distance in both horizontal and * vertical directions. The resulting network forms a regular tiling pattern. * * Note: the `dimension` and `circular` parameters are reserved for future * extension to higher-dimensional and toroidal lattices. * * @param N Total number of nodes (should equal length^2). * @param length Number of nodes along each dimension of the grid. * @param dimension Reserved for future use (higher-dimensional lattices). * @param neighborhoodLength Distance within which nodes are connected. * @param mode "graph" for undirected, anything else for directed. * @param circular Reserved for future use (toroidal/circular lattice). * @return true on success, false if the user cancelled. */ bool Graph::randomNetLatticeCreate(const int &N, const int &length, const int &dimension, const int &neighborhoodLength, const QString &mode, const bool &circular) { qCDebug(lcGenerators) << "Creating lattice network..." << "N" << N << "length" << length << "neighborhoodLength" << neighborhoodLength << "mode" << mode; Q_UNUSED(circular); Q_UNUSED(dimension); if (mode == "graph") { setDirected(false); } int x = 0; int y = 0; int nCount = 0; double nodeHPadding = 0; double nodeVPadding = 0; double canvasPadding = 100; qreal progressCounter = 0; qreal progressFraction = 0; int target = 0; QList latticeEdges; QStringList m_edge; QString edge; QString oppEdge; randomizeThings(); vpos.reserve(N); QString pMsg = tr("Creating lattice network. \n" "Please wait..."); progressStatus(pMsg); // Create nodes arranged in a length x length grid nCount = 0; canvasPadding = 20; nodeHPadding = (canvasWidth) / (double)(length + 2); nodeVPadding = (canvasHeight) / (double)(length + 2); qCDebug(lcGenerators) << "Creating" << N << "vertices in a" << length << "x" << length << "grid - nodeHPadding" << nodeHPadding << "nodeVPadding" << nodeVPadding; for (int i = 0; i < length; i++) { y = canvasPadding + nodeVPadding * (i + 1); for (int j = 0; j < length; j++) { nCount++; x = canvasPadding + nodeHPadding * (j + 1); vertexCreate( nCount, initVertexSize, initVertexColor, initVertexNumberColor, initVertexNumberSize, QString::number(nCount), initVertexLabelColor, initVertexLabelSize, QPoint(x, y), initVertexShape, initVertexIconPath, false); } } // Compute the edge list: for each node, find all neighbours // within neighborhoodLength steps in both axes qCDebug(lcGenerators) << "Computing edges..."; if (mode == "graph") { for (int i = 1; i <= N; i++) { for (int j = 1; j < neighborhoodLength + 1; j++) { for (int p = 0; p < 2; p++) { for (int q = 0; q < 2; q++) { target = i + pow((-1), p) * j * pow(length, q); // Skip out-of-bounds: wrap-around on right edge if (i % length == 0 && target == i + 1) continue; // Skip out-of-bounds: wrap-around on left edge if (i % length == 1 && target == i - 1) continue; // Skip out-of-bounds: below grid if (target > N) { target = target % N; continue; } // Skip out-of-bounds: above grid if (target < 1) { target = N - target; continue; } // Add edge only once (undirected: skip if reverse already recorded) edge = QString::number(i) + "<->" + QString::number(target); oppEdge = QString::number(target) + "<->" + QString::number(i); if (!latticeEdges.contains(edge) && !latticeEdges.contains(oppEdge)) { latticeEdges.append(edge); } } } } } } else { // Directed lattice: reserved for future implementation } // Draw edges; progress tracks actual edge count for accuracy qCDebug(lcGenerators) << "Drawing" << latticeEdges.size() << "edges..."; progressFraction = (latticeEdges.size() > 0) ? 1.0 / (qreal)latticeEdges.size() : 1.0; for (int i = 0; i < latticeEdges.size(); ++i) { m_edge = latticeEdges.at(i).split("<->"); edgeCreate(m_edge[0].toInt(0), m_edge[1].toInt(0), 1, initEdgeColor, (isUndirected()) ? EdgeType::Undirected : EdgeType::Directed, (isUndirected()) ? false : true, false, QString(), false); progressCounter += progressFraction; if (fmod(progressCounter, 1.0) == 0) { if (progressCanceled()) { return false; } } } relationCurrentRename(tr("lattice"), true); setModStatus(ModStatus::VertexEdgeCount); emit signalLayoutFinished(); return true; } /** Calculates and returns x! factorial... used in (n 2)p edges calculation */ int Graph::factorial(int x) { int tmp; if (x <= 1) return 1; tmp = x * factorial(x - 1); return tmp; } SocNetV-3.7/src/graph/io/000077500000000000000000000000001524402210300151745ustar00rootroot00000000000000SocNetV-3.7/src/graph/io/graph_io.cpp000066400000000000000000001462631524402210300175040ustar00rootroot00000000000000/** * @file graph_io.cpp * @brief Implements graph file load/save wrapper methods for the Graph class * (load pipeline coordination, parser thread termination, format writers). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include #include #include #include #include #include #include #include "graph/io/graph_parse_sink_graph.h" #include /** * @brief Loads a graph from a given file. * * It creates a new Parser object, moves it to a another thread, * connects signals and slots and calls its run() method. * * @param fileName * @param codecName * @param m_showLabels * @param maxWidth * @param maxHeight * @param fileFormat * @param sm_two_mode * @return */ void Graph::loadFile(const QString fileName, const QString codecName, const int fileFormat, const QString delimiter, const int sm_two_mode, const bool sm_has_labels) { qCDebug(lcGraphIO) << "Loading the file:" << fileName; qCDebug(lcGraphIO) << "First, clearing current relations..."; relationsClear(); qCDebug(lcGraphIO) << "Next, creating new file_parser -- we are on thread:" << this->thread(); file_parser = new Parser(); qCDebug(lcGraphIO) << " moving parser to her own new thread..."; file_parser->moveToThread(&file_parserThread); qCDebug(lcGraphIO) << "file_parser thread now: " << file_parser->thread(); qCDebug(lcGraphIO) << "connecting file_parser signals..."; connect(&file_parserThread, &QThread::finished, file_parser, &QObject::deleteLater); // NEW: GUI uses sink-backed mutation (Parser owns the sink for thread-safe lifetime) file_parser->setOwnedParseSink( std::make_unique(*this)); connect(file_parser, &Parser::finished, this, &Graph::graphLoadedTerminateParserThreads); qCDebug(lcGraphIO) << "Starting parser thread..."; file_parserThread.start(); qCDebug(lcGraphIO) << "Calling the file_parser to load the file..."; file_parser->load( fileName, codecName, initVertexSize, initVertexColor, initVertexShape, initVertexNumberColor, initVertexNumberSize, initVertexLabelColor, initVertexLabelSize, initEdgeColor, canvasWidth, canvasHeight, fileFormat, delimiter, sm_two_mode, sm_has_labels); } /** * @brief Graph::graphLoadedTerminateParserThreads * @param reason */ void Graph::graphLoadedTerminateParserThreads(QString reason) { qCDebug(lcGraphIO) << "Terminating parser threads - reason " << reason << " Checking if file_parserThread is running..."; if (file_parserThread.isRunning()) { qCDebug(lcGraphIO) << "deleting file_parser pointer"; delete file_parser; file_parser = 0; // see why here: https://goo.gl/tQxpGA qCDebug(lcGraphIO) << "file_parserThread running." "Calling file_parserThread.quit();"; file_parserThread.quit(); } } /** * @brief Stores loaded file name, graph name, sets edge direction type and signals MW to update the UI * * Called from Parser when file parsing ends. * * @param fileType * @param fileName * @param netName * @param totalNodes * @param totalLinks * @param edgeDirType * @param elapsedTime * @param message */ void Graph::graphFileLoaded(const int &fileType, const QString &fileName, const QString &netName, const int &totalNodes, const int &totalLinks, const int &edgeDirType, const qint64 &elapsedTime, const QString &message) { if (fileType == FileType::UNRECOGNIZED) { emit signalGraphLoaded(fileType, QString(), QString(), 0, 0, 0, elapsedTime, message); return; } qCDebug(lcGraphIO) << "Loaded file OK." << "type:" << fileType << "filename:" << fileName << "nodes:" << totalNodes << "links (parser count):" << totalLinks << "edgeDirType:" << edgeDirType << "isDirected (currently):" << isDirected(); setFileName(fileName); if (!netName.isEmpty()) setName(netName); else setName((fileName.split("/").last()).split("/").first()); if (edgeDirType == EdgeType::Directed) this->setDirected(true); else this->setDirected(false); setFileFormat(fileType); setModStatus(ModStatus::SavedUnchanged); qCDebug(lcGraphIO) << "graphFileLoaded: after setDirected/setUndirected:" << "isDirected" << isDirected() << "isUndirected" << isUndirected() << "m_totalEdges" << m_totalEdges << "calculatedEdges" << calculatedEdges; // Do not trust totalLinks from the parser. // // The parser accumulates totalLinks naively per section: each *Arcs line // adds 1, each *Edges line adds 2. In mixed Pajek files where the same // node pair appears in both *Arcs and *Edges sections, those pairs are // counted twice, inflating the total. (See issue #183.) // // edgesEnabled() counts from the actual adjacency (outEdgesCount() per // vertex) which is always correct regardless of file format or section // overlap. calculatedEdges is false at this point (invalidated by each // edgeCreate() call during parsing via setModStatus(EdgeCount)), so // edgesEnabled() will perform a fresh recount here. int actualLinks = edgesEnabled(); if (actualLinks != totalLinks) { qCDebug(lcGraphIO) << "WARNING: Parser totalLinks" << totalLinks << "differs from actual adjacency count" << actualLinks << "- using actual count." << "This typically indicates overlapping *Arcs/*Edges pairs" << "in a mixed Pajek file (see issue #183)."; } emit signalGraphLoaded(fileType, fileName, getName(), totalNodes, actualLinks, graphDensity(), elapsedTime, message); } /** * @brief Saves the current graph to a file. * * Checks the requested file type and calls the corresponding saveGraphTo...() method * * @param fileName * @param fileType * */ void Graph::saveToFile(const QString &fileName, const int &fileType, const bool &saveEdgeWeights, const bool &saveZeroWeightEdges) { qCDebug(lcGraphIO) << "Saving current graph to file named:" << fileName; bool saved = false; switch (fileType) { case FileType::PAJEK: { saved = saveToPajekFormat(fileName, getName(), canvasWidth, canvasHeight); break; } case FileType::ADJACENCY: { saved = saveToAdjacencyFormat(fileName, saveEdgeWeights); break; } case FileType::GRAPHVIZ: { saved = saveToDotFormat(fileName); break; } case FileType::GRAPHML: { saved = saveToGraphMLFormat(fileName, saveZeroWeightEdges); break; } case FileType::UCINET: { saved = saveToUCINETDLFormat(fileName); break; } case FileType::EDGELIST_WEIGHTED: { saved = saveToEdgeListWeightedFormat(fileName); break; } case FileType::EDGELIST_SIMPLE: { saved = saveToEdgeListSimpleFormat(fileName); break; } default: { setFileFormat(FileType::UNRECOGNIZED); break; } }; if (saved) { setModStatus(ModStatus::SavedUnchanged); } else { emit signalGraphSavedStatus(FileType::UNRECOGNIZED); } } /** * @brief Save the current graph to a Pajek (.paj) file. * * Writes: * - `*Network ` * - `*Vertices N` with labels, colors, positions, and shapes (as available) * - Edges either as: * - single-relation: `*Arcs` + `*Edges` sections (legacy behavior), or * - multi-relation: one `*Matrix` block per relation. * * Relation header export (multi-relation): * - Canonical matrix header syntax: * - `*Matrix :k` * - `*Matrix :k "Label"` * - If the relation label is empty/unlabeled, no quotes are emitted. * - Labels are normalized to avoid polluted forms like `9 'star'`: * - strips wrapping quotes * - strips a leading index token (e.g. `9`, `9:`) * - escapes internal quotes per Pajek convention (`"` -> `""`) * * Notes: * - Numeric output is written using the C locale for stable decimal formatting. * - `maxWidth`/`maxHeight` are used to scale vertex coordinates to Pajek's * normalized coordinate space. If 0, current canvas dimensions are used. * - The active relation is restored before returning. * - On success, updates the graph’s file name and file format metadata. * * @param fileName Output file path (should end in .paj). * @param networkName Optional exported network name. If empty, uses graph name. * If "unnamed", derives from file name without extension. * @param maxWidth Canvas width used to normalize X coordinates (0 => use current). * @param maxHeight Canvas height used to normalize Y coordinates (0 => use current). * * @return true on success, false if the file cannot be opened/written. */ bool Graph::saveToPajekFormat(const QString &fileName, QString networkName, int maxWidth, int maxHeight) { qCDebug(lcGraphIO) << "Saving graph to Pajek-formatted file:" << fileName; qreal weight = 0; QFileInfo fileInfo(fileName); QString fileNameNoPath = fileInfo.fileName(); networkName = (networkName == "") ? getName().toHtmlEscaped() : networkName; networkName = (networkName == "unnamed") ? fileNameNoPath.toHtmlEscaped().left(fileNameNoPath.lastIndexOf('.')) : networkName; maxWidth = (maxWidth == 0) ? canvasWidth : maxWidth; maxHeight = (maxHeight == 0) ? canvasHeight : maxHeight; QFile f(fileName); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcGraphIO) << "Could not open (for writing) file:" << fileName; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream t(&f); // Keep numeric output stable-ish across locales. t.setLocale(QLocale::c()); t.setRealNumberNotation(QTextStream::SmartNotation); t.setRealNumberPrecision(12); t << "*Network " << networkName << "\n"; // ---- Vertices (once) ---- t << "*Vertices " << vertices() << "\n"; for (VList::const_iterator it = m_graph.cbegin(); it != m_graph.cend(); ++it) { t << (*it)->number() << " " << "\"" << (*it)->label() << "\""; t << " ic " << (*it)->colorToPajek(); t << "\t\t" << (*it)->x() / (maxWidth) << " \t" << (*it)->y() / (maxHeight); t << "\t" << (*it)->shape(); t << "\n"; } // ---- Relations ---- const int relCount = relations(); const int originalRel = relationCurrent(); /** * @brief Returns a canonical label for Pajek *Matrix headers (no surrounding quotes). * * Pajek *Matrix Header Export Contract * * EXPORT IS STRICT AND CANONICAL. * * Multirelational headers MUST be written in exactly one of these forms: * * *Matrix :k * *Matrix :k "Label" * * Where: * - k is the 1-based relation index. * - No leading index is embedded in the label. * - No empty quotes are emitted. * - No alternate forms like "*Matrix k:", "*Matrix k: """, etc. * * Relation labels are normalized before export: * - Strip wrapping quotes. * - Strip leading index pollution (e.g. "9 'star'" β†’ "star"). * - Treat empty or index-only labels as unlabeled. * - Escape internal double quotes as "" (Pajek rule). * * IMPORT remains tolerant. EXPORT remains canonical. */ auto canonicalPajekRelationName = [](QString s, int k) -> QString { s = s.trimmed(); const QString kStr = QString::number(k); auto stripWrapQuotes = [](QString x) -> QString { x = x.trimmed(); if (x.size() >= 2) { const QChar a = x.front(); const QChar b = x.back(); if ((a == '"' && b == '"') || (a == '\'' && b == '\'')) x = x.mid(1, x.size() - 2).trimmed(); } return x; }; s = stripWrapQuotes(s); // Remove leading ":k" if (s.startsWith(':')) s = s.mid(1).trimmed(); // Remove leading "k" or "k:" if (s.startsWith(kStr)) { s = s.mid(kStr.size()).trimmed(); if (s.startsWith(':')) s = s.mid(1).trimmed(); s = stripWrapQuotes(s); } // Unlabeled cases if (s.isEmpty() || s == kStr) return QString(); // Escape internal quotes per Pajek rules s.replace("\"", "\"\""); return s.trimmed(); }; // Multirelational: write Pajek *Matrix blocks per relation (like Padgett, etc.) if (relCount > 1) { for (int r = 0; r < relCount; ++r) { relationSet(r, false /*updateUI*/); const int k = r + 1; const QString relLabel = canonicalPajekRelationName(relationCurrentName(), k); // Canonical header: // *Matrix :k // *Matrix :k "label" t << "*Matrix :" << k; if (!relLabel.isEmpty()) t << " \"" << relLabel << "\""; t << "\n"; // Emit N x N matrix for (VList::const_iterator it = m_graph.cbegin(); it != m_graph.cend(); ++it) { const int i = (*it)->number(); bool first = true; for (VList::const_iterator jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { const int j = (*jt)->number(); if (isDirected()) weight = edgeExists(i, j); // directed lookup else weight = edgeExists(i, j, true); // undirected lookup // Pajek expects space-separated row entries if (!first) t << " "; first = false; // Print 0 for no edge. Otherwise print numeric weight. // (Unweighted relations usually store 1.) if (weight == 0) t << "0"; else t << weight; } t << "\n"; } } } else { // Single relation: keep existing arcs/edges output to minimize behavior change. t << "*Arcs \n"; for (VList::const_iterator it = m_graph.cbegin(); it != m_graph.cend(); ++it) { for (VList::const_iterator jt = m_graph.begin(); jt != m_graph.end(); jt++) { if ((weight = edgeExists((*it)->number(), (*jt)->number())) != 0 && (edgeExists((*jt)->number(), (*it)->number())) != weight) { t << (*it)->number() << " " << (*jt)->number() << " " << weight; t << " c " << (*it)->outLinkColor((*jt)->number()); t << "\n"; } } } t << "*Edges \n"; for (VList::const_iterator it = m_graph.cbegin(); it != m_graph.cend(); ++it) { for (VList::const_iterator jt = m_graph.begin(); jt != m_graph.end(); jt++) { if ((weight = edgeExists((*it)->number(), (*jt)->number(), true)) != 0) { if ((*it)->number() > (*jt)->number()) continue; t << (*it)->number() << " " << (*jt)->number() << " " << weight; t << " c " << (*it)->outLinkColor((*jt)->number()); t << "\n"; } } } } // Restore original relation (important if caller expects it) if (originalRel >= 0 && originalRel < relCount) relationSet(originalRel, false); f.close(); setFileName(fileName); setFileFormat(FileType::PAJEK); progressStatus(tr("File %1 saved").arg(fileNameNoPath)); return true; } /** * @brief Saves the active graph to an adjacency-formatted file * * @param fileName * * @return bool */ bool Graph::saveToAdjacencyFormat(const QString &fileName, const bool &saveEdgeWeights) { qCDebug(lcGraphIO) << "Saving graph to adjacency-formatted file:" << fileName; QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcGraphIO) << "Could not open (for writing) file:" << fileName; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); writeMatrixAdjacencyTo(outText, saveEdgeWeights); file.close(); // Store the filename setFileName(fileName); // Store the file format setFileFormat(FileType::ADJACENCY); QString fileNameNoPath = fileName.split("/").last(); progressStatus(QString(tr("Adjacency matrix-formatted network saved into file %1")).arg(fileNameNoPath)); return true; } /** * @brief Saves the active graph (current relation) to a GraphViz DOT file. * * Produces a valid digraph or graph block. Node identifiers are n to * avoid issues with labels that contain spaces or special characters. All * standard visual properties (label, color, shape) and custom node/edge * attributes are written as DOT attribute lists so the file round-trips * through parseAsDot() without data loss. * * Only the currently active relation is exported. If the graph has multiple * relations the caller is responsible for warning the user before invoking * this method. * * @param fileName Destination file path. * @return true on success, false if the file could not be opened. */ bool Graph::saveToDotFormat(const QString &fileName) { qCDebug(lcGraphIO) << "Saving graph to GraphViz DOT file:" << fileName; QFile f(fileName); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcGraphIO) << "Could not open (for writing) file:" << fileName; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream t(&f); t.setLocale(QLocale::c()); t.setRealNumberNotation(QTextStream::SmartNotation); t.setRealNumberPrecision(6); const QString graphKeyword = isDirected() ? "digraph" : "graph"; const QString edgeOp = isDirected() ? " -> " : " -- "; QString netName = getName(); netName.replace('"', '\''); t << graphKeyword << " \"" << netName << "\" {\n"; t << " graph [label=\"" << netName << "\"];\n\n"; // Sanitise a custom attribute key to a valid DOT identifier. auto sanitiseKey = [](const QString &k) -> QString { QString s = k; s.replace(QRegularExpression("[^A-Za-z0-9_]"), "_"); if (!s.isEmpty() && s[0].isDigit()) s.prepend('_'); return s; }; // Escape a value string for DOT double-quoted literals. auto escapeVal = [](const QString &v) -> QString { QString s = v; s.replace('\\', "\\\\"); s.replace('"', "\\\""); return s; }; const bool hasVertexAttrs = !graphHasVertexCustomAttributes().isEmpty(); const bool hasEdgeAttrs = !graphHasEdgeCustomAttributes().isEmpty(); const int activeRel = relationCurrent(); // ---- Nodes ---------------------------------------------------------------- for (VList::const_iterator it = m_graph.cbegin(); it != m_graph.cend(); ++it) { GraphVertex *v = *it; t << " n" << v->number() << " [label=\"" << escapeVal(v->label()) << "\"" << ", color=\"" << escapeVal(v->color()) << "\"" << ", shape=\"" << escapeVal(v->shape()) << "\"" << ", pos=\"" << v->x() << "," << v->y() << "\""; if (hasVertexAttrs) { const QHash attrs = v->customAttributes(); for (auto ai = attrs.constBegin(); ai != attrs.constEnd(); ++ai) t << ", " << sanitiseKey(ai.key()) << "=\"" << escapeVal(ai.value()) << "\""; } t << "];\n"; } t << "\n"; // ---- Edges (active relation only) ----------------------------------------- for (VList::const_iterator it = m_graph.cbegin(); it != m_graph.cend(); ++it) { GraphVertex *sv = *it; const H_edges &oe = sv->outEdges(); for (auto ei = oe.constBegin(); ei != oe.constEnd(); ++ei) { const int tgt = ei.key(); const int relIdx = ei.value().first; const qreal weight = ei.value().second.first; const bool enabled = ei.value().second.second; if (!enabled || relIdx != activeRel) continue; // For undirected graphs each edge is stored in both directions; // only emit the lower-numbered endpoint to avoid duplicates. if (!isDirected() && sv->number() > tgt) continue; t << " n" << sv->number() << edgeOp << "n" << tgt << " [weight=" << weight << ", label=\"" << escapeVal(sv->outEdgeLabel(tgt)) << "\"" << ", color=\"" << escapeVal(sv->outLinkColor(tgt)) << "\""; if (hasEdgeAttrs) { const QHash eAttrs = sv->outEdgeCustomAttributes(tgt); for (auto ai = eAttrs.constBegin(); ai != eAttrs.constEnd(); ++ai) t << ", " << sanitiseKey(ai.key()) << "=\"" << escapeVal(ai.value()) << "\""; } t << "];\n"; } } t << "}\n"; f.close(); setFileName(fileName); setFileFormat(FileType::GRAPHVIZ); progressStatus(tr("File %1 saved").arg(QFileInfo(fileName).fileName())); return true; } /** * @brief Saves the current graph to a GraphML-formatted file * * @param fileName * @param networkName * @param maxWidth * @param maxHeight * * @return bool */ bool Graph::saveToGraphMLFormat(const QString &fileName, const bool &saveZeroWeightEdges, QString networkName, int maxWidth, int maxHeight) { qCDebug(lcGraphIO) << "Saving graph to GraphML-formatted file:" << fileName; qreal weight = 0; int source = 0, target = 0, edgeCount = 0, m_size = 1, m_labelSize; QString m_color, m_labelColor, m_label; bool openToken; QFileInfo fileInfo(fileName); QString fileNameNoPath = fileInfo.fileName(); QString saveDirPath = fileInfo.canonicalPath(); QString iconsSubDir = fileInfo.baseName() + "_" + fileInfo.suffix() + "_images"; QString iconsDirPath = saveDirPath + "/" + iconsSubDir; QDir saveDir(saveDirPath); qreal rel_coord_x = 0; qreal rel_coord_y = 0; // Check if there are nodes with custom icons in the network if (graphHasVertexCustomIcons()) { qCDebug(lcGraphIO) << "Custom node icons exist." << "Creating images subdir" << iconsDirPath; // There are custom node icons in this net. // We need to save these custom icons to a folder // Create a subdir inside the directory where the actual network file // is about to be saved. All custom icons will be copied one-by-one there. if (saveDir.mkpath(iconsDirPath)) { qCDebug(lcGraphIO) << "created icons subdir" << iconsDirPath; } else { qCDebug(lcGraphIO) << "ERROR creating subdir!"; } } else { qCDebug(lcGraphIO) << "No custom node icons. Nothing to do"; } QString iconPath = QString(); QString iconFileName = QString(); QString copyIconFileNamePath = QString(); // Init custom attributes lists and temp hashes QStringList vertexCustomAttributesList = graphHasVertexCustomAttributes(); QHash m_vertexCustomAttributes = QHash(); QStringList edgeCustomAttributesList = graphHasEdgeCustomAttributes(); QHash m_edgeCustomAttributes = QHash(); networkName = (networkName == "") ? getName().toHtmlEscaped() : networkName; networkName = (networkName == "unnamed") ? fileNameNoPath.toHtmlEscaped().left(fileNameNoPath.lastIndexOf('.')) : networkName; qCDebug(lcGraphIO) << "file:" << fileName.toUtf8() << "networkName" << networkName; maxWidth = (maxWidth == 0) ? (int)canvasWidth : maxWidth; maxHeight = (maxHeight == 0) ? (int)canvasHeight : maxHeight; QFile f(fileName); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcGraphIO) << "Could not open (for writing) file:" << fileName; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&f); QString outTextEncoding = QStringEncoder(outText.encoding()).name(); qCDebug(lcGraphIO) << "Using default codec for saving stream:" << outTextEncoding; qCDebug(lcGraphIO) << " writing xml version..."; outText << " \n"; outText << " \n"; outText << "" "\n"; qCDebug(lcGraphIO) << "writing keys..."; outText << " \n" " " " \n" " \n"; outText << " \n" " " << "0.0" << " \n" " \n"; outText << " \n" " " << "0.0" << " \n" " \n"; outText << " \n" " " << initVertexSize << " \n" " \n"; outText << " \n" " " << initVertexColor << " \n" " \n"; outText << " \n" " " << initVertexShape << " \n" " \n"; // Check if there are nodes with custom icons in this network if (graphHasVertexCustomIcons()) { // There are custom icons, so we will copy the default custom icon // to the subdir we created earlier iconPath = initVertexIconPath; iconFileName = QFileInfo(iconPath).fileName(); copyIconFileNamePath = iconsDirPath + "/" + iconFileName; if (!QFile(copyIconFileNamePath).exists()) { if (QFile::copy(iconPath, copyIconFileNamePath)) { qCDebug(lcGraphIO) << "default iconFile saved to:" << copyIconFileNamePath; } else { qCDebug(lcGraphIO) << "ERROR saving default iconFile to:" << copyIconFileNamePath; } } else { qCDebug(lcGraphIO) << "default iconFile already exists in:" << copyIconFileNamePath; } // And we write a new key (id 51) in our graphml for this default custom icon outText << " \n" " " << iconsSubDir + "/" + iconFileName << " \n" " \n"; } // end check if custom icons exist outText << " \n" " " << initVertexLabelColor << " \n" " \n"; outText << " \n" " " << initVertexLabelSize << " \n" " \n"; outText << " \n" " 1.0 \n" " \n"; outText << " \n" " " << initEdgeColor << " \n" " \n"; outText << " \n" " " << "" << " \n" " \n"; // Save custom node attribute key definitions, if any. if (!vertexCustomAttributesList.isEmpty()) { qCDebug(lcGraphIO) << "saving defaults for vertexCustomAttributesList:" << vertexCustomAttributesList; QString customVertexAttrId; for (qsizetype i = 0; i < vertexCustomAttributesList.size(); ++i) { customVertexAttrId = 'd' + QString::number(1000 + i); qCDebug(lcGraphIO) << "customVertexAttrId:" << customVertexAttrId << "customVertexAttr" << vertexCustomAttributesList.at(i); outText << " \n" " \n" " \n"; } } // Save custom edge attribute key definitions, if any (IDs: d2000+). if (!edgeCustomAttributesList.isEmpty()) { qCDebug(lcGraphIO) << "saving defaults for edgeCustomAttributesList:" << edgeCustomAttributesList; QString customEdgeAttrId; for (qsizetype i = 0; i < edgeCustomAttributesList.size(); ++i) { customEdgeAttrId = 'd' + QString::number(2000 + i); qCDebug(lcGraphIO) << "customEdgeAttrId:" << customEdgeAttrId << "customEdgeAttr" << edgeCustomAttributesList.at(i); outText << " \n" " \n" " \n"; } } VList::const_iterator it; VList::const_iterator jt; QString relationName; int relationPrevious = relationCurrent(); for (int i = 0; i < relations(); ++i) { relationName = (m_relationsList.at(i).simplified()).remove("\""); relationSet(i, false); qCDebug(lcGraphIO) << "writing graph tag. Relation:" << relationName; if (isUndirected()) outText << " \n"; else outText << " \n"; qCDebug(lcGraphIO) << "writing nodes data..."; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) continue; qCDebug(lcGraphIO) << "Node id:" << (*it)->number(); outText << " number() << "\"> \n"; m_color = (*it)->color(); m_size = (*it)->size(); m_labelSize = (*it)->labelSize(); m_labelColor = (*it)->labelColor(); m_label = (*it)->label(); m_label = htmlEscaped(m_label); m_vertexCustomAttributes = (*it)->customAttributes(); outText << " " << m_label << "\n"; rel_coord_x = (*it)->x() / (maxWidth); rel_coord_y = (*it)->y() / (maxHeight); outText << " " << rel_coord_x << "\n"; outText << " " << rel_coord_y << "\n"; if (initVertexSize != m_size) { outText << " " << m_size << "\n"; } if (QString::compare(initVertexColor, m_color, Qt::CaseInsensitive) != 0) { outText << " " << m_color << "\n"; } outText << " " << (*it)->shape() << "\n"; if ((*it)->shape() == "custom") { iconPath = (*it)->shapeIconPath(); iconFileName = QFileInfo(iconPath).fileName(); copyIconFileNamePath = iconsDirPath + "/" + iconFileName; if (!QFile(copyIconFileNamePath).exists()) { if (QFile::copy(iconPath, copyIconFileNamePath)) { qCDebug(lcGraphIO) << "iconFile for node:" << (*it)->number() << "saved to:" << copyIconFileNamePath; } else { qCDebug(lcGraphIO) << "ERROR saving iconFile for" << (*it)->number() << "saved to: " << copyIconFileNamePath; } } else { qCDebug(lcGraphIO) << "iconFile for node:" << (*it)->number() << "already exists in:" << copyIconFileNamePath; } outText << " " << iconsSubDir + "/" + iconFileName << "\n"; } if (QString::compare(initVertexLabelColor, m_labelColor, Qt::CaseInsensitive) != 0) { outText << " " << m_labelColor << "\n"; } if (initVertexLabelSize != m_labelSize) { outText << " " << m_labelSize << "\n"; } qCDebug(lcGraphIO) << "m_vertexCustomAttributes:" << m_vertexCustomAttributes; if (!m_vertexCustomAttributes.isEmpty()) { QString customVertexAttrId; for (auto cit = m_vertexCustomAttributes.cbegin(), end = m_vertexCustomAttributes.cend(); cit != end; ++cit) { int attrIndex = vertexCustomAttributesList.indexOf(cit.key()); if (attrIndex < 0) continue; // key not in global list, skip customVertexAttrId = 'd' + QString::number(1000 + attrIndex); outText << " " << cit.value() << "\n"; } } outText << " \n"; } qCDebug(lcGraphIO) << "writing edges data..."; edgeCount = 0; if (isDirected()) { for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { for (jt = m_graph.cbegin(); jt != m_graph.cend(); jt++) { source = (*it)->number(); target = (*jt)->number(); m_label = ""; // Check if user opted to save zero-weight edges if (saveZeroWeightEdges) { weight = this->edgeExistsVirtual(source, target); } else { weight = this->edgeExists(source, target); } if ((!saveZeroWeightEdges && weight != 0) || (saveZeroWeightEdges && weight != RAND_MAX)) { ++edgeCount; m_color = (*it)->outLinkColor(target); m_label = edgeLabel(source, target); m_label = htmlEscaped(m_label); outText << " \n"; outText << " " << weight << "" << " \n"; openToken = false; } if (QString::compare(initEdgeColor, m_color, Qt::CaseInsensitive) != 0) { if (openToken) outText << "> \n"; outText << " " << m_color << "" << " \n"; openToken = false; } if (!m_label.isEmpty()) { if (openToken) outText << "> \n"; outText << " " << m_label << "" << " \n"; openToken = false; } if (!edgeCustomAttributesList.isEmpty()) { m_edgeCustomAttributes = edgeCustomAttributes(source, target); if (!m_edgeCustomAttributes.isEmpty()) { if (openToken) outText << "> \n"; openToken = false; QString customEdgeAttrId; for (auto cit = m_edgeCustomAttributes.cbegin(); cit != m_edgeCustomAttributes.cend(); ++cit) { int attrIndex = edgeCustomAttributesList.indexOf(cit.key()); if (attrIndex < 0) continue; customEdgeAttrId = 'd' + QString::number(2000 + attrIndex); outText << " " << cit.value() << "\n"; } } } if (openToken) outText << "/> \n"; else outText << " \n"; } } } } else { for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { for (jt = it; jt != m_graph.end(); jt++) { source = (*it)->number(); target = (*jt)->number(); m_label = ""; // Check if user opted to save zero-weight edges if (saveZeroWeightEdges) { weight = this->edgeExistsVirtual(source, target); } else { weight = this->edgeExists(source, target); } if ((!saveZeroWeightEdges && weight != 0) || (saveZeroWeightEdges && weight != RAND_MAX)) { ++edgeCount; m_color = (*it)->outLinkColor(target); m_label = edgeLabel(source, target); m_label = htmlEscaped(m_label); outText << " \n"; outText << " " << weight << "" << " \n"; openToken = false; } if (QString::compare(initEdgeColor, m_color, Qt::CaseInsensitive) != 0) { if (openToken) outText << "> \n"; outText << " " << m_color << "" << " \n"; openToken = false; } if (!m_label.isEmpty()) { if (openToken) outText << "> \n"; outText << " " << m_label << "" << " \n"; openToken = false; } if (!edgeCustomAttributesList.isEmpty()) { m_edgeCustomAttributes = edgeCustomAttributes(source, target); if (!m_edgeCustomAttributes.isEmpty()) { if (openToken) outText << "> \n"; openToken = false; QString customEdgeAttrId; for (auto cit = m_edgeCustomAttributes.cbegin(); cit != m_edgeCustomAttributes.cend(); ++cit) { int attrIndex = edgeCustomAttributesList.indexOf(cit.key()); if (attrIndex < 0) continue; customEdgeAttrId = 'd' + QString::number(2000 + attrIndex); outText << " " << cit.value() << "\n"; } } } if (openToken) outText << "/> \n"; else outText << " \n"; } } } } outText << " \n"; } outText << "\n"; f.close(); relationSet(relationPrevious, false); // Store the filename setFileName(fileName); // Store the file format setFileFormat(FileType::GRAPHML); progressStatus(tr("File %1 saved").arg(fileNameNoPath)); return true; } /** * @brief Saves the current graph to a UCINET DL file (FULLMATRIX format). * * Emits one header block followed by all relation matrices back-to-back in the * DATA section. The parser advances its relation counter whenever the source * row counter exceeds N, so no separator is needed between matrices. * * Single relation: N=, no NM or LEVEL LABELS. * Multi-relation: N= NM=, LEVEL LABELS block with relation names. * * Node labels are always written in ROW LABELS / COLUMN LABELS sections. * Edge weights are written as-is (0 for absent edges). * * @param fileName Destination file path. * @return true on success, false if the file cannot be opened. */ bool Graph::saveToUCINETDLFormat(const QString &fileName) { qCDebug(lcGraphIO) << "Saving graph to UCINET DL file:" << fileName; QFile f(fileName); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcGraphIO) << "Could not open (for writing) file:" << fileName; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream t(&f); t.setLocale(QLocale::c()); t.setRealNumberNotation(QTextStream::SmartNotation); t.setRealNumberPrecision(12); const int n = vertices(); const int nm = relations(); const int orig = relationCurrent(); t << "DL\n"; if (nm > 1) t << "N=" << n << " NM=" << nm << "\n"; else t << "N=" << n << "\n"; t << "FORMAT = FULLMATRIX DIAGONAL PRESENT\n"; // LEVEL LABELS (relation names) β€” only for multi-relation if (nm > 1) { t << "LEVEL LABELS:\n"; for (int r = 0; r < nm; ++r) { relationSet(r, false); t << relationCurrentName() << "\n"; } relationSet(orig, false); } // ROW LABELS (node labels) t << "ROW LABELS:\n"; for (VList::const_iterator it = m_graph.cbegin(); it != m_graph.cend(); ++it) t << (*it)->label() << "\n"; // COLUMN LABELS (same as row for square matrices) t << "COLUMN LABELS:\n"; for (VList::const_iterator it = m_graph.cbegin(); it != m_graph.cend(); ++it) t << (*it)->label() << "\n"; // DATA: all NM matrices back-to-back t << "DATA:\n"; for (int r = 0; r < nm; ++r) { relationSet(r, false); for (VList::const_iterator it = m_graph.cbegin(); it != m_graph.cend(); ++it) { const int i = (*it)->number(); bool first = true; for (VList::const_iterator jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { const int j = (*jt)->number(); qreal w = 0; if (isDirected()) w = edgeExists(i, j); else w = edgeExists(i, j, true); if (!first) t << " "; first = false; if (w == 0) t << "0"; else t << w; } t << "\n"; } } f.close(); if (orig >= 0 && orig < nm) relationSet(orig, false); setFileName(fileName); setFileFormat(FileType::UCINET); progressStatus(tr("File %1 saved").arg(QFileInfo(fileName).fileName())); return true; } /** * @brief Saves the active relation to a weighted edge list file. * * Format: one edge per line β€” "source target weight" β€” using the node label * as identifier (spaces in labels replaced with underscores). A comment * header line describes the columns. * * Only the currently active relation is exported. * * @param fileName Destination file path. * @return true on success, false if the file cannot be opened. */ bool Graph::saveToEdgeListWeightedFormat(const QString &fileName) { qCDebug(lcGraphIO) << "Saving graph to weighted edge list file:" << fileName; QFile f(fileName); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcGraphIO) << "Could not open (for writing) file:" << fileName; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream t(&f); t.setLocale(QLocale::c()); t.setRealNumberNotation(QTextStream::SmartNotation); t.setRealNumberPrecision(12); // Sanitise a node label for use as an edge-list identifier. auto sanitiseLabel = [](const QString &lbl) -> QString { QString s = lbl.trimmed(); s.replace(' ', '_'); return s; }; t << "# source target weight\n"; const int activeRel = relationCurrent(); for (VList::const_iterator it = m_graph.cbegin(); it != m_graph.cend(); ++it) { GraphVertex *sv = *it; const H_edges &oe = sv->outEdges(); for (auto ei = oe.constBegin(); ei != oe.constEnd(); ++ei) { const int tgt = ei.key(); const int relIdx = ei.value().first; const qreal weight = ei.value().second.first; const bool enabled = ei.value().second.second; if (!enabled || relIdx != activeRel) continue; if (!isDirected() && sv->number() > tgt) continue; t << sanitiseLabel(sv->label()) << " " << sanitiseLabel(vertexLabel(tgt)) << " " << weight << "\n"; } } f.close(); setFileName(fileName); setFileFormat(FileType::EDGELIST_WEIGHTED); progressStatus(tr("File %1 saved").arg(QFileInfo(fileName).fileName())); return true; } /** * @brief Saves the active relation to a simple (unweighted) edge list file. * * Format: one edge per line β€” "source target" β€” using the node label as * identifier (spaces in labels replaced with underscores). A comment header * line describes the columns. * * Only the currently active relation is exported. * * @param fileName Destination file path. * @return true on success, false if the file cannot be opened. */ bool Graph::saveToEdgeListSimpleFormat(const QString &fileName) { qCDebug(lcGraphIO) << "Saving graph to simple edge list file:" << fileName; QFile f(fileName); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcGraphIO) << "Could not open (for writing) file:" << fileName; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream t(&f); auto sanitiseLabel = [](const QString &lbl) -> QString { QString s = lbl.trimmed(); s.replace(' ', '_'); return s; }; t << "# source target\n"; const int activeRel = relationCurrent(); for (VList::const_iterator it = m_graph.cbegin(); it != m_graph.cend(); ++it) { GraphVertex *sv = *it; const H_edges &oe = sv->outEdges(); for (auto ei = oe.constBegin(); ei != oe.constEnd(); ++ei) { const int tgt = ei.key(); const int relIdx = ei.value().first; const bool enabled = ei.value().second.second; if (!enabled || relIdx != activeRel) continue; if (!isDirected() && sv->number() > tgt) continue; t << sanitiseLabel(sv->label()) << " " << sanitiseLabel(vertexLabel(tgt)) << "\n"; } } f.close(); setFileName(fileName); setFileFormat(FileType::EDGELIST_SIMPLE); progressStatus(tr("File %1 saved").arg(QFileInfo(fileName).fileName())); return true; } SocNetV-3.7/src/graph/io/graph_parse_sink.h000066400000000000000000000077461524402210300207020ustar00rootroot00000000000000/** * @file graph_parse_sink.h * @brief Transitional mutation sink interface for Parser -> Graph IO operations. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #pragma once #include #include #include namespace SocNetV::IO { /** * @brief Explicit mutation surface for parse-time graph construction. * * IMPORTANT (WS4/P3 guardrail): * Method signatures intentionally mirror Parser signals 1:1. * No cleanup, normalization, or semantic changes are permitted here. */ class IGraphParseSink { public: virtual ~IGraphParseSink() = default; // Mirrors: Parser::signalAddNewRelation(const QString&, const bool&) virtual void addNewRelation(const QString &relName, const bool &changeRelation = false) = 0; // Mirrors: Parser::signalSetRelation(int, const bool&) virtual void setRelation(int relNum, const bool &updateUI = true) = 0; // Mirrors: Parser::signalCreateNode(...) virtual void createNode(const int &num, const int &size, const QString &color, const QString &numColor, const int &numSize, const QString &label, const QString &lColor, const int &lSize, const QPointF &p, const QString &shape, const QString &iconPath = QString(), const bool &signalMW = false, const QHash nodeCustomAttributes = QHash()) = 0; // Mirrors: Parser::signalCreateNodeAtPosRandom(const bool&) virtual void createNodeAtPosRandom(const bool &signalMW = false) = 0; // Mirrors: Parser::signalCreateNodeAtPosRandomWithLabel(const int&, const QString&, const bool&) virtual void createNodeAtPosRandomWithLabel(const int &num, const QString &label, const bool &signalMW = false) = 0; // Mirrors: Parser::signalCreateEdge(...) virtual void createEdge(const int &source, const int &target, const qreal &weight, const QString &color, const int &edgeDirType, const bool &arrows, const bool &bezier, const QString &edgeLabel = QString(), const bool &signalMW = false, const QHash &edgeCustomAttributes = QHash()) = 0; // Mirrors: Parser::removeDummyNode(int) virtual void removeDummyNode(int num) = 0; // Mirrors: Parser::signalFileLoaded(...) virtual void fileLoaded(const int &fileType, const QString &fileName, const QString &netName, const int &totalNodes, const int &totalLinks, const int &edgeDirType, const qint64 &elapsedTime, const QString &message = QString()) = 0; }; } // namespace SocNetV::IOSocNetV-3.7/src/graph/io/graph_parse_sink_graph.cpp000066400000000000000000000115051524402210300224020ustar00rootroot00000000000000/** * @file graph_parse_sink_graph.cpp * @brief Graph-backed implementation of IGraphParseSink (Parser -> Graph bridge). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include "graph_parse_sink_graph.h" namespace SocNetV::IO { GraphParseSinkGraph::GraphParseSinkGraph(Graph &graph) : m_graph(graph) { } void GraphParseSinkGraph::addNewRelation(const QString &relName, const bool &changeRelation) { m_graph.relationAdd(relName, changeRelation); } void GraphParseSinkGraph::setRelation(int relNum, const bool &updateUI) { m_graph.relationSet(relNum, updateUI); } void GraphParseSinkGraph::createNode(const int &num, const int &size, const QString &color, const QString &numColor, const int &numSize, const QString &label, const QString &lColor, const int &lSize, const QPointF &p, const QString &shape, const QString &iconPath, const bool &signalMW, const QHash nodeCustomAttributes) { m_graph.vertexCreate(num, size, color, numColor, numSize, label, lColor, lSize, p, shape, iconPath, signalMW, nodeCustomAttributes); } void GraphParseSinkGraph::createNodeAtPosRandom(const bool &signalMW) { m_graph.vertexCreateAtPosRandom(signalMW); } void GraphParseSinkGraph::createNodeAtPosRandomWithLabel(const int &num, const QString &label, const bool &signalMW) { m_graph.vertexCreateAtPosRandomWithLabel(num, label, signalMW); } void GraphParseSinkGraph::createEdge(const int &source, const int &target, const qreal &weight, const QString &color, const int &edgeDirType, const bool &arrows, const bool &bezier, const QString &edgeLabel, const bool &signalMW, const QHash &edgeCustomAttributes) { m_graph.edgeCreate(source, target, weight, color, edgeDirType, arrows, bezier, edgeLabel, signalMW, edgeCustomAttributes); } void GraphParseSinkGraph::removeDummyNode(int num) { m_graph.vertexRemoveDummyNode(num); } void GraphParseSinkGraph::fileLoaded(const int &fileType, const QString &fileName, const QString &netName, const int &totalNodes, const int &totalLinks, const int &edgeDirType, const qint64 &elapsedTime, const QString &message) { m_graph.graphFileLoaded(fileType, fileName, netName, totalNodes, totalLinks, edgeDirType, elapsedTime, message); } } // namespace SocNetV::IOSocNetV-3.7/src/graph/io/graph_parse_sink_graph.h000066400000000000000000000062671524402210300220600ustar00rootroot00000000000000/** * @file graph_parse_sink_graph.h * @brief Graph-backed implementation of IGraphParseSink (Parser -> Graph bridge). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #pragma once #include "graph_parse_sink.h" class Graph; namespace SocNetV::IO { /** * @brief Forwards parse mutations directly into an existing Graph instance. * * NOTE: This is intentionally a thin adapter. No logic, no ordering changes. */ class GraphParseSinkGraph final : public IGraphParseSink { public: explicit GraphParseSinkGraph(Graph &graph); void addNewRelation(const QString &relName, const bool &changeRelation = false) override; void setRelation(int relNum, const bool &updateUI = true) override; void createNode(const int &num, const int &size, const QString &color, const QString &numColor, const int &numSize, const QString &label, const QString &lColor, const int &lSize, const QPointF &p, const QString &shape, const QString &iconPath = QString(), const bool &signalMW = false, const QHash nodeCustomAttributes = QHash()) override; void createNodeAtPosRandom(const bool &signalMW = false) override; void createNodeAtPosRandomWithLabel(const int &num, const QString &label, const bool &signalMW = false) override; void createEdge(const int &source, const int &target, const qreal &weight, const QString &color, const int &edgeDirType, const bool &arrows, const bool &bezier, const QString &edgeLabel = QString(), const bool &signalMW = false, const QHash &edgeCustomAttributes = QHash()) override; void removeDummyNode(int num) override; void fileLoaded(const int &fileType, const QString &fileName, const QString &netName, const int &totalNodes, const int &totalLinks, const int &edgeDirType, const qint64 &elapsedTime, const QString &message = QString()) override; private: Graph &m_graph; }; } // namespace SocNetV::IOSocNetV-3.7/src/graph/io/table_export.cpp000066400000000000000000000055661524402210300204040ustar00rootroot00000000000000/** * @file table_export.cpp * @brief Implements TableExport::toCSV() and TableExport::toJSON() (#226). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #include "table_export.h" #include #include #include #include #include #include #include namespace TableExport { QString csvQuote(const QString &value) { if (!value.contains(QLatin1Char(',')) && !value.contains(QLatin1Char('"')) && !value.contains(QLatin1Char('\n')) && !value.contains(QLatin1Char('\r'))) { return value; } QString escaped = value; escaped.replace(QLatin1String("\""), QLatin1String("\"\"")); return QLatin1Char('"') + escaped + QLatin1Char('"'); } bool toCSV(QAbstractItemModel *model, const QString &filePath) { if (!model) return false; QFile file(filePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return false; QTextStream out(&file); const int cols = model->columnCount(); const int rows = model->rowCount(); // Header row QStringList headerFields; headerFields.reserve(cols); for (int c = 0; c < cols; ++c) { headerFields << csvQuote( model->headerData(c, Qt::Horizontal, Qt::DisplayRole).toString()); } out << headerFields.join(QLatin1Char(',')) << QLatin1Char('\n'); // Data rows for (int r = 0; r < rows; ++r) { QStringList rowFields; rowFields.reserve(cols); for (int c = 0; c < cols; ++c) { rowFields << csvQuote( model->data(model->index(r, c), Qt::DisplayRole).toString()); } out << rowFields.join(QLatin1Char(',')) << QLatin1Char('\n'); } return true; } bool toJSON(QAbstractItemModel *model, const QString &filePath) { if (!model) return false; const int cols = model->columnCount(); const int rows = model->rowCount(); // Collect column headers once QStringList headers; headers.reserve(cols); for (int c = 0; c < cols; ++c) { headers << model->headerData(c, Qt::Horizontal, Qt::DisplayRole).toString(); } QJsonArray array; for (int r = 0; r < rows; ++r) { QJsonObject obj; for (int c = 0; c < cols; ++c) { obj.insert(headers.at(c), model->data(model->index(r, c), Qt::DisplayRole).toString()); } array.append(obj); } QFile file(filePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return false; file.write(QJsonDocument(array).toJson(QJsonDocument::Indented)); return true; } } // namespace TableExport SocNetV-3.7/src/graph/io/table_export.h000066400000000000000000000041551524402210300200420ustar00rootroot00000000000000/** * @file table_export.h * @brief Declares TableExport β€” CSV and JSON serialisers for QAbstractItemModel * (#226). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #pragma once class QAbstractItemModel; class QString; /** * @brief Free functions that serialise any QAbstractItemModel to CSV or JSON. * * Both functions operate on whatever rows and columns the model exposes β€” * pass a QSortFilterProxyModel to export only the currently visible rows, or * the raw source model to export everything. * * Constraint: QtCore only β€” no widgets, no UI signals. */ namespace TableExport { /** * @brief Returns a CSV-safe quoted field: wraps @p value in double-quotes if it * contains a comma, double-quote, or newline; embedded double-quotes are * escaped as "". * * Exposed so other CSV writers outside this module (e.g. Graph's report writers, * which build rows from data that never passes through a QAbstractItemModel) can * reuse the same escaping rules instead of reimplementing them. */ QString csvQuote(const QString &value); /** * @brief Writes @p model to an RFC 4180 CSV file at @p filePath. * * The first row is the column headers from headerData(). Each subsequent row * is one model row. Fields containing commas, double-quotes, or newlines are * enclosed in double-quotes; embedded double-quotes are escaped as "". * * @return true on success, false if the file could not be opened/written. */ bool toCSV(QAbstractItemModel *model, const QString &filePath); /** * @brief Writes @p model to a JSON file at @p filePath. * * Produces a top-level JSON array of objects. Each object has one key per * column, taken from headerData(), with the display-role cell value as the * string value. * * @return true on success, false if the file could not be opened/written. */ bool toJSON(QAbstractItemModel *model, const QString &filePath); } // namespace TableExport SocNetV-3.7/src/graph/io/table_import.cpp000066400000000000000000000075141524402210300203700ustar00rootroot00000000000000/** * @file table_import.cpp * @brief Implements TableImport::fromCSV() and TableImport::fromJSON() (#227). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #include "table_import.h" #include #include #include #include #include #include namespace TableImport { // Parses one CSV line, respecting RFC 4180 double-quote escaping. static QStringList parseCSVLine(const QString &line) { QStringList fields; QString field; bool inQuotes = false; for (int i = 0; i < line.size(); ++i) { const QChar c = line.at(i); if (inQuotes) { if (c == QLatin1Char('"')) { if (i + 1 < line.size() && line.at(i + 1) == QLatin1Char('"')) { field += QLatin1Char('"'); ++i; } else { inQuotes = false; } } else { field += c; } } else { if (c == QLatin1Char('"')) { inQuotes = true; } else if (c == QLatin1Char(',')) { fields << field; field.clear(); } else { field += c; } } } fields << field; return fields; } ParsedTable fromCSV(const QString &filePath) { ParsedTable result; QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { result.errorString = QStringLiteral("Cannot open file: %1").arg(filePath); return result; } QTextStream in(&file); bool headerRead = false; while (!in.atEnd()) { const QString line = in.readLine(); if (line.trimmed().isEmpty()) continue; const QStringList fields = parseCSVLine(line); if (!headerRead) { result.headers = fields; headerRead = true; } else { // Pad short rows so every row has the same column count QStringList row = fields; while (row.size() < result.headers.size()) row << QString(); result.rows << row; } } if (!headerRead || result.headers.isEmpty()) { result.errorString = QStringLiteral("Empty or malformed CSV file"); return result; } result.ok = true; return result; } ParsedTable fromJSON(const QString &filePath) { ParsedTable result; QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { result.errorString = QStringLiteral("Cannot open file: %1").arg(filePath); return result; } QJsonParseError parseError; const QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &parseError); if (parseError.error != QJsonParseError::NoError) { result.errorString = parseError.errorString(); return result; } if (!doc.isArray()) { result.errorString = QStringLiteral("Expected a JSON array of objects"); return result; } const QJsonArray array = doc.array(); if (array.isEmpty()) { result.errorString = QStringLiteral("Empty JSON array"); return result; } // Derive column order from the first object's keys result.headers = array.first().toObject().keys(); for (const QJsonValue &val : array) { const QJsonObject obj = val.toObject(); QStringList row; row.reserve(result.headers.size()); for (const QString &key : std::as_const(result.headers)) row << obj.value(key).toString(); result.rows << row; } result.ok = true; return result; } } // namespace TableImport SocNetV-3.7/src/graph/io/table_import.h000066400000000000000000000057061524402210300200360ustar00rootroot00000000000000/** * @file table_import.h * @brief Declares TableImport β€” CSV and JSON parsers that return a ParsedTable * suitable for attribute-import workflows (#227). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #pragma once #include #include /** * @brief Free functions that parse tabular files (CSV / JSON) into an * in-memory ParsedTable that can be fed to Graph::vertexAttributesImport() * or Graph::edgeAttributesImport(). * * Constraint: QtCore only β€” no widgets, no UI signals. */ namespace TableImport { /** * @brief In-memory representation of a parsed tabular file. * * @p headers Column names from the first CSV row or JSON object keys. * @p rows Data rows; each inner list has the same length as @p headers * (shorter rows are right-padded with empty strings during parsing). * @p ok True if parsing succeeded and at least one header was found. * @p errorString Human-readable failure reason when @p ok is false. */ struct ParsedTable { QStringList headers; QVector rows; bool ok = false; QString errorString; }; /** * @brief Parses a CSV file at @p filePath into a ParsedTable. * * The first non-empty line is treated as the header row. * Quoted fields (RFC 4180) are handled: embedded double-quotes are written * as two consecutive double-quotes (""). * * Node attributes example β€” ID column is "#", native columns (Label, Size, Color, * Shape) are routed to their setters; everything else becomes a custom attribute: * @code * #,Label,Shape,type,year_founded * 1,Alice,diamond,investor,2010 * 2,Bob,circle,founder,2018 * 3,Carol,box,advisor,2015 * @endcode * * Edge attributes example β€” Source/Target identify the edge, rest become attributes: * @code * Source,Target,relationship,weight * 1,2,invested_in,0.8 * 2,3,mentors,0.5 * 1,3,co_founded,1.0 * @endcode */ ParsedTable fromCSV(const QString &filePath); /** * @brief Parses a JSON file at @p filePath into a ParsedTable. * * Expects a top-level JSON array of objects β€” the format produced by * TableExport::toJSON(). Column order follows the key order of the * first object in the array. * * Node attributes example: * @code * [ * { "#": "1", "Label": "Alice", "type": "investor", "year_founded": "2010" }, * { "#": "2", "Label": "Bob", "type": "founder", "year_founded": "2018" } * ] * @endcode * * Edge attributes example: * @code * [ * { "Source": "1", "Target": "2", "relationship": "invested_in", "weight": "0.8" }, * { "Source": "2", "Target": "3", "relationship": "mentors", "weight": "0.5" } * ] * @endcode */ ParsedTable fromJSON(const QString &filePath); } // namespace TableImport SocNetV-3.7/src/graph/layouts/000077500000000000000000000000001524402210300162655ustar00rootroot00000000000000SocNetV-3.7/src/graph/layouts/graph_layouts_basic.cpp000066400000000000000000000534441524402210300230250ustar00rootroot00000000000000/** * @file graph_layouts_basic.cpp * @brief Implements basic graph layout algorithms of the Graph class (e.g., circular, random, grid and related positioning strategies). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include #include #include #include /** * @brief Repositions all nodes on random positions * Emits setNodePos(i, x,y) to tell GW that the node item should be moved. * @param maxWidth * @param maxHeight */ void Graph::layoutRandom() { qCDebug(lcLayouts) << "Graph::layoutRandom() "; double new_x = 0, new_y = 0; VList::const_iterator it; QString pMsg = tr("Embedding Random Layout. \n" "Please wait..."); progressStatus(pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { new_x = canvasRandomX(); new_y = canvasRandomY(); (*it)->setX(new_x); (*it)->setY(new_y); qCDebug(lcLayouts) << "Graph::layoutRandom() - " << " vertex " << (*it)->number() << " emitting setNodePos to new pos " << new_x << " , " << new_y; emit setNodePos((*it)->number(), new_x, new_y); } setModStatus(ModStatus::VertexPositions); emit signalLayoutFinished(); } /** * @brief Repositions all nodes on the periphery of * different circles with random radius * @param x0 * @param y0 * @param maxRadius */ void Graph::layoutRadialRandom(const bool &guides) { qCDebug(lcLayouts) << "Graph::layoutRadialRandom - "; double rad = 0, new_radius = 0, new_x = 0, new_y = 0; double i = 0; double x0 = canvasWidth / 2.0; double y0 = canvasHeight / 2.0; double maxRadius = canvasMaxRadius(); // offset controls how far from the centre the central nodes be positioned qreal offset = 0.06, randomDecimal = 0; VList::const_iterator it; int N = vertices(); QString pMsg = tr("Embedding Random Radial layout. \n" "Please wait ...."); progressStatus(pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { randomDecimal = (qreal)(rand() % 100) / 100.0; new_radius = (maxRadius - (randomDecimal - offset) * maxRadius); qCDebug(lcLayouts) << "Vertice " << (*it)->number() << " at x=" << (*it)->x() << ", y= " << (*it)->y() << ", maxradius " << maxRadius << " randomDecimal " << randomDecimal << " new radius " << new_radius; // Calculate new position rad = (2.0 * M_PI / N); new_x = x0 + new_radius * cos(i * rad); new_y = y0 + new_radius * sin(i * rad); (*it)->setX(new_x); (*it)->setY(new_y); qCDebug(lcLayouts, "Vertice will move to x=%f and y=%f ", new_x, new_y); // Move node to new position emit setNodePos((*it)->number(), new_x, new_y); i++; if (guides) { emit addGuideCircle(x0, y0, new_radius); } } setModStatus(ModStatus::VertexPositions); emit signalLayoutFinished(); } /** * @brief Repositions all vertices at random coordinates without emitting any signals. * * Used internally by force-directed layout algorithms (Eades, FR) to set up * initial particle positions before the iteration loop begins. Since the iteration * loop will immediately overwrite these positions and emits a bulk setNodePos pass * at the end, there is no need to signal the graphics scene here. * * For the standalone random layout action (with progress dialog and scene update), * use layoutRandom() instead. */ void Graph::layoutRandomInMemory() { qCDebug(lcLayouts) << "Graph::layoutRandomInMemory()"; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setX(canvasRandomX()); (*it)->setY(canvasRandomY()); } } /** * @brief Repositions all nodes on the periphery of a circle with given radius * @param x0 * @param y0 * @param maxRadius */ void Graph::layoutCircular(const double &x0, const double &y0, const double &newRadius, const bool &guides) { qCDebug(lcLayouts) << "Graph::layoutCircular - "; double rad = 0, new_x = 0, new_y = 0; double i = 0; VList::const_iterator it; int N = vertices(); QString pMsg = tr("Applying circular layout. \nPlease wait..."); progressStatus(pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { qCDebug(lcLayouts) << " vertex i" << (*it)->number() << " disabled. Continue"; continue; } // Calculate new position rad = (2.0 * M_PI / N); new_x = x0 + newRadius * cos(i * rad); new_y = y0 + newRadius * sin(i * rad); (*it)->setX(new_x); (*it)->setY(new_y); qCDebug(lcLayouts, "Vertice will move to x=%f and y=%f ", new_x, new_y); // Move node to new position emit setNodePos((*it)->number(), new_x, new_y); i++; if (guides) { emit addGuideCircle(x0, y0, newRadius); } } setModStatus(ModStatus::VertexPositions); } /** * @brief Convenience method * Changes the size of all nodes to be proportional to their outDegree (Degree Centrality) * Calls layoutByProminenceIndex */ void Graph::layoutVertexSizeByOutdegree() { layoutByProminenceIndex(1, 2); } /** * @brief Convenience method * Changes the size of all nodes to be proportional to their InDegree (Degree Prestige) * Calls layoutByProminenceIndex */ void Graph::layoutVertexSizeByIndegree() { layoutByProminenceIndex(10, 2); } /** * @brief Applies a layout according to each actor's prominence index score. * The layout type can be radial (0), level (1), node sizes (2) or node colors (3), as follows: * layoutType=0 - Repositions all nodes on the periphery of concentric circles with radius analogous to their prominence index * layoutType=1 - Repositions all nodes on different top-down levels according to their centrality * layoutType=2 - Changes node sizes to be proportional to their prominence index score * layoutType=2 - Changes node colors to reflect their prominence index score (from red to green) * @param prominenceIndex, 0-12 * @param layoutType: 0,1,2,3 * @param considerWeights * @param inverseWeights * @param dropIsolates */ void Graph::layoutByProminenceIndex(int prominenceIndex, int layoutType, const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates) { qCDebug(lcLayouts) << "Applying layout by prominence index:" << prominenceIndex << "type:" << layoutType; double i = 0, stdC = 0, norm = 0; double new_x = 0, new_y = 0; qreal C = 0, maxC = 0; double x0 = 0, y0 = 0, maxRadius = 0, new_radius = 0, rad = 0; double maxWidth = 0, maxHeight = 0; qreal offset = 0; int new_size = 0; int N = vertices(); VList::const_iterator it; QColor new_color; switch (layoutType) { case 0: { // radial x0 = canvasWidth / 2.0; y0 = canvasHeight / 2.0; offset = 0.06; maxRadius = canvasMaxRadius(); break; } case 1: { // level offset = 50; maxHeight = canvasHeight - offset; maxWidth = canvasWidth - offset; break; } }; progressStatus(tr("Computing centrality/prestige scores. Please wait...")); // first compute centrality indices if needed if (prominenceIndex == 0) { // do nothing } else if (prominenceIndex == IndexType::DC) { centralityDegree(considerWeights, dropIsolates); } else if (prominenceIndex == IndexType::IRCC) { centralityClosenessIR(considerWeights, inverseWeights, dropIsolates); } else if (prominenceIndex == IndexType::IC) { centralityInformation(considerWeights, inverseWeights); } else if (prominenceIndex == IndexType::EVC) { centralityEigenvector(considerWeights, inverseWeights, dropIsolates); } else if (prominenceIndex == IndexType::DP) { prestigeDegree(considerWeights, dropIsolates); } else if (prominenceIndex == IndexType::PRP) { prestigePageRank(dropIsolates); } else if (prominenceIndex == IndexType::PP) { prestigeProximity(considerWeights, inverseWeights, dropIsolates); } else if (prominenceIndex == IndexType::CLC) { clusteringCoefficient(); } else if (prominenceIndex == IndexType::KATZ) { if (m_lastKatzAlpha < 0) { progressStatus(tr("Please compute Katz Centrality first " "(Analyze > Centrality > Katz Centrality), then try this layout again.")); return; } centralityKatz(m_lastKatzAlpha, considerWeights, inverseWeights, dropIsolates); } else if (prominenceIndex == IndexType::BPC) { if (m_lastBonacichAlpha < 0) { progressStatus(tr("Please compute Bonacich Power Centrality first " "(Analyze > Centrality > Bonacich Power Centrality), then try " "this layout again.")); return; } centralityBonacich(m_lastBonacichAlpha, m_lastBonacichBeta, considerWeights, inverseWeights, dropIsolates); } else { graphDistancesGeodesic(true, considerWeights, inverseWeights, dropIsolates); } if (progressCanceled()) { return; } QString pMsg; switch (layoutType) { case 0: { // radial pMsg = tr("Embedding Radial layout by Prominence Score. \nPlease wait..."); break; } case 1: { // level pMsg = tr("Embedding Level layout by Prominence Score. \nPlease wait..."); break; } case 2: { // node size pMsg = tr("Embedding Node Size by Prominence Score layout. \nPlease wait..."); break; } case 3: { // node color pMsg = tr("Embedding Node Color by Prominence Score layout. \nPlease wait..."); break; } } progressStatus(pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { switch (prominenceIndex) { case 0: { C = 0; maxC = 0; break; } case IndexType::DC: { C = (*it)->SDC(); stdC = (*it)->SDC(); maxC = maxSDC; break; } case IndexType::CC: { C = (*it)->CC(); stdC = (*it)->SCC(); maxC = maxSCC; break; } case IndexType::IRCC: { C = (*it)->IRCC(); stdC = (*it)->SIRCC(); maxC = maxIRCC; break; } case IndexType::BC: { C = (*it)->BC(); stdC = (*it)->SBC(); maxC = maxSBC; break; } case IndexType::SC: { C = (*it)->SC(); stdC = (*it)->SSC(); maxC = maxSSC; break; } case IndexType::EC: { C = (*it)->EC(); stdC = (*it)->SEC(); maxC = maxEC; break; } case IndexType::PC: { C = (*it)->PC(); stdC = (*it)->SPC(); maxC = maxSPC; break; } case IndexType::IC: { C = (*it)->IC(); stdC = (*it)->SIC(); maxC = maxIC; break; } case IndexType::EVC: { C = (*it)->EVC(); stdC = (*it)->SEVC(); maxC = 1; break; } case IndexType::DP: { C = (*it)->SDP(); stdC = (*it)->SDP(); maxC = maxSDP; break; } case IndexType::PRP: { C = (*it)->PRP(); stdC = (*it)->SPRP(); maxC = 1; break; } case IndexType::PP: { C = (*it)->PP(); stdC = (*it)->SPP(); maxC = maxPP; break; } case IndexType::CLC: { C = (*it)->CLC(); stdC = (*it)->CLC(); maxC = 1; break; } case IndexType::KATZ: { C = (*it)->KC(); stdC = (*it)->SKC(); maxC = 1; break; } case IndexType::BPC: { C = (*it)->BPC(); stdC = (*it)->SBPC(); maxC = 1; break; } }; norm = stdC / maxC; switch (layoutType) { case 0: { // radial qCDebug(lcLayouts) << "vertex" << (*it)->number() << "pos x" << (*it)->x() << "y" << (*it)->y() << "C" << C << "stdC" << stdC << "maxC" << maxC << "norm (stdC/maxC)" << norm << "maxradius" << maxRadius << "newradius" << maxRadius - (norm - offset) * maxRadius; // Compute new vertex position switch (static_cast(ceil(maxC))) { case 0: { qCDebug(lcLayouts, "maxC=0. Using maxRadius"); new_radius = maxRadius; break; } default: { new_radius = (maxRadius - (norm - offset) * maxRadius); break; } }; rad = (2.0 * M_PI / N); new_x = x0 + new_radius * cos(i * rad); new_y = y0 + new_radius * sin(i * rad); qCDebug(lcLayouts) << "Finished calculation. " "new radial pos: x" << new_x << "y" << new_y; // Move vertex to new position (*it)->setX(new_x); (*it)->setY(new_y); emit setNodePos((*it)->number(), new_x, new_y); i++; emit addGuideCircle(x0, y0, new_radius); break; } case 1: { // level qCDebug(lcLayouts) << "vertex" << (*it)->number() << "pos x" << (*it)->x() << "y" << (*it)->y() << "C" << C << "stdC" << stdC << "maxC " << maxC << "norm (stdC/maxC)" << norm << "maxWidth " << maxWidth << " maxHeight " << maxHeight << "maxHeight-(norm)*maxHeight " << maxHeight - (norm)*maxHeight; // Compute new vertex position switch (static_cast(ceil(maxC))) { case 0: { qCDebug(lcLayouts, "maxC=0. Using maxHeight"); new_y = maxHeight; break; } default: { new_y = offset / 2.0 + maxHeight - (norm)*maxHeight; break; } }; new_x = offset / 2.0 + rand() % (static_cast(maxWidth)); qCDebug(lcLayouts) << "Finished calculation. " "new level pos: x" << new_x << "y" << new_y; // Move vertex to new position (*it)->setX(new_x); (*it)->setY(new_y); emit setNodePos((*it)->number(), new_x, new_y); i++; emit addGuideHLine(new_y); break; } case 2: { // node size qCDebug(lcLayouts) << "vertex" << (*it)->number() << "C=" << C << ", stdC=" << stdC << "maxC" << maxC << "initVertexSize " << initVertexSize << "norm (stdC/maxC) " << norm << ", (norm) * initVertexSize " << (norm * initVertexSize); // Calculate new node size switch (static_cast(ceil(maxC))) { case 0: { qCDebug(lcLayouts) << "maxC=0. Using initVertexSize"; new_size = initVertexSize; break; } default: { new_size = ceil(initVertexSize / 2.0 + (qreal)initVertexSize * (norm)); break; } }; // set new vertex size and emit signal to change node size qCDebug(lcLayouts) << "Finished calculation. " << "new vertex size " << new_size << " call setSize()"; (*it)->setSize(new_size); emit setNodeSize((*it)->number(), new_size); break; } case 3: { // node color qCDebug(lcLayouts) << "vertex" << (*it)->number() << "C=" << C << ", stdC=" << stdC << "maxC" << maxC << "initVertexColor " << initVertexColor << "norm (stdC/maxC) " << norm; // Calculate new node color switch (static_cast(ceil(maxC))) { case 0: { qCDebug(lcLayouts) << "maxC=0. Using initVertexColor"; new_color = QColor(initVertexColor); break; } default: { // H = 0 (red) for most important nodes // H = 240 (blue) for least important nodes new_color.setHsv(240 - norm * 240, 255, 255, 255); break; } }; // change vertex color and emit signal to change node color as well qCDebug(lcLayouts) << "new vertex color " << new_color << " call setSize()"; (*it)->setColor(new_color.name()); emit setNodeColor((*it)->number(), new_color.name()); break; } }; } setModStatus(ModStatus::VertexPositions); prominenceDistribution(prominenceIndex, m_reportsChartType); emit signalLayoutFinished(); } /** * @brief Ego-centered radial layout. * * Places @p egoVertex at the canvas center, its 1-hop out-neighbors (in the * current relation) on an inner ring, and all remaining enabled nodes on an * outer ring. Only node positions are changed β€” graph structure is untouched. * * Ring radii are fixed fractions of canvasMaxRadius(): * ring1 = 0.35 Γ— maxRadius (ego neighbors) * ring2 = 0.75 Γ— maxRadius (all other nodes) * * Edge case: if @p egoVertex does not exist, emits a status message and returns. * * @param egoVertex The vertex number to place at the center. */ void Graph::layoutEgoRadial(const int egoVertex) { qCDebug(lcLayouts) << "Graph::layoutEgoRadial() - ego:" << egoVertex; if (!vertexExists(egoVertex)) { progressStatus(tr("Ego radial layout: vertex %1 not found.").arg(egoVertex)); return; } const int N = vertices(); const double x0 = canvasWidth / 2.0; const double y0 = canvasHeight / 2.0; const double maxRadius = canvasMaxRadius(); const double ring1Radius = maxRadius * 0.35; const double ring2Radius = maxRadius * 0.75; // --- Build neighbor set: all enabled out-edges of ego in current relation --- const QSet neighbors = vertexOutNeighborsSet(egoVertex); qCDebug(lcLayouts) << "Graph::layoutEgoRadial() - neighbors:" << neighbors.size() << "other nodes:" << (N - 1 - neighbors.size()); QString pMsg = tr("Embedding Ego Radial layout. \nPlease wait..."); progressStatus(pMsg); // --- Place ego at center --- m_graph[vpos[egoVertex]]->setX(x0); m_graph[vpos[egoVertex]]->setY(y0); emit setNodePos(egoVertex, x0, y0); // --- Place neighbors evenly on ring1 --- const int ring1Count = neighbors.size(); if (ring1Count > 0) { const double ring1Step = 2.0 * M_PI / ring1Count; int i = 0; for (const int neighbor : neighbors) { const double new_x = canvasVisibleX(x0 + ring1Radius * cos(i * ring1Step)); const double new_y = canvasVisibleY(y0 + ring1Radius * sin(i * ring1Step)); m_graph[vpos[neighbor]]->setX(new_x); m_graph[vpos[neighbor]]->setY(new_y); emit setNodePos(neighbor, new_x, new_y); ++i; } emit addGuideCircle(x0, y0, ring1Radius); } // --- Place remaining enabled nodes on ring2 --- // Collect them first so we know the count for even spacing. QList ring2Nodes; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) continue; const int vnum = (*it)->number(); if (vnum == egoVertex) continue; if (neighbors.contains(vnum)) continue; ring2Nodes.append(vnum); } const int ring2Count = ring2Nodes.size(); if (ring2Count > 0) { const double ring2Step = 2.0 * M_PI / ring2Count; for (int i = 0; i < ring2Count; ++i) { const double new_x = canvasVisibleX(x0 + ring2Radius * cos(i * ring2Step)); const double new_y = canvasVisibleY(y0 + ring2Radius * sin(i * ring2Step)); m_graph[vpos[ring2Nodes[i]]]->setX(new_x); m_graph[vpos[ring2Nodes[i]]]->setY(new_y); emit setNodePos(ring2Nodes[i], new_x, new_y); } emit addGuideCircle(x0, y0, ring2Radius); } setModStatus(ModStatus::VertexPositions); emit signalLayoutFinished(); } SocNetV-3.7/src/graph/layouts/graph_layouts_force.cpp000066400000000000000000001263471524402210300230450ustar00rootroot00000000000000/** * @file graph_layouts_force.cpp * @brief Implements force-directed layout algorithms of the Graph class (e.g., spring-embedder and related force-based positioning methods). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include #include #include /** * @brief Embeds a Force Directed Placement layout according to the initial Spring Embedder model proposed by Eades. * @param maxIterations Maximum number of iterations to run. The loop may exit * earlier if the layout converges (max displacement falls below epsilon). * * The Spring Embedder model (Eades, 1984), part of the Force Directed Placement (FDP) family, * assigns forces to all vertices and edges: non-adjacent node pairs repel each other via an * inverse-square law (analogous to Coulomb's law), while adjacent pairs attract via a * logarithmic spring β€” deliberately not Hooke's law, which Eades argued causes clumping * at large distances. * These forces are applied to the nodes iteratively, pulling them closer together or pushing * them further apart, until the system comes to an equilibrium state (node positions do not * change anymore). * * Implementation notes: * - Adjacency is pre-cached into a QSet> before the iteration loop to avoid * O(log N) edgeExists() calls inside the O(NΒ²) inner loop. * - Repulsion and attraction math is inlined directly (no QString model dispatch per call). * - Vertex numbers are cached to local ints before the inner loop. * - A linear cooling schedule decreases c4 from c4_init to c4_min over maxIterations, * damping displacement progressively so the layout settles rather than oscillating. * - Early exit when max displacement in an iteration falls below epsilon (convergence). * */ void Graph::layoutForceDirectedSpringEmbedder(const int maxIterations) { // --- Step size (c4 in Eades' notation) --- // Eades used a fixed c4=0.1. We keep a mild linear cooling schedule // (0.1 β†’ 0.01) to help the layout settle without oscillating, while // staying close to the original spirit. The displacement cap in // layoutForceDirected_Eades_moveNodes() prevents early-iteration explosions. const qreal c4_init = 0.10; const qreal c4_min = 0.01; // --- Force constants --- // Faithful to Eades (1984): c1=2, c3=1 in his notation. // c_spring (c1): logarithmic spring stiffness for adjacent pairs. // c_rep (c3): inverse-square repulsion strength for all pairs. const qreal c_spring = 2.0; const qreal c_rep = 1.0; // --- Convergence threshold --- // Exit early when the largest single-node displacement in an iteration // falls below epsilon (canvas pixels). Avoids running all maxIterations // when the layout has already settled. // Must be large enough that the layout runs for sufficient iterations // before converging β€” 0.5 was too tight and caused exit after 1-2 iterations. const qreal epsilon = 2.0; int iteration = 1; qreal dist = 0; qreal f_rep = 0, f_att = 0; QPointF DV; VList::const_iterator v1; VList::const_iterator v2; qreal V = (qreal)vertices(); // Natural length (c2 in Eades' notation): the ideal edge length in canvas pixels. // Eades worked in a normalised unit square with c2=1. Translating to pixel // coordinates: c2 = canvasMinDimension / sqrt(V), which gives the pixel-space // equivalent of one "unit" in his formulation. For N=100 on a 799px canvas // this yields ~80px β€” well within the range where log(d/c2) is meaningfully // attractive for connected pairs (d > c2) and repulsive for too-close pairs. qreal naturalLength = canvasMinDimension() / qSqrt(V); qCDebug(lcLayouts) << "\n\n layoutForceDirectedSpringEmbedder()" << "vertices" << V << "canvas" << canvasWidth << canvasHeight << "naturalLength" << naturalLength << "maxIterations" << maxIterations << "c4_init" << c4_init << "c4_min" << c4_min << "c_spring" << c_spring << "c_rep" << c_rep; // --- Pre-cache adjacency into a flat QSet for O(1) edge existence lookup. // This avoids NΒ² Γ— maxIterations calls to edgeExists() (which is O(log N)) // inside the inner loop. We cache both directions so the undirected case // is handled transparently. QSet> adjCache; adjCache.reserve(edgesEnabled() * 2); for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { if (!(*v1)->isEnabled()) continue; int s = (*v1)->number(); for (v2 = m_graph.cbegin(); v2 != m_graph.cend(); ++v2) { if (!(*v2)->isEnabled()) continue; int t = (*v2)->number(); if (s == t) continue; if (edgeExists(s, t)) adjCache.insert(qMakePair(s, t)); } } qCDebug(lcLayouts) << "layoutForceDirectedSpringEmbedder() - adjCache size" << adjCache.size(); /* Apply an initial random layout */ layoutRandomInMemory(); QString pMsg = tr("Embedding Eades Spring-Gravitational model. \n" "Please wait ...."); progressStatus(pMsg); for (iteration = 1; iteration <= maxIterations; iteration++) { // Linear cooling: c4 decreases from c4_init at iteration 1 // to c4_min at iteration maxIterations. const qreal c4 = c4_init - (c4_init - c4_min) * (qreal(iteration - 1) / qreal(maxIterations - 1)); // Reset displacement vectors for all vertices for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { (*v1)->disp().rx() = 0; (*v1)->disp().ry() = 0; } for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { if (!(*v1)->isEnabled()) continue; // Cache source vertex number once β€” avoids repeated iterator // dereferences inside the O(N) inner loop. const int s = (*v1)->number(); for (v2 = m_graph.cbegin(); v2 != m_graph.cend(); ++v2) { if (!(*v2)->isEnabled()) continue; if (v2 == v1) continue; // Cache target vertex number once per inner iteration. const int t = (*v2)->number(); DV.setX((*v2)->x() - (*v1)->x()); DV.setY((*v2)->y() - (*v1)->y()); dist = graphDistanceEuclidean(DV); // --- Repulsive force (Eades: inverse-square, all pairs) --- // Inlined from layoutForceDirected_F_rep("Eades",...). // No distance cutoff: every pair repels, regardless of distance. // This fixes the clustering bug caused by the old // "f_rep = 0 when dist > 2*optimalDistance" cutoff. if (dist > 0) f_rep = -(c_rep / (dist * dist)); else f_rep = -naturalLength; // coincident vertices: push apart (*v1)->disp().rx() += sign(DV.x()) * f_rep; (*v1)->disp().ry() += sign(DV.y()) * f_rep; // --- Attractive (spring) force (Eades: log spring, adjacent pairs only) --- // Inlined from layoutForceDirected_F_att("Eades",...). // O(1) lookup via pre-cached QSet instead of edgeExists(). if (adjCache.contains(qMakePair(s, t))) { f_att = (dist > 0) ? c_spring * log10(dist / naturalLength) : 0.0; (*v1)->disp().rx() += sign(DV.x()) * f_att; (*v1)->disp().ry() += sign(DV.y()) * f_att; (*v2)->disp().rx() -= sign(DV.x()) * f_att; (*v2)->disp().ry() -= sign(DV.y()) * f_att; } } // end for v2 } // end for v1 // Apply displacements with current cooling factor and track max displacement // for convergence detection. const qreal maxDisp = layoutForceDirected_Eades_moveNodes(c4); if (progressCanceled()) { setModStatus(ModStatus::VertexPositions); return; } qCDebug(lcLayouts) << " iteration" << iteration << " c4=" << c4 << " maxDisp=" << maxDisp; // Early exit: layout has converged when no node moved more than epsilon pixels. if (maxDisp < epsilon) { qCDebug(lcLayouts) << "layoutForceDirectedSpringEmbedder() - converged at iteration" << iteration << " maxDisp=" << maxDisp; break; } } // end iterations // Emit all final node positions in one bulk pass now that the iteration loop // is complete β€” avoids NΓ—maxIterations signal emissions to the graphics scene. for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { emit setNodePos((*v1)->number(), (*v1)->x(), (*v1)->y()); } setModStatus(ModStatus::VertexPositions); emit signalLayoutFinished(); } /** * @brief Embeds a Force Directed Placement layout according to the Fruchterman-Reingold model. * Fruchterman and Reingold (1991) refined the Spring Embedder model by replacing the forces. In this model, "the vertices behave as atomic particles or celestial bodies, exerting attractive and repulsive forces on one another." (ibid). Again, only vertices that are neighbours attract each other but, unlike Spring Embedder, all vertices repel each other. These forces induce movement. The algorithm might resemble molecular or planetary simulations, sometimes called n-body problems. * @param maxIterations */ void Graph::layoutForceDirectedFruchtermanReingold(const int maxIterations) { qreal dist = 0; qreal f_att, f_rep; QPointF DV; // difference vector qreal V = (qreal)vertices(); qreal C = 0.9; // this is found experimentally // optimalDistance (or k) is the radius of the empty area around a vertex - // we add vertexWidth to it qreal optimalDistance = C * computeOptimalDistance(V); // Convergence threshold: exit early when no node moves more than epsilon pixels. const qreal epsilon = 2.0; VList::const_iterator v1, v2; int iteration = 1; // Apply an initial random layout, same as Eades. // Ensures FR never starts from a stale or degenerate position state. layoutRandomInMemory(); qCDebug(lcLayouts) << "Graph: layoutForceDirectedFruchtermanReingold() "; qCDebug(lcLayouts) << "Graph: Setting optimalDistance = " << optimalDistance << "...following Fruchterman-Reingold (1991) formula "; qCDebug(lcLayouts) << "Graph: canvasWidth " << canvasWidth << " canvasHeight " << canvasHeight; // Pre-cache adjacency into a flat QSet for O(1) edge existence lookup. // Avoids NΒ² Γ— maxIterations calls to edgeExists() (O(log N) each) // inside the inner loop. Both directions cached for undirected transparency. QSet> adjCache; adjCache.reserve(edgesEnabled() * 2); for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { if (!(*v1)->isEnabled()) continue; int s = (*v1)->number(); for (v2 = m_graph.cbegin(); v2 != m_graph.cend(); ++v2) { if (!(*v2)->isEnabled()) continue; int t = (*v2)->number(); if (s == t) continue; if (edgeExists(s, t)) adjCache.insert(qMakePair(s, t)); } } qCDebug(lcLayouts) << "layoutForceDirectedFruchtermanReingold() - adjCache size" << adjCache.size(); QString pMsg = tr("Embedding Fruchterman & Reingold forces model. \n" "Please wait ..."); progressStatus(pMsg); for (iteration = 1; iteration <= maxIterations; iteration++) { // setup init disp for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { (*v1)->disp().rx() = 0; (*v1)->disp().ry() = 0; } for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { if (!(*v1)->isEnabled()) continue; // Cache source vertex number once β€” avoids repeated iterator // dereferences inside the O(N) inner loop. const int s = (*v1)->number(); for (v2 = m_graph.cbegin(); v2 != m_graph.cend(); ++v2) { if (!(*v2)->isEnabled()) continue; if (v2 == v1) continue; // Cache target vertex number once per inner iteration. const int t = (*v2)->number(); DV.setX((*v2)->x() - (*v1)->x()); DV.setY((*v2)->y() - (*v1)->y()); dist = graphDistanceEuclidean(DV); // Repulsive force β€” all pairs f_rep = layoutForceDirected_F_rep("FR", dist, optimalDistance); (*v1)->disp().rx() += sign(DV.x()) * f_rep; (*v1)->disp().ry() += sign(DV.y()) * f_rep; // Attractive (spring) force β€” adjacent pairs only. // O(1) lookup via pre-cached QSet if (adjCache.contains(qMakePair(s, t))) { f_att = layoutForceDirected_F_att("FR", dist, optimalDistance); (*v1)->disp().rx() += sign(DV.x()) * f_att; (*v1)->disp().ry() += sign(DV.y()) * f_att; (*v2)->disp().rx() -= sign(DV.x()) * f_att; (*v2)->disp().ry() -= sign(DV.y()) * f_att; } } // end for v2 } // end for v1 // Limit displacement to temperature; track max displacement for convergence. const qreal maxDisp = layoutForceDirected_FR_moveNodes( layoutForceDirected_FR_temperature(iteration)); if (progressCanceled()) { setModStatus(ModStatus::VertexPositions); return; } qCDebug(lcLayouts) << " iteration" << iteration << " maxDisp=" << maxDisp; // Early exit: layout has converged when no node moved more than epsilon pixels. if (maxDisp < epsilon) { qCDebug(lcLayouts) << "layoutForceDirectedFruchtermanReingold() - converged at iteration" << iteration << " maxDisp=" << maxDisp; break; } } // Emit all final node positions in one bulk pass now that the // iteration loop is complete β€” avoids NΓ—maxIterations signal emissions. for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { emit setNodePos((*v1)->number(), (*v1)->x(), (*v1)->y()); } setModStatus(ModStatus::VertexPositions); // was missing on normal exit path emit signalLayoutFinished(); } /** * @brief Embeds a Force Directed Placement layout according to the Kamada-Kawai model. * In this model, the network is considered to be a dynamic system * where every two actors are 'particles' mutually connected by a 'spring'. * Each spring has a desirable length, which corresponds to their graph * theoretic distance. In this way, the optimal layout of the graph * is the state with the minimum imbalance. The degree of * imbalance is formulated as the total spring energy: * the square summation of the differences between desirable * distances and real ones for all pairs of particles * Initially, the particles/actors are placed on the vertices of a regular n-polygon */ void Graph::layoutForceDirectedKamadaKawai(const int maxIterations, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, const QString &initialPositions) { qCDebug(lcLayouts) << "Embedding an FDP layout according to the Kamada-Kawai model, maxIterations:" << maxIterations; VList::const_iterator v1, v2; int progressCounter = 0, minimizationIterations = 0; int i = 0, j = 0, m = 0, pm = 0, pnm = 0, pn = 0; int mVpos = 0, pmVpos = 0; // physical m_graph[] indices, used only for m_graph[] access int N = vertices(); // active actors qreal K = 1; // constant qreal L = 0; // the desirable length of a single edge. qreal L0 = 0; // the length of a side of the display square area qreal D = 0; // the graph diameter Matrix l; // the original spring length between pairs of particles/actors Matrix k; // the strength of the spring between pairs of particles/actors Matrix LIN_EQ_COEF(2, 2); // holds the coefficients of set of linear equations 11 and 12 qreal b[2]; // holds the right hand vector of linear equations 11 and 12 qreal partDrvtEx = 0; // partial derivative of E by Xm qreal partDrvtEy = 0; // partial derivative of E by Ym qreal partDrvtExSec_m = 0; // partial second derivative of E by Xm qreal partDrvtEySec_m = 0; // partial second derivative of E by Ym qreal partDrvtExEySec_m = 0; // partial second derivative of E by Xm Ym qreal partDrvtEyExSec_m = 0; // partial second derivative of E by Ym Xm qreal partDrvtEx_m = 0; // cache for partial derivative of E by Xm, for particle with max D_i qreal partDrvtEy_m = 0; // cache for partial derivative of E by Ym, for particle with max D_i qreal partDrvDenom = 0; qreal xm = 0, ym = 0; qreal xi = 0, yi = 0; qreal xpm = 0, ypm = 0; // cache for pos of particle with max D_i qreal dx = 0, dy = 0; qreal epsilon = 0.1; qreal Delta_m = 0; qreal Delta_max = epsilon + 0.0001; bool couldNotSolveLinearSystem = false; // Compute graph-theoretic distances dij for 1 <= i!=j <= n qCDebug(lcLayouts) << "Compute dij, where (i,j) in E"; if (!graphMatrixDistanceGeodesicCreate(considerWeights, inverseWeights, dropIsolates)) { return; } if (progressCanceled()) { return; } // processEvents() ensures the geodesic progress dialog updates are // flushed before we start the KK iteration phase. QApplication::processEvents(); // Use the cached diameter β€” graphMatrixDistanceGeodesicCreate() already // ran the DistanceEngine so re-calling graphDiameter() would redundantly // re-trigger it if the cache were ever invalidated. D = graphDiameterCached(); L0 = canvasMinDimension() - 100; // Guard against degenerate graphs (no edges, all isolates, single node) // where D == 0, which would cause division by zero. if (D <= 0) { qCDebug(lcLayouts) << "KK layout: graph diameter is 0 (degenerate graph). Aborting."; setModStatus(ModStatus::VertexPositions); return; } // L = L0 / D: the desirable length of a single edge in the display pane // scales with the graph diameter so the layout fills the canvas. L = L0 / D; qCDebug(lcLayouts) << "Compute lij = L x dij. lij will be symmetric." << "L0=" << L0 << "D=" << D << "L=" << L; l.zeroMatrix(DM.rows(), DM.cols()); l = DM; l.multiplyScalar(L); // l.printMatrixConsole(); // Compute kij for 1 <= i!=j <= n using the formula: // kij = K / dij ^2 // kij is the strength of the spring between pi and pj, K a constant qCDebug(lcLayouts) << "Compute kij = K / dij ^2. kij will be symmmetric. "; k.zeroMatrix(DM.rows(), DM.cols()); for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { if (i == j) continue; qreal dij = DM.item(i, j); if (dij <= 0 || dij >= RAND_MAX) { // disconnected or degenerate pair β€” no spring force k.setItem(i, j, 0); continue; } k.setItem(i, j, K / (dij * dij)); } } // k.printMatrixConsole(); // Build a local index mapping: vertex number β†’ sequential KK matrix index. // This mirrors exactly the order graphMatrixDistanceGeodesicCreate() used // when filling DM (iterating m_graph in order, skipping disabled/isolated). // Matrix indices m and i must use this mapping; m_graph[] access uses vpos. QHash kkIdx; kkIdx.reserve(N); { int seq = 0; VList::const_iterator vit; for (vit = m_graph.cbegin(); vit != m_graph.cend(); ++vit) { if (!(*vit)->isEnabled()) continue; if (dropIsolates && (*vit)->isIsolated()) continue; kkIdx[(*vit)->number()] = seq++; } } // initialize p1, p2, ... pn qCDebug(lcLayouts) << "Set particles to initial positions p"; i = 0; if (initialPositions == "circle") { double x0 = 0, y0 = 0; x0 = canvasWidth / 2.0; y0 = canvasHeight / 2.0; // placing the particles on the vertices of a regular n-polygon // circumscribed by a circle whose diameter is L0 layoutCircular(x0, y0, L0 / 2, false); } else if (initialPositions == "random") { layoutRandom(); } QString pMsg = tr("Embedding Kamada & Kawai spring model.\n" "Please wait..."); progressStatus(pMsg); // while ( max_D_i > e ) while (Delta_max > epsilon) { couldNotSolveLinearSystem = false; // progressCounter is also this loop's iteration-count guard (see the // maxIterations check below), not just a dialog display value - kept even // though the progressUpdate() dialog call it used to feed is gone. progressCounter++; if (progressCanceled()) { setModStatus(ModStatus::VertexPositions); return; } if (progressCounter == maxIterations) { break; } Delta_max = epsilon; // choose particle with largest Delta_m = max Delta_i // compute partial derivatives of E by xm and ym for every particle m // using equations 7 and 8 qCDebug(lcLayouts) << "Iteration:" << progressCounter << "Choose particle with largest Delta_m = max Delta_i "; pnm = -1; for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { pn = (*v1)->number(); mVpos = vpos[pn]; m = kkIdx.value(pn, -1); xm = (*v1)->x(); ym = (*v1)->y(); if (!(*v1)->isEnabled()) { continue; } partDrvtEx = 0; partDrvtEy = 0; for (v2 = m_graph.cbegin(); v2 != m_graph.cend(); ++v2) { i = kkIdx.value((*v2)->number(), -1); xi = (*v2)->x(); yi = (*v2)->y(); if (!(*v2)->isEnabled()) { qCDebug(lcLayouts) << " i " << (*v2)->number() << " disabled. Continue"; continue; } if (i < 0 || i == m) { continue; } qreal denom1 = sqrt((xm - xi) * (xm - xi) + (ym - yi) * (ym - yi)); if (denom1 < 1e-6) { // particles at same position β€” skip to avoid division by zero continue; } partDrvtEx += k.item(m, i) * ((xm - xi) - (l.item(m, i) * (xm - xi)) / denom1); partDrvtEy += k.item(m, i) * ((ym - yi) - (l.item(m, i) * (ym - yi)) / denom1); } // end v2 for loop Delta_m = sqrt(partDrvtEx * partDrvtEx + partDrvtEy * partDrvtEy); if (Delta_m > Delta_max) { Delta_max = Delta_m; partDrvtEx_m = partDrvtEx; partDrvtEy_m = partDrvtEy; pnm = pn; // store name of particle satisfying Delta_m = max Delta_i pm = m; // store kkIdx of particle satisfying Delta_m = max Delta_i pmVpos = mVpos; // store m_graph index of same particle xpm = xm; // store particle x pos ypm = ym; // store particle y pos } } // end v1 for loop if (pnm < 0) { break; } // let pm the particle satisfying D_m = max_D_i m = pm; mVpos = pmVpos; xm = xpm; ym = ypm; minimizationIterations = 0; // while ( D_m > e) do { if (minimizationIterations > 10) { qCDebug(lcLayouts) << "Reached minimizationIterations threshold. BREAK"; break; } minimizationIterations++; // compute dx and dy by solving equations 11 and 12 partDrvtEx = 0; partDrvtEy = 0; partDrvtEx_m = 0; partDrvtEy_m = 0; partDrvtExSec_m = 0; partDrvtEySec_m = 0; partDrvtExEySec_m = 0; partDrvtEyExSec_m = 0; // first compute coefficients of the linear system equations for (v2 = m_graph.cbegin(); v2 != m_graph.cend(); ++v2) { i = kkIdx.value((*v2)->number(), -1); xi = (*v2)->x(); yi = (*v2)->y(); if (!(*v2)->isEnabled()) { qCDebug(lcLayouts) << " i " << (*v2)->number() << " disabled. Continue"; continue; } if (i < 0 || i == m) { continue; } qreal denom2 = sqrt((xm - xi) * (xm - xi) + (ym - yi) * (ym - yi)); if (denom2 < 1e-6) { // particles at same position β€” skip to avoid division by zero continue; } partDrvDenom = denom2 * denom2 * denom2; partDrvtEx_m += k.item(m, i) * ((xm - xi) - (l.item(m, i) * (xm - xi)) / denom2); partDrvtEy_m += k.item(m, i) * ((ym - yi) - (l.item(m, i) * (ym - yi)) / denom2); partDrvtExSec_m += k.item(m, i) * (1 - (l.item(m, i) * (ym - yi) * (ym - yi)) / partDrvDenom); partDrvtExEySec_m += k.item(m, i) * ((l.item(m, i) * (xm - xi) * (ym - yi)) / partDrvDenom); partDrvtEyExSec_m += k.item(m, i) * ((l.item(m, i) * (xm - xi) * (ym - yi)) / partDrvDenom); partDrvtEySec_m += k.item(m, i) * (1 - (l.item(m, i) * (xm - xi) * (xm - xi)) / partDrvDenom); } // end v2 for loop Delta_m = sqrt(partDrvtEx_m * partDrvtEx_m + partDrvtEy_m * partDrvtEy_m); LIN_EQ_COEF.setItem(0, 0, partDrvtExSec_m); LIN_EQ_COEF.setItem(0, 1, partDrvtExEySec_m); LIN_EQ_COEF.setItem(1, 0, partDrvtEyExSec_m); LIN_EQ_COEF.setItem(1, 1, partDrvtEySec_m); // LIN_EQ_COEF.printMatrixConsole(); b[0] = -partDrvtEx_m; b[1] = -partDrvtEy_m; if (!LIN_EQ_COEF.solve(b)) { couldNotSolveLinearSystem = true; continue; } dx = b[0]; dy = b[1]; // Clamp new position to visible canvas area. // Previously used canvasRandomX/Y() as fallback β€” a random teleport // that introduced noise for large graphs with many out-of-bounds particles. // canvasVisibleX/Y() clamps to the valid range, consistent with all // other moveNodes functions. xm = canvasVisibleX(xm + dx); ym = canvasVisibleY(ym + dy); qCDebug(lcLayouts) << "m" << m << " new m pos " << xm << ym; // TODO CHECK IF WE HAVE REACHED A FIXED POINT LOOP } while (Delta_m > epsilon); if (couldNotSolveLinearSystem) { qCDebug(lcLayouts) << "Could not solve linear system for particle " << pnm << "vpos" << m; } qCDebug(lcLayouts) << "Finished minimizing Delta_m for particle" << pnm << "vpos" << m << "Minimized Delta_m" << Delta_m << "moving it to new pos" << xm << ym; m_graph[mVpos]->setX(xm); m_graph[mVpos]->setY(ym); } // end while (Delta_max > epsilon) { qCDebug(lcLayouts) << "Delta_max =< epsilon -- RETURN"; for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { emit setNodePos((*v1)->number(), (*v1)->pos().x(), (*v1)->pos().y()); } setModStatus(ModStatus::VertexPositions); emit signalLayoutFinished(); } /** * @brief Reduces the temperature as the layout approaches a better configuration * @return qreal temperature */ qreal Graph::layoutForceDirected_FR_temperature(const int iteration) const { qreal temp = 0; // Simmering temperature: derived from canvas width to maintain continuity // with the quenching phase formula canvasWidth / (iteration + 10). // At the quench/simmer boundary (iteration=10) the quench formula gives // canvasWidth/20; we use canvasWidth/210 as the constant low simmering // value (the quench formula evaluated at the simmer/freeze boundary, // iteration=200). Previously hardcoded as 5.8309518948453, which assumed // a fixed canvas width of ~1224px and broke on other display sizes. qreal temperature = canvasWidth / 210.0; qCDebug(lcLayouts) << "Graph::layoutForceDirected_FR_temperature(): cool iteration " << iteration; // For the temperature (which limits the displacement of each vertex), // Fruchterman & Reingold suggested in their paper that it might start // at an initial high value (i.e. "one tenth the width of the frame") // and then decay to 0 in an inverse linear fashion. // They also suggested "a combination of quenching and simmering", // which is a high initial temperature and then a constant low one. // This is what SocNetV does. In fact after 200 iterations we follow Eades // advice and stop movement (temp = 0). if (iteration < 10) { // quenching: starts at a high temperature (canvasWidth / 10) // and cools steadily and rapidly temp = canvasWidth / (iteration + 10.0); } else if (iteration > 200) { // follow Eades advice: freeze all movement temp = 0; } else { // simmering: stay at a constant low temperature temp = temperature; } qCDebug(lcLayouts) << "Graph::layoutForceDirected_FR_temperature() - iteration " << iteration << " temp " << temp; return temp; } /** * @brief Computes the optimal inter-vertex distance for force-directed layouts. * * The optimal distance (also called k or naturalLength) represents the radius * of the empty area ideally surrounding each vertex. It is derived from the * available canvas area per vertex, plus the vertex's own diameter. * * Formula: * vertexArea = ceil( sqrt( canvasArea / V ) ) β€” side of a square tile per vertex * optimalDistance = vertexDiameter + vertexArea * * This gives the distance at which repulsive and attractive forces balance for * a uniformly distributed graph. For denser graphs or larger N, vertexArea * shrinks (fewer pixels per node), so the returned value decreases β€” callers * may apply their own scaling multiplier on top (e.g. FR uses C=0.9). * * @param V Number of active vertices in the graph. * @return Optimal inter-vertex spacing in canvas pixels. */ qreal Graph::computeOptimalDistance(const int &V) { // Diameter of a vertex in canvas pixels qreal vertexWidth = (qreal)2.0 * initVertexSize; qreal screenArea = canvasHeight * canvasWidth; // Side length of the square canvas tile allocated to each vertex qreal vertexArea = qCeil(qSqrt(screenArea / V)); // Optimal distance = vertex diameter + per-vertex tile side return (vertexWidth + vertexArea); } /** * @brief Computes the attractive (spring) force between two adjacent vertices. * * For the Eades model: logarithmic spring force scaled by c_spring = 2. * Note: log10(dist / optimalDistance) is negative when dist < optimalDistance, * meaning the spring pushes nodes apart when they are closer than the natural * length β€” this is intentional per Eades (1984), who uses a log spring that * both attracts far-apart neighbours and repels too-close ones. * * For the FR model: quadratic attraction as per Fruchterman & Reingold (1991). * * @param model Layout model identifier: "Eades" or "FR". * @param dist Euclidean distance between the two adjacent vertices (canvas pixels). * @param optimalDistance The natural/optimal inter-vertex spacing for the current graph. * @return Attractive force magnitude (positive = pull toward each other). */ qreal Graph::layoutForceDirected_F_att(const QString model, const qreal &dist, const qreal &optimalDistance) { qreal f_att; if (model == "Eades") { // NOTE: This branch is retained for reference only. // The Eades path in layoutForceDirectedSpringEmbedder() inlines // the attraction force directly and does NOT call this function. qreal c_spring = 2; f_att = c_spring * log10(dist / optimalDistance); } else { // model == "FR" f_att = (dist * dist) / optimalDistance; } return f_att; } /** * @brief Computes the repulsive force between two vertices for force-directed layouts. * * For the Eades model: uses an inverse-square law with constant c_rep. * - The previous cutoff (f_rep = 0 when dist > 2 * optimalDistance) has been removed. * That cutoff was the primary cause of node clustering: distant nodes received zero * repulsion and were never pushed apart. All vertex pairs now repel each other, * regardless of distance. * - c_rep = c3 = 1.0 per Eades (1984). The inlined Eades path in * layoutForceDirectedSpringEmbedder() uses the same value directly. * * For the FR model: uses the grid-variant with a 2*optimalDistance cutoff radius, * as described by Fruchterman & Reingold (1991). * * @param model Layout model identifier: "Eades" or "FR". * @param dist Euclidean distance between the two vertices (canvas pixels). * @param optimalDistance The natural/optimal inter-vertex spacing for the current graph. * @return Repulsive force magnitude (negative, i.e. pushing apart). */ qreal Graph::layoutForceDirected_F_rep(const QString model, const qreal &dist, const qreal &optimalDistance) { qreal f_rep; if (model == "Eades") { if (dist > 0) { // c_rep = c3 = 1.0 per Eades (1984). // No distance cutoff: every pair repels regardless of distance, // fixing the clustering bug from the original implementation. qreal c_rep = 1.0; f_rep = c_rep / (dist * dist); } else { f_rep = 0; // coincident vertices handled by inlined Eades path } } else { // model == "FR" // Grid-variant: neglect repulsion outside 2*optimalDistance radius // to approximate the Barnes-Hut speedup described by FR (1991). if ((2.0 * optimalDistance) < dist) { f_rep = 0; } else { f_rep = (optimalDistance * optimalDistance / dist); } } return -f_rep; } /** * @brief Graph::sign * returns the sign of number D as integer (1 or -1) * @param D * @return */ int Graph::sign(const qreal &D) { if (D != 0) { return (D / qAbs(D)); } else { return 0; } } /** * @brief Graph::compute_angles * Computes the two angles of the orthogonal triangle shaped by two points * of difference vector DV and distance dist * A = 90 * B = angle1 * C = angle2 * * @param DV * @param dist * @param angle1 * @param angle2 * @param degrees1 * @param degrees2 */ void Graph::compute_angles(const QPointF &DV, const qreal &dist, qreal &angle1, qreal &angle2, qreal °rees1, qreal °rees2) { if (dist > 0) { angle1 = qAcos(qAbs(DV.x()) / dist); // radians angle2 = (M_PI / 2.0) - angle1; // radians (pi/2 -a1) } else { angle1 = 0; angle2 = 0; } degrees1 = angle1 * 180.0 / M_PI; // convert to degrees degrees2 = angle2 * 180.0 / M_PI; // convert to degrees qCDebug(lcLayouts) << "angle1 " << angle1 << " angle2 " << angle2 << "deg1 " << degrees1 << " deg2 " << degrees2 << " qCos " << qCos(angle1) << " qSin" << qSin(angle2); } /** * @brief Computes the euclideian distance between QPointF a and b * @param a * @param b * @return */ qreal Graph::graphDistanceEuclidean(const QPointF &a, const QPointF &b) { return qSqrt( (b.x() - a.x()) * (b.x() - a.x()) + (b.y() - a.y()) * (b.y() - a.y())); } /** * @brief the euclideian distance of QPointF a (where a is difference vector) * @param a * @return */ qreal Graph::graphDistanceEuclidean(const QPointF &a) { return qSqrt( a.x() * a.x() + a.y() * a.y()); } /** * @brief Moves all vertices to their new positions as computed by the Eades Spring Embedder model. * * Called once per iteration from layoutForceDirectedSpringEmbedder(). Applies the * accumulated displacement vectors to each vertex position, scaled by the normalization * factor c4, and clamps the result to the visible canvas area. * * Displacement clamping: each component (x, y) is capped at * Β±(canvasMinDimension * 0.05) before being applied. This prevents the * "iteration-2 explosion" caused by near-coincident node pairs at the start * of the simulation β€” without the cap, f_rep = c_rep/distΒ² diverges for * distβ†’0 and a single node pair can produce a 100px+ spike that locks the * layout into a bad configuration for all remaining iterations. * * Node positions are updated in-memory only (setX/setY). The caller is responsible * for emitting setNodePos signals in a single bulk pass after the iteration loop * completes β€” this avoids NΓ—maxIterations signal emissions to the graphics scene. * * @param c4 Displacement normalization factor for this iteration. Supplied by the * caller's linear cooling schedule (decreases from c4_init to c4_min over * maxIterations) to progressively damp movement and allow the layout to settle. * @return The largest Euclidean displacement applied to any single vertex this * iteration (canvas pixels). Used by the caller for convergence detection. */ qreal Graph::layoutForceDirected_Eades_moveNodes(const qreal &c4) { qCDebug(lcLayouts) << "\n ***** layoutForceDirected_Eades_moveNodes() c4=" << c4; // Maximum displacement allowed per node per iteration, per component (x or y). // Caps the effect of near-coincident node pairs where f_rep = c_rep/distΒ² // diverges as distβ†’0, preventing a single large spike from locking the // layout into a bad configuration in the first few iterations. // Scales with canvas size so it works across different display resolutions. const qreal maxAllowedDisp = canvasMinDimension() * 0.05; QPointF newPos; qreal xvel = 0, yvel = 0; qreal maxDisp = 0; VList::const_iterator v1; for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { // Scale displacement by the current cooling factor xvel = c4 * (*v1)->disp().rx(); yvel = c4 * (*v1)->disp().ry(); // Clamp each component to Β±maxAllowedDisp to prevent explosion // from near-coincident node pairs at simulation start xvel = qBound(-maxAllowedDisp, xvel, maxAllowedDisp); yvel = qBound(-maxAllowedDisp, yvel, maxAllowedDisp); qCDebug(lcLayouts) << " ##### vertex" << (*v1)->number() << " xvel,yvel=(" << xvel << "," << yvel << ")" << " maxAllowedDisp=" << maxAllowedDisp; // Fix Qt sub-pixel rounding: a fractional positive displacement < 1 // gets floored to 0 by integer QPoint conversion; bump it to 1. if (xvel < 1 && xvel > 0) xvel = 1; if (yvel < 1 && yvel > 0) yvel = 1; // Track largest displacement for convergence detection in the caller qreal disp = qSqrt(xvel * xvel + yvel * yvel); if (disp > maxDisp) maxDisp = disp; newPos = QPointF((*v1)->x() + xvel, (*v1)->y() + yvel); qCDebug(lcLayouts) << " vertex" << (*v1)->number() << " current pos:(" << (*v1)->x() << "," << (*v1)->y() << ")" << " possible new pos:(" << newPos.x() << "," << newPos.y() << ")"; // Clamp to visible canvas area newPos.rx() = canvasVisibleX(newPos.x()); newPos.ry() = canvasVisibleY(newPos.y()); qCDebug(lcLayouts) << " final new pos:(" << newPos.x() << "," << newPos.y() << ")"; (*v1)->setX(newPos.x()); (*v1)->setY(newPos.y()); } return maxDisp; } /** * @brief Moves all vertices to their new positions as computed by the Fruchterman-Reingold model. * * Called once per iteration from layoutForceDirectedFruchtermanReingold(). Limits each * vertex's displacement to the current temperature value, which decreases with each * iteration to act as a simulated annealing cooling schedule, then clamps the result * to the visible canvas area. * * Node positions are updated in-memory only (setX/setY). The caller is responsible * for emitting setNodePos signals in a single bulk pass after the iteration loop * completes β€” this avoids NΓ—maxIterations signal emissions to the graphics scene. * * @param temperature Current annealing temperature, controlling the maximum displacement * per iteration. Computed by layoutForceDirected_FR_temperature(). */ qreal Graph::layoutForceDirected_FR_moveNodes(const qreal &temperature) { qCDebug(lcLayouts) << "\n\n ***** layoutForceDirected_FR_moveNodes() \n\n "; qCDebug(lcLayouts) << " temperature " << temperature; QPointF newPos; qreal xvel = 0, yvel = 0; qreal maxDisp = 0; VList::const_iterator v1; for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { // Limit displacement to the current temperature (simulated annealing cap) xvel = sign((*v1)->disp().rx()) * qMin(qAbs((*v1)->disp().rx()), temperature); yvel = sign((*v1)->disp().ry()) * qMin(qAbs((*v1)->disp().ry()), temperature); // Track largest displacement for convergence detection in the caller qreal disp = qSqrt(xvel * xvel + yvel * yvel); if (disp > maxDisp) maxDisp = disp; newPos = QPointF((*v1)->x() + xvel, (*v1)->y() + yvel); qCDebug(lcLayouts) << " source vertex v1 " << (*v1)->number() << " current pos: (" << (*v1)->x() << "," << (*v1)->y() << ")" << " new pos (" << newPos.x() << "," << newPos.y() << ")"; newPos.rx() = canvasVisibleX(newPos.x()); newPos.ry() = canvasVisibleY(newPos.y()); (*v1)->setX(newPos.x()); (*v1)->setY(newPos.y()); } return maxDisp; } SocNetV-3.7/src/graph/matrices/000077500000000000000000000000001524402210300163745ustar00rootroot00000000000000SocNetV-3.7/src/graph/matrices/graph_matrix_adjacency.cpp000066400000000000000000000144161524402210300235740ustar00rootroot00000000000000/** * @file graph_matrix_adjacency.cpp * @brief Implements Graph methods for building adjacency-based matrices (adjacency and inverse adjacency) from the current network state. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2025 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Creates an adjacency matrix AM * where AM(i,j)=1 if i is connected to j * and AM(i,j)=0 if i not connected to j * @param dropIsolates * @param considerWeights * @param inverseWeights * @param symmetrize */ void Graph::createMatrixAdjacency(const bool dropIsolates, const bool considerWeights, const bool inverseWeights, const bool symmetrize) { qCDebug(lcGraphMatrices) << "Graph::createMatrixAdjacency() " << "dropIsolates" << dropIsolates << "considerWeights" << considerWeights << "inverseWeights" << inverseWeights << "symmetrize" << symmetrize; qreal m_weight = RAND_MAX; int i = 0, j = 0; int N = vertices(dropIsolates, false, true); VList::const_iterator it, jt; qCDebug(lcGraphMatrices) << "Graph::createMatrixAdjacency() -resizing AM to" << N; AM.resize(N, N); QString pMsg = tr("Creating Adjacency Matrix. \nPlease wait..."); progressStatus(pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { qCDebug(lcGraphMatrices) << "Graph::createMatrixAdjacency() - i" << i << "name" << (*it)->number(); if (progressCanceled()) { return; } if (!(*it)->isEnabled() || ((*it)->isIsolated() && dropIsolates)) { qCDebug(lcGraphMatrices) << "Graph::createMatrixAdjacency() - SKIP i" << i << "name" << (*it)->number(); continue; } j = i; for (jt = it; jt != m_graph.end(); jt++) { qCDebug(lcGraphMatrices) << "Graph::createMatrixAdjacency() - j" << j << "name" << (*jt)->number(); if (!(*jt)->isEnabled() || ((*jt)->isIsolated() && dropIsolates)) { qCDebug(lcGraphMatrices) << "Graph::createMatrixAdjacency() - SKIP j" << j << "name" << (*jt)->number(); continue; } if ((m_weight = edgeExists((*it)->number(), (*jt)->number())) != 0) { if (!considerWeights) { AM.setItem(i, j, 1); } else { if (inverseWeights) AM.setItem(i, j, 1.0 / m_weight); else AM.setItem(i, j, m_weight); } } else { AM.setItem(i, j, 0); } qCDebug(lcGraphMatrices) << " AM(" << i << "," << j << ") = " << AM.item(i, j); if (i != j) { if ((m_weight = edgeExists((*jt)->number(), (*it)->number())) != 0) { if (!considerWeights) { AM.setItem(j, i, 1); } else { if (inverseWeights) AM.setItem(j, i, 1.0 / m_weight); else AM.setItem(j, i, m_weight); } if (symmetrize && (AM.item(i, j) != AM.item(j, i))) { AM.setItem(i, j, AM.item(j, i)); } } else { AM.setItem(j, i, 0); if (symmetrize && (AM.item(i, j) != AM.item(j, i))) AM.setItem(j, i, AM.item(i, j)); } qCDebug(lcGraphMatrices) << " AM(" << j << "," << i << ") = " << AM.item(j, i); } j++; } i++; } calculatedAdjacencyMatrix = true; } /** * @brief Computes the inverse of the current adjacency matrix * @param method * @return */ bool Graph::createMatrixAdjacencyInverse(const QString &method) { qCDebug(lcGraphMatrices) << "Graph::createMatrixAdjacencyInverse() "; bool considerWeights = false; bool dropIsolates = true; // always drop isolates else AM will be singular int N = vertices(dropIsolates, false, true); createMatrixAdjacency(dropIsolates, considerWeights); if (progressCanceled()) { return false; } invAM.resize(N, N); if (method == "gauss") { // Unreachable in the current codebase - every caller passes "lu" - kept for // cross-checking only. inverseByGaussJordanElimination() has no singularity signal // of its own, so this keeps the after-the-fact "any nonzero entry" scan Fix #269 // replaced on the "lu" path below. invAM.inverseByGaussJordanElimination(AM); int i = 0, j = 0; bool isSingular = true; VList::const_iterator it, it1; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled() || (*it)->isIsolated()) continue; j = 0; for (it1 = m_graph.cbegin(); it1 != m_graph.cend(); ++it1) { if (!(*it1)->isEnabled() || (*it1)->isIsolated()) continue; if (invAM.item(i, j) != 0) isSingular = false; j++; } i++; } return !isSingular; } // Fix #269: inverse() now reports genuine singularity via a real relative pivot-magnitude // check in ludcmp(), instead of the weak "any nonzero entry in the result" heuristic above. const bool invertible = invAM.inverse(AM, [this] { return progressCanceled(); }); if (progressCanceled()) { return false; } return invertible; } SocNetV-3.7/src/graph/matrices/graph_matrix_distances.cpp000066400000000000000000000135641524402210300236330ustar00rootroot00000000000000/** * @file graph_matrix_distances.cpp * @brief Implements construction of the SIGMA (shortest-paths count) and DM * (geodesic distance) matrices for the Graph class, from cached APSP results. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Creates the matrix SIGMA of shortest paths (geodesics) between vertices * Each SIGMA(i,j) is the number of shortest paths (geodesics) from i and j * @param considerWeights * @param inverseWeights * @param dropIsolates */ void Graph::graphMatrixShortestPathsCreate(const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates) { qCDebug(lcDistances) << "Graph::graphMatrixShortestPathsCreate()"; graphDistancesGeodesic(false, considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { calculatedDistances = false; return; } VList::const_iterator it, jt; int N = vertices(dropIsolates, false, true); int source = 0, target = 0; int i = 0, j = 0; qCDebug(lcDistances) << "Graph::graphMatrixShortestPathsCreate() - Resizing matrix to hold " << N << " vertices"; SIGMA.resize(N, N); QString pMsg = tr("Creating shortest paths matrix. \nPlease wait "); progressStatus(pMsg); qCDebug(lcDistances) << "Graph::graphMatrixShortestPathsCreate() - Writing shortest paths matrix..."; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (progressCanceled()) { calculatedDistances = false; return; } source = (*it)->number(); if ((*it)->isIsolated() && dropIsolates) { qCDebug(lcDistances) << "Graph::graphMatrixShortestPathsCreate() - " << source << "isolated. SKIP"; continue; } if (!(*it)->isEnabled()) { qCDebug(lcDistances) << "Graph::graphMatrixShortestPathsCreate() - " << source << "disabled. SKIP"; continue; } qCDebug(lcDistances) << "Graph::graphMatrixShortestPathsCreate() - source" << source << "i" << i; for (jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { target = (*jt)->number(); if ((*jt)->isIsolated() && dropIsolates) { qCDebug(lcDistances) << "Graph::graphMatrixShortestPathsCreate() - " << target << "isolated. SKIP"; continue; } if (!(*jt)->isEnabled()) { qCDebug(lcDistances) << "Graph::graphMatrixShortestPathsCreate() - " << target << "disabled. SKIP"; continue; } qCDebug(lcDistances) << "Graph::graphMatrixShortestPathsCreate() - " << "target" << target << "j" << j; qCDebug(lcDistances) << "Graph::graphMatrixShortestPathsCreate() - setting SIGMA (" << i << "," << j << ") =" << apspShortestPaths(source, target); SIGMA.setItem(i, j, apspShortestPaths(source, target)); j++; } j = 0; i++; } } /** * @brief Creates the matrix DM of geodesic distances between vertices. * * Phase 1: calls graphDistancesGeodesic() which runs the DistanceEngine. * * Phase 2: fills the DM matrix from the cached per-vertex distances - an O(NΒ²) * memory-write pass. * * @param considerWeights If true, edge weights are used in distance computations. * @param inverseWeights If true, edge weights are inverted before use. * @param dropIsolates If true, isolate nodes are excluded from the analysis. * @return true on success, false if the computation was cancelled. */ bool Graph::graphMatrixDistanceGeodesicCreate(const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates) { qCDebug(lcDistances) << "Graph::graphMatrixDistanceGeodesicCreate()"; // Phase 1: compute all geodesic distances via DistanceEngine. graphDistancesGeodesic(false, considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { calculatedDistances = false; return false; } VList::const_iterator it, jt; int N = vertices(dropIsolates, false, true); int source = 0, target = 0; int i = 0, j = 0; qCDebug(lcDistances) << "Graph::graphMatrixDistanceGeodesicCreate() - " "Resizing distance matrix to hold " << N << " vertices"; DM.resize(N, N); // Phase 2: fill DM from cached per-vertex distances. progressStatus(tr("Creating geodesic distances matrix. \nPlease wait ")); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { source = (*it)->number(); if ((*it)->isIsolated() && dropIsolates) { continue; } if (!(*it)->isEnabled()) { continue; } for (jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { target = (*jt)->number(); if ((*jt)->isIsolated() && dropIsolates) { continue; } if (!(*jt)->isEnabled()) { continue; } DM.setItem(i, j, apspDistance(source, target)); j++; } j = 0; i++; } return true; } SocNetV-3.7/src/graph/matrices/graph_matrix_reachability.cpp000066400000000000000000000115421524402210300243100ustar00rootroot00000000000000/** * @file graph_matrix_reachability.cpp * @brief Implements construction of the XRM (reachability) and XM/XSM * (walks) matrices for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Creates the reachability matrix XRM */ void Graph::createMatrixReachability() { qCDebug(lcReachability) << "Creating the Reachability Matrix..."; graphDistancesGeodesic(false); if (progressCanceled()) { return; } VList::const_iterator it, jt; int N = vertices(false, false, true); int source = 0, target = 0; int i = 0, j = 0; int reachVal = 0; XRM.resize(N, N); QString pMsg = tr("Creating reachability matrix. \nPlease wait "); progressStatus(pMsg); qCDebug(lcReachability) << "Writing Reachability matrix..."; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (progressCanceled()) { return; } source = (*it)->number(); if (!(*it)->isEnabled()) { qCDebug(lcReachability) << "source vertex" << source << "disabled. SKIP"; continue; } qCDebug(lcReachability) << "source vertex" << source << "i" << i; for (jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { target = (*jt)->number(); if (!(*jt)->isEnabled()) { qCDebug(lcReachability) << "target vertex" << target << "disabled. SKIP"; continue; } qCDebug(lcReachability) << "target vertex" << target << "j" << j; reachVal = (apspDistance(source, target) != RAND_MAX) ? 1 : 0; qCDebug(lcReachability) << "Setting XRM (" << i << "," << j << ") =" << reachVal; XRM.setItem(i, j, reachVal); j++; } j = 0; i++; } } /** * @brief Computes either the "Walks of given length" or the "Total Walks" matrix. * If length>0, it computes the Walks of given length matrix, XM=AM^l * where each element (i,j) denotes the number of walks of length l between vertex i and j. * If length=0, it computes the Total Walks matrix, XSM=Sum{AM^n} where each (i,j) * denotes the total number of walks of any length between vertices i and j. * NOTE: In the latter case, this function is VERY SLOW on large networks (n>50), * since it will calculate all powers of the sociomatrix up to n-1 in order to find out all * possible walks. * @param N - dimension of the sociomatrix (number of vertices). Default is 0, in which case it will be calculated as the number of vertices in the graph. * @param length - the length of walks to be calculated. Default is 0, in which case all walks of any length will be calculated. */ void Graph::graphWalksMatrixCreate(const int &N, const int &length, const bool &dropIsolates, const bool &considerWeights, const bool &inverseWeights, const bool &symmetrize) { // Build adjacency matrix with explicit policy (BUGFIX: do not force weights) createMatrixAdjacency(dropIsolates, considerWeights, inverseWeights, symmetrize); if (progressCanceled()) { return; } if (length > 0) { qCDebug(lcReachability) << "Graph::graphWalksMatrixCreate() - " "Calculating sociomatrix power" << length; QString pMsg = tr("Computing walks of length %1. \nPlease wait...").arg(length); progressStatus(pMsg); XM = AM.pow(length, false); } else { qCDebug(lcReachability) << "Graph::graphWalksMatrixCreate() - " "Calculating all sociomatrix powers up to" << N - 1; XM = AM; // product matrix XSM = AM; // sum of product matrices QString pMsg = tr("Computing sociomatrix powers up to %1. \nPlease wait...").arg(N - 1); progressStatus(pMsg); for (int i = 2; i <= (N - 1); ++i) { progressStatus(tr("Computing all sociomatrix powers up to %1. " "Now computing A^%2. Please wait...") .arg(N - 1) .arg(i)); XM *= AM; XSM += XM; if (progressCanceled()) { return; } } } } SocNetV-3.7/src/graph/prominence/000077500000000000000000000000001524402210300167245ustar00rootroot00000000000000SocNetV-3.7/src/graph/prominence/graph_prominence_distribution.cpp000066400000000000000000000323141524402210300255520ustar00rootroot00000000000000/** * @file graph_prominence_distribution.cpp * @brief Implements prominence index distribution and visualization routines * (bars, spline, area charts) for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include #include // std::priority_queue #include // std::vector (used as the container in priority_queue) namespace { struct PromDistData { std::vector> points; // (value, frequency) double min = 0.0; double max = 0.0; double minF = 0.0; double maxF = 0.0; }; static PromDistData computePromDistData(const H_StrToInt &discreteClasses) { // priority queue (value, frequency) ordered ascending by value priority_queue, PairVFCompare> seriesPQ; for (auto it = discreteClasses.constBegin(); it != discreteClasses.constEnd(); ++it) { seriesPQ.push(PairVF(it.key().toDouble(), it.value())); } PromDistData out; const size_t initialSize = seriesPQ.size(); if (initialSize == 0) { out.minF = 0.0; out.maxF = 0.0; return out; } out.minF = static_cast(RAND_MAX); out.maxF = 0.0; size_t idx = 0; while (!seriesPQ.empty()) { const double value = seriesPQ.top().value; const double freq = seriesPQ.top().frequency; out.points.emplace_back(value, freq); if (freq < out.minF) out.minF = freq; if (freq > out.maxF) out.maxF = freq; if (idx == 0) out.min = value; if (seriesPQ.size() == 1) out.max = value; seriesPQ.pop(); ++idx; } return out; } static QVector> toQPoints(const std::vector> &pts) { QVector> out; out.reserve(static_cast(pts.size())); for (const auto &p : pts) { out.append(qMakePair(static_cast(p.first), static_cast(p.second))); } return out; } } // namespace /** * @brief Returns the IndexType of the given prominence index name * Called from MW::slotEditNodeFind, MW::slotLayoutRadialByProminenceIndex etc * @param prominenceIndexName */ int Graph::getProminenceIndexByName(const QString &prominenceIndexName) { qCDebug(lcProminence) << "Returning index type for index named: " << prominenceIndexName; if (prominenceIndexName.contains("Degree Centr")) { return IndexType::DC; } else if (prominenceIndexName.contains("Closeness Centr") && !prominenceIndexName.contains("IR")) { return IndexType::CC; } else if (prominenceIndexName.contains("Influence Range Closeness Centrality") || prominenceIndexName.contains("IR Closeness")) { return IndexType::IRCC; } else if (prominenceIndexName.contains("Betweenness Centr")) { return IndexType::BC; } else if (prominenceIndexName.contains("Stress Centr")) { return IndexType::SC; } else if (prominenceIndexName.contains("Eccentricity Centr")) { return IndexType::EC; } else if (prominenceIndexName.contains("Bonacich")) { return IndexType::BPC; } else if (prominenceIndexName.contains("Power Centr")) { return IndexType::PC; } else if (prominenceIndexName.contains("Information Centr")) { return IndexType::IC; } else if (prominenceIndexName.contains("Eigenvector Centr")) { return IndexType::EVC; } else if (prominenceIndexName.contains("Degree Prestige")) { return IndexType::DP; } else if (prominenceIndexName.contains("PageRank Prestige")) { return IndexType::PRP; } else if (prominenceIndexName.contains("Proximity Prestige")) { return IndexType::PP; } else if (prominenceIndexName.contains("Clustering Coefficient")) { return IndexType::CLC; } else if (prominenceIndexName.contains("Katz Centr")) { return IndexType::KATZ; } else return 0; } /** * @brief Computes the distribution of a centrality index score. * The distribution is stored as Qt Series depending on the SeriesType parameter type * It is send to MW through signal/slot * @param index * @param type */ void Graph::prominenceDistribution(const int &index, const ChartType &type, const QString &distImageFileName) { qCDebug(lcProminence) << "Request to compute prominence distribution. " << "index" << index << "chart type: " << type << "distImageFileName" << distImageFileName; QString pMsg = tr("Computing Centrality Distribution. \nPlease wait..."); progressStatus(pMsg); H_StrToInt discreteClasses; QString seriesName; qCDebug(lcProminence) << "setting prominence distribution series name and classes..."; switch (index) { case 0: { break; } case IndexType::DC: { seriesName = ("(out)Degree"); discreteClasses = discreteSDCs; break; } case IndexType::CC: { seriesName = ("Closeness"); discreteClasses = discreteCCs; break; } case IndexType::IRCC: { seriesName = ("IRCC"); discreteClasses = discreteIRCCs; break; } case IndexType::BC: { seriesName = ("Betweenness"); discreteClasses = discreteBCs; break; } case IndexType::SC: { seriesName = ("Stress"); discreteClasses = discreteSCs; break; } case IndexType::EC: { seriesName = ("Eccentricity"); discreteClasses = discreteECs; break; } case IndexType::PC: { seriesName = ("Power"); discreteClasses = discretePCs; break; } case IndexType::IC: { seriesName = ("Information"); discreteClasses = discreteICs; break; } case IndexType::EVC: { seriesName = ("Eigenvector"); discreteClasses = discreteEVCs; break; } case IndexType::DP: { seriesName = ("Prestige Degree"); discreteClasses = discreteDPs; break; } case IndexType::PRP: { seriesName = ("Pagerank"); discreteClasses = discretePRPs; break; } case IndexType::PP: { seriesName = ("Proximity"); discreteClasses = discretePPs; break; } case IndexType::CLC: { seriesName = ("Clustering"); discreteClasses = discreteCLCs; break; } case IndexType::KATZ: { seriesName = ("Katz"); discreteClasses = discreteKCs; break; } case IndexType::BPC: { seriesName = ("Bonacich Power"); discreteClasses = discreteBPCs; break; } } qCDebug(lcProminence) << "calling the relevant prominence distribution computation method..."; switch (type) { case ChartType::None: emit signalPromininenceDistributionChartUpdate(Q_NULLPTR, Q_NULLPTR); break; case ChartType::Spline: progressStatus(tr("Creating prominence index distribution line chart...")); prominenceDistributionSpline(discreteClasses, seriesName, distImageFileName); break; case ChartType::Area: progressStatus(tr("Creating prominence index distribution area chart...")); prominenceDistributionArea(discreteClasses, seriesName, distImageFileName); break; case ChartType::Bars: progressStatus(tr("Creating prominence index distribution bar chart...")); prominenceDistributionBars(discreteClasses, seriesName, distImageFileName); break; } } /** * @brief Computes prominence distribution data and delegates Spline chart rendering. * * Performs the algorithmic portion only: * - Orders (value, frequency) pairs derived from @p discreteClasses * - Computes min/max value and min/max frequency * * UI construction (Qt Charts series/axes), optional PNG export, and emission of * signalPromininenceDistributionChartUpdate(...) are delegated to the UI faΓ§ade * implementation in graph_ui_prominence_distribution.cpp (WS2/F4). * * Behavior and output semantics are preserved. * * @param discreteClasses Map of value (string) -> frequency. * @param seriesName Display name of the series. * @param distImageFileName If non-empty, export chart to this PNG file. */ void Graph::prominenceDistributionSpline(const H_StrToInt &discreteClasses, const QString &seriesName, const QString &distImageFileName) { qCDebug(lcProminence) << "Computing prominence distribution as spline chart..."; const PromDistData data = computePromDistData(discreteClasses); uiProminenceDistributionSpline( toQPoints(data.points), static_cast(data.min), static_cast(data.max), static_cast(data.minF), static_cast(data.maxF), seriesName, distImageFileName); } /** * @brief Computes prominence distribution data and delegates Area chart rendering. * * Performs the algorithmic portion only: * - Orders (value, frequency) pairs derived from @p discreteClasses * - Computes min/max value and min/max frequency * * UI construction (Qt Charts series/axes), optional PNG export, and emission of * signalPromininenceDistributionChartUpdate(...) are delegated to the UI faΓ§ade * implementation in graph_ui_prominence_distribution.cpp (WS2/F4). * * Behavior and output semantics are preserved. * * @param discreteClasses Map of value (string) -> frequency. * @param name Display name of the series. * @param distImageFileName If non-empty, export chart to this PNG file. */ void Graph::prominenceDistributionArea(const H_StrToInt &discreteClasses, const QString &name, const QString &distImageFileName) { qCDebug(lcProminence) << "Computing prominence distribution as area chart..."; const PromDistData data = computePromDistData(discreteClasses); // UI-only: builds QAreaSeries/QAxes, exports PNG if requested, // emits signalPromininenceDistributionChartUpdate(...) uiProminenceDistributionArea(toQPoints(data.points), data.min, data.max, data.minF, data.maxF, name, distImageFileName); } /** * @brief Computes the prominence distribution and delegates Bar chart rendering. * * This method performs only the algorithmic portion: * it derives ordered category labels (centrality values formatted with 6 decimals), * the matching frequencies, and computes basic range statistics: * - min/max value (numeric) * - min/max frequency * * UI construction (Qt Charts objects, axes, optional PNG export) and emission * of the update signal to MainWindow are delegated to the UI faΓ§ade layer * (graph_ui_prominence_distribution.cpp), following WS2/F4 rules. * * Behavior, rendering semantics, and export output remain unchanged. * * @param discreteClasses A map of centrality value (as string) to frequency. * @param name The display name of the distribution series. * @param distImageFileName If non-empty, the chart is exported to this PNG file. */ void Graph::prominenceDistributionBars(const H_StrToInt &discreteClasses, const QString &name, const QString &distImageFileName) { qCDebug(lcProminence) << "Computing prominence distribution as bar chart..."; const PromDistData data = computePromDistData(discreteClasses); // Bars need string categories (old code used QString::number(value, 'f', 6)) QStringList categories; QVector freqs; categories.reserve(static_cast(data.points.size())); freqs.reserve(static_cast(data.points.size())); for (const auto &p : data.points) { categories.append(QString::number(p.first, 'f', 6)); freqs.append(static_cast(p.second)); } uiProminenceDistributionBars(categories, freqs, static_cast(data.min), static_cast(data.max), static_cast(data.minF), static_cast(data.maxF), name, distImageFileName); } SocNetV-3.7/src/graph/reachability/000077500000000000000000000000001524402210300172255ustar00rootroot00000000000000SocNetV-3.7/src/graph/reachability/graph_reachability_walks.cpp000066400000000000000000000042141524402210300247540ustar00rootroot00000000000000/** * @file graph_reachability_walks.cpp * @brief Implements reachability analysis and walk-based algorithms * for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Returns true if vertices v1 and v2 are reachable. * * @param v1 * @param v2 * @return bool */ bool Graph::graphReachable(const int &v1, const int &v2) { qCDebug(lcReachability) << "Graph::reachable()"; graphDistancesGeodesic(false); return (apspDistance(v1, v2) != RAND_MAX) ? true : false; } // XRM (createMatrixReachability) and XM/XSM (graphWalksMatrixCreate) construction // moved to matrices/graph_matrix_reachability.cpp (WS5 A4). /** * @brief Calculates and returns the number of walks of a given length between v1 and v2 * @param v1 * @param v2 * @param length * @return */ int Graph::walksBetween(int v1, int v2, int length) { const bool dropIsolates = false; const bool considerWeights = false; // counting walks, not weight-products const bool inverseWeights = false; const bool symmetrize = false; graphWalksMatrixCreate(vertices(), length, dropIsolates, considerWeights, inverseWeights, symmetrize); return XM.item(v1 - 1, v2 - 1); } /** Returns the number of triples of vertex v1 A triple Ξ₯ at a vertex v is a path of length two for which v is the center vertex. */ qreal Graph::numberOfTriples(int v1) { qreal totalDegree = 0; if (isSymmetric()) { totalDegree = vertexEdgesOutbound(v1); return totalDegree * (totalDegree - 1.0) / 2.0; } totalDegree = vertexEdgesOutbound(v1) + vertexEdgesInbound(v1); // FIXEM return totalDegree * (totalDegree - 1.0); }SocNetV-3.7/src/graph/relations/000077500000000000000000000000001524402210300165655ustar00rootroot00000000000000SocNetV-3.7/src/graph/relations/graph_relations.cpp000066400000000000000000000326001524402210300224530ustar00rootroot00000000000000/** * @file graph_relations.cpp * @brief Implements relation management methods for the Graph class (current * relation selection, add/remove/rename, relation-based operations). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" /** * @brief Changes the current relation, and optionally emits signals to MW/GW (default: true) * * Forces all enabled vertices to disable edges in the old relation and enable edges of the new relation * * Then, if updateUI==true (default), it emits signals to MW and GW * to update the MW UI and toggle the edges on the GW, respectivelly. * * Called from Parser, Graph methods and when the user selects a relation in the MW combo box. * * @param relNum int * @param updateUI bool */ void Graph::relationSet(int relNum, const bool &updateUI) { qCDebug(lcRelations) << "++ Request to change graph to relation:" << relNum << " - current relation:" << m_curRelation << "updateUI:" << updateUI; // // Perform checks for requested new relation number // if (m_curRelation == relNum) { // Same as current, don't do nothing qCDebug(lcRelations) << "++ Requested relation is the current one - END"; return; } if (relNum < 0) { // negative, don't do nothing qCDebug(lcRelations) << "++ Requested relation is negative - END "; return; } else if (relNum == RAND_MAX) { // Set relation to the last existing relation relNum = relations() - 1; } else if (relNum > relations() - 1) { // Invalid relation, abort qCDebug(lcRelations) << "++ Invalid relation - END "; return; } // // Force enabled vertices to disable all edges // in the old relation and enable edges in the new relation // // Collected into one batch and dispatched once at the end (WS3 M2) rather than emitting // per-edge - a relation switch on a large network can touch thousands of edges, and each // GraphicsWidget-bound signal crosses from graphThread to the GUI thread as a queued call. // QList visibilityChanges; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { qCDebug(lcRelations) << "++ changing relation of vertex" << (*it)->number() << "to" << relNum; if (!(*it)->isEnabled()) continue; visibilityChanges.append((*it)->setRelation(relNum)); } notifyEdgesVisibilityBatch(visibilityChanges); // // Save the current relation's directed state before switching. // if (m_curRelation >= 0 && m_curRelation < m_relationsDirected.size()) m_relationsDirected[m_curRelation] = m_graphIsDirected; // // now store the selected relation // m_curRelation = relNum; // // Restore directed state for the new relation. // m_graphIsDirected = m_relationsDirected.value(relNum, true); // // Invalidate the weighted cache so isWeighted() re-scans the new relation's edges. // Without this, isWeighted() returns the cached result from the previous relation. // calculatedGraphWeighted = false; // // Invalidate the symmetry cache and recompute from actual edges of the new relation, // so m_graphIsSymmetric is correct for any subsequent setUndirected() call. // calculatedGraphSymmetry = false; isSymmetric(); // // Check if isWeighted so that multiple-relation networks are properly loaded. // isWeighted(); // // Check if we need to update the UI // if (updateUI) { qCDebug(lcRelations) << "++ Signaling to update UI and GW and setting graph mod status to edge count changed."; // Notify MW to change combo box relation name emit signalRelationChangedToMW(m_curRelation); // Notify MW to update the edge-mode combo to match the new relation's directed state emit signalGraphDirectedChanged(m_graphIsDirected); // notify GW to disable/enable the on screen edges. emit signalRelationChangedToGW(m_curRelation); // update graph mod status setModStatus(ModStatus::EdgeCount); } } /** * @brief Changes graph to previous relation */ void Graph::relationPrev() { qCDebug(lcRelations) << "Changing to the previous relation..."; int relNum = m_curRelation; if (m_curRelation > 0) { --relNum; relationSet(relNum); // editFilterNodesIsolatesAct->setChecked(false); } } /** * @brief Changes graph to next relation */ void Graph::relationNext() { qCDebug(lcRelations) << "Changing to the next relation..."; int relNum = m_curRelation; if (relations() > 0 && relNum < relations()) { ++relNum; relationSet(relNum); // editFilterNodesIsolatesAct->setChecked(false); } } /** * @brief Adds a new relation to the graph * * Adds a new relation named relName, emitting signal to MW UI, and * optionally changing current graph relation to the new one. * Called by file parser and various Graph methods, i.e clear() etc. * * @param relName */ void Graph::relationAdd(const QString &relName, const bool &changeRelation) { qCDebug(lcRelations) << "Adding new relation named:" << relName; // Add the new relation to our relations list m_relationsList << relName; m_relationsDirected << m_graphIsDirected; // Emit signal for the new relation to be added to the MW UI combo... emit signalRelationAddToMW(relName); // Check if we need to change to the new relation... if (changeRelation) { relationSet(); } progressStatus((tr("Added a new relation named: %1.")) .arg(relName)); } /** * @brief Gets the current relation number * * @return int */ int Graph::relationCurrent() { return m_curRelation; } /** * @brief Gets the current relation name * * @return string */ QString Graph::relationCurrentName() const { return m_relationsList.value(m_curRelation); } /** * @brief Renames current relation to newName, optionally emitting a signal to MW * @param newName */ void Graph::relationCurrentRename(const QString &newName, const bool &signalMW) { // // Check if new name is the same // if (!m_relationsList.isEmpty() && newName == m_relationsList[m_curRelation]) { qCDebug(lcRelations) << "The new name of the relation is the same as the current name. Nothing to do. Returning."; return; } // // Check if new name is empty // if (newName.isEmpty()) { qCDebug(lcRelations) << "The new name of the relation is empty. Nothing to do. Returning."; return; } // // Rename current relation to newName // qCDebug(lcRelations) << "Renaming current relation:" << m_curRelation << "to:" << newName << " - signalMW:" << signalMW; m_relationsList[m_curRelation] = newName; // // Check if we need to emit a signal // if (signalMW) { emit signalRelationRenamedToMW(newName); } } /** * @brief Overload. Renames current relation to newName, without emitting any signal to MW * * @param newName */ void Graph::relationCurrentRename(const QString &newName) { relationCurrentRename(newName, false); } /** * @brief Returns the count of relationships in this Graph * * @return int */ int Graph::relations() { return m_relationsList.size(); } /** * @brief Clears relationships in this Graph */ void Graph::relationsClear() { int oldRelationsCounter = m_relationsList.size(); m_relationsList.clear(); m_relationsDirected.clear(); m_curRelation = 0; qCDebug(lcRelations) << "Cleared" << oldRelationsCounter << "relation(s)" << "Emitting signalRelationsClear()"; emit signalRelationsClear(); } /** * @brief Creates a new symmetric relation by keeping only strong-ties (mutual links) * in the current relation. In the new relation, two actors are connected only if * they are mutually connected in the current relation. * @param allRelations */ void Graph::addRelationSymmetricStrongTies(const bool &allRelations) { qCDebug(lcRelations) << "Creating new relation using strong ties only." << "initial relations" << relations(); int y = 0, v2 = 0, v1 = 0, weight; qreal invertWeight = 0; VList::const_iterator it; QHash outEdgesAll; QHash::const_iterator it1; QHash *strongTies = new QHash; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { v1 = (*it)->number(); qCDebug(lcRelations) << "Graph::addRelationSymmetricStrongTies() - v" << v1 << "iterate over outEdges in all relations"; outEdgesAll = (*it)->outEdgesEnabledHash(allRelations); // outEdgesAllRelationsUniqueHash(); it1 = outEdgesAll.cbegin(); while (it1 != outEdgesAll.cend()) { v2 = it1.key(); weight = it1.value(); y = vpos[v2]; qCDebug(lcRelations) << "" << v1 << "->" << v2 << "=" << weight << "Checking opposite."; invertWeight = m_graph[y]->hasEdgeTo(v1, allRelations); if (invertWeight == 0) { qCDebug(lcRelations) << v1 << "<-" << v2 << " does not exist. Weak tie. Continue."; } else { if (!strongTies->contains(QString::number(v1) + "--" + QString::number(v2)) && !strongTies->contains(QString::number(v2) + "--" + QString::number(v1))) { qCDebug(lcRelations) << v1 << "--" << v2 << " exists. Strong Tie. Adding"; strongTies->insert(QString::number(v1) + "--" + QString::number(v2), 1); } else { qCDebug(lcRelations) << v1 << "--" << v2 << " exists. Strong Tie already found. Continue"; } } ++it1; } } relationAdd("Strong Ties", true); QHash::const_iterator it2; it2 = strongTies->constBegin(); QStringList vertices; qCDebug(lcRelations) << "creating strong tie edges..."; while (it2 != strongTies->constEnd()) { vertices = it2.key().split("--"); v1 = (vertices.at(0)).toInt(); v2 = (vertices.at(1)).toInt(); edgeCreate(v1, v2, 1, initEdgeColor, EdgeType::Undirected, true, false, QString(), false); ++it2; } // delete outEdgesAll; delete strongTies; m_graphIsSymmetric = true; setModStatus(ModStatus::EdgeCount); } /** * @brief Creates a new symmetric relation by connecting those actors * that are cocitated by others. * In the new relation, an edge will exist between actor i and actor j * only if C(i,j) > 0, where C the Cocitation Matrix. * Thus the actor pairs cited by more common neighbors will appear * with a stronger tie between them than pairs those cited by fewer * common neighbors. The resulting relation is symmetric. */ void Graph::relationAddCocitation() { qCDebug(lcRelations) << "Graph::relationAddCocitation()" << "initial relations" << relations(); int v1 = 0, v2 = 0, i = 0, j = 0, weight; bool dropIsolates = false; createMatrixAdjacency(); if (progressCanceled()) { return; } Matrix *CT = new Matrix(AM.rows(), AM.cols()); *CT = AM.cocitationMatrix(); // CT->printMatrixConsole(true); VList::const_iterator it, it1; relationAdd("Cocitation", true); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled() || ((*it)->isIsolated() && dropIsolates)) { continue; } v1 = (*it)->number(); j = 0; for (it1 = m_graph.cbegin(); it1 != m_graph.cend(); it1++) { qCDebug(lcRelations) << "Graph::relationAddCocitation() - (i,j)" << i + 1 << j + 1; if (!(*it1)->isEnabled() || ((*it1)->isIsolated() && dropIsolates)) { continue; } v2 = (*it1)->number(); if (v1 == v2) { j++; qCDebug(lcRelations) << "Graph::relationAddCocitation() - skipping self loop" << v1 << v2; continue; } if ((weight = CT->item(i, j)) != 0) { qCDebug(lcRelations) << "Graph::relationAddCocitation() - creating edge" << v1 << "<->" << v2 << "because CT(" << i + 1 << "," << j + 1 << ") = " << weight; edgeCreate(v1, v2, weight, initEdgeColor, EdgeType::Undirected, true, false, QString(), false); } j++; } i++; } delete CT; m_graphIsSymmetric = true; setModStatus(ModStatus::EdgeCount); qCDebug(lcRelations) << "Graph::relationAddCocitation()" << "final relations" << relations(); } SocNetV-3.7/src/graph/reporting/000077500000000000000000000000001524402210300165765ustar00rootroot00000000000000SocNetV-3.7/src/graph/reporting/graph_reports.cpp000066400000000000000000006734641524402210300222050ustar00rootroot00000000000000/** * @file graph_reports.cpp * @brief Implements reporting, export, and analytical output generation * for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include "graph/io/table_export.h" #include #include #include #include #include #include /** * @brief Writes reciprocity report to filename * @param fileName * @param considerWeights */ bool Graph::writeReciprocity(const QString fileName, const bool considerWeights, const int &format) { qCDebug(lcReporting) << "Writing reciprocity report to file:" << fileName; Q_UNUSED(considerWeights); QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); m_graphReciprocityArc = graphReciprocity(); int N = vertices(); // Shared by both the CSV and HTML branches below - single source of truth for the per-actor // ratios (previously duplicated separately in each branch). // // Bug fix: tiesSym used to divide by (outEdgesCount() + inEdgesCount()) with no zero-guard, // unlike every other ratio here. A node with zero total ties (in the directed sense - no // inbound or outbound edges at all) hit 0/0, producing a literal "nan" in both the HTML and // CSV output. Guarded the same way as the other four ratios: no ties means 0, not NaN. auto rowValues = [](GraphVertex *v) -> QVector { const qreal totalTies = v->outEdgesCount() + v->inEdgesCount(); const qreal tiesSym = (totalTies != 0) ? (qreal)v->outEdgesReciprocated() / totalTies : 0; const qreal tiesNonSym = (totalTies != 0) ? 1 - tiesSym : 0; const qreal tiesOutNonSym = (v->outEdgesNonSym() || v->inEdgesNonSym()) ? (qreal)v->outEdgesNonSym() / (qreal)(v->outEdgesNonSym() + v->inEdgesNonSym()) : 0; const qreal tiesInNonSym = (v->outEdgesNonSym() || v->inEdgesNonSym()) ? (qreal)v->inEdgesNonSym() / (qreal)(v->outEdgesNonSym() + v->inEdgesNonSym()) : 0; const qreal tiesOutNonSymTotalOut = (v->outEdgesCount() != 0) ? (qreal)v->outEdgesNonSym() / (qreal)v->outEdgesCount() : 0; const qreal tiesInNonSymTotalIn = (v->inEdgesCount() != 0) ? (qreal)v->inEdgesNonSym() / (qreal)v->inEdgesCount() : 0; return {tiesSym, tiesNonSym, tiesOutNonSym, tiesInNonSym, tiesOutNonSymTotalOut, tiesInNonSymTotalIn}; }; const QStringList dataColumnHeaders = {"Symmetric", "nonSymmetric", "nsym out/nsym", "nsym in/nsym", "nsym out/out", "nsym in/in"}; if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, dataColumnHeaders, rowValues); file.close(); return true; } QString pMsg = tr("Writing Reciprocity to file. \nPlease wait..."); progressStatus(pMsg); outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); outText << "

"; outText << tr("RECIPROCITY (r) REPORT"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("Reciprocity, r, is a measure of the likelihood of vertices " "in a directed network to be mutually linked.
" "SocNetV supports two different methods to index the degree of " "reciprocity in a social network:
" "- The arc reciprocity, which is the fraction of " "reciprocated ties over all actual ties in the network.
" "- The dyad reciprocity which is the fraction of " "actor pairs that have reciprocated ties over all " "pairs of actors that have any connection.
" "In a directed network, the arc reciprocity measures the proportion " "of directed edges that are bidirectional. If the reciprocity is 1, " "then the adjacency matrix is structurally symmetric.
" "Likewise, in a directed network, the dyad reciprocity measures " "the proportion of connected actor dyads that have bidirectional ties " "between them.
" "In an undirected graph, all edges are reciprocal. Thus the " "reciprocity of the graph is always 1.
" "Reciprocity can be computed on undirected, directed, and weighted graphs.") << "

"; outText << "

" << "" << tr("r range: ") << "" << tr("0 ≤ r ≤ 1") << "

"; outText << "

" << "" << tr("Arc reciprocity: ") << "" << tr("%1 / %2 = %3").arg(m_graphReciprocityTiesReciprocated).arg(m_graphReciprocityTiesTotal).arg(m_graphReciprocityArc) << "
" << tr("Of all actual ties in the network, %1% are reciprocated.").arg(m_graphReciprocityArc * 100) << "

"; outText << "

" << "" << tr("Dyad reciprocity: ") << "" << tr("%1 / %2 = %3").arg(m_graphReciprocityPairsReciprocated).arg(m_graphReciprocityPairsTotal).arg(m_graphReciprocityDyad) << "
" << tr("Of all pairs of actors that have any ties, %1% have a reciprocated connection.").arg(m_graphReciprocityDyad * 100) << "

"; outText << "

" << "
" << "" << tr("Reciprocity proportions per actor: ") << "" << "

"; writeScoreTableHTML(outText, dataColumnHeaders, rowValues); outText << "

"; outText << "" << tr("Symmetric ") << "" << tr("Proportion of reciprocated ties involving the actor to the total incoming and outgoing ties.") << "
" << "" << tr("nonSymmetric ") << "" << tr("One minus symmetric") << "
" << "" << tr("nonSym Out/NonSym ") << "" << tr("Proportion of non-symmetric outgoing ties to the total non-symmetric ties.") << "
" << "" << tr("nonSym In/NonSym ") << "" << tr("Proportion of non-symmetric incoming ties to the total non-symmetric ties.") << "
" << "" << tr("nonSym Out/Out ") << "" << tr("Proportion of non-symmetric outgoing ties to the total outgoing ties.") << "
" << "" << tr("nonSym In/In ") << "" << tr("Proportion of non-symmetric incoming ties to the total incoming ties") << "
"; outText << "

"; outText << "

 

"; outText << "

"; outText << tr("Reciprocity Report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the Eccentricity report to file * @param fileName * @param considerWeights * @param inverseWeights * @param dropIsolates */ bool Graph::writeEccentricity(const QString fileName, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing Eccentricity report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); if (!calculatedCentralities) { graphDistancesGeodesic(true, considerWeights, inverseWeights, dropIsolates); } if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"e"}, [](GraphVertex *v) -> QVector { const qreal e = v->eccentricity(); return {(e == 0) ? (qreal)RAND_MAX : e}; }, nullptr, [](GraphVertex *v) { return !v->isEnabled(); }); file.close(); return true; } int N = vertices(); QString pMsg = tr("Writing Eccentricity scores to file. \nPlease wait..."); progressStatus(pMsg); outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); outText << "

"; outText << tr("ECCENTRICITY (e) REPORT"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The eccentricity e measures how far, at most, is each " " node from every other node.
" "In a connected graph, the eccentricity e of a vertex " "is the maximum geodesic distance between that vertex and all other vertices.
" "In a disconnected graph, the eccentricity e of all vertices " "is considered to be infinite.") << "

"; outText << "

" << "" << tr("e range: ") << "" << tr("1 ≤ e ≤ \xE2\x88\x9E") << "

"; writeScoreTableHTML(outText, {"e"}, [](GraphVertex *v) -> QVector { const qreal e = v->eccentricity(); return {(e == 0) ? (qreal)RAND_MAX : e}; }, nullptr, [](GraphVertex *v) { return !v->isEnabled(); }); if (minEccentricity == maxEccentricity) { outText << "

" << tr("All nodes have the same eccentricity.") << "

"; } else { outText << "

"; outText << "" << tr("Max e (Graph Diameter) = ") << "" << maxEccentricity << " (node " << maxNodeEccentricity << ")" << "
" << "" << tr("Min e (Graph Radius) = ") << "" << minEccentricity << " (node " << minNodeEccentricity << ")" << "
" << "" << tr("e classes = ") << "" << classesEccentricity << "

"; } outText << "

"; outText << "" << tr("e = 1 ") << "" << tr("when the node is connected to all others (star node).") << "
" << "" << tr("e > 1 ") << "" << tr("when the node is not directly connected to all others. " "Larger eccentricity means the actor is farther from others.") << "
" << "" << tr("e = \xE2\x88\x9E ") << "" << tr("there is no path from that node to one or more other nodes.") << "
"; outText << "

"; outText << "

 

"; outText << "

"; outText << tr("Eccentricity Report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Renders a per-node score table as a sortable HTML , shared by every * centrality/prestige report writer (WS16 Step 2) plus Reciprocity/Clustering * Coefficient/Eccentricity (WS16 Step 3). "Node" and "Label" are always the * first two columns; @p dataColumnHeaders supplies the rest, in order, and * @p rowValues must return that many values per row, in the same order - a * value equal to RAND_MAX renders as the infinity glyph, matching * writeMatrixHTMLTable()'s sentinel convention. @p isBlanked (optional) lets * each report keep its own existing isolate-handling: rows for which it * returns true get "--" placeholders instead of @p rowValues's output. * @param outText * @param dataColumnHeaders * @param rowValues * @param isBlanked */ void Graph::writeScoreTableHTML(QTextStream &outText, const QStringList &dataColumnHeaders, const std::function(GraphVertex *)> &rowValues, const std::function &isBlanked, const std::function &isSkipped) { const int dataCols = dataColumnHeaders.size(); const int totalCols = dataCols + 2; outText << "
"; outText << "" << ""; for (int c = 0; c < totalCols; ++c) { outText << ""; } outText << "" << "" << ""; int rowCount = 0; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { GraphVertex *v = *it; if (isSkipped && isSkipped(v)) continue; rowCount++; outText << Qt::fixed; outText << "" << ""; if (isBlanked && isBlanked(v)) { for (int c = 0; c < dataCols; ++c) outText << ""; } else { const QVector values = rowValues(v); for (int c = 0; c < dataCols; ++c) { outText << ""; } } outText << ""; } outText << "
" << (c == 0 ? tr("Node") : c == 1 ? tr("Label") : dataColumnHeaders.at(c - 2)) << "
" << v->number() << "" << ((!(v->label().simplified()).isEmpty()) ? v->label().simplified().left(m_reportsLabelLength) : "-") << "--"; if (values.at(c) == RAND_MAX) outText << infinity; else outText << values.at(c); outText << "
"; } /** * @brief CSV sibling of writeScoreTableHTML() - same column shape and semantics, * comma-delimited, no HTML markup, same RAND_MAX-as-infinity convention. * Reuses TableExport::csvQuote() for the Label column, the only free-text * field (Node numbers and scores are always numeric, so they never need * escaping). * @param outText * @param dataColumnHeaders * @param rowValues * @param isBlanked * @param isSkipped */ void Graph::writeScoreTableCSV(QTextStream &outText, const QStringList &dataColumnHeaders, const std::function(GraphVertex *)> &rowValues, const std::function &isBlanked, const std::function &isSkipped) { QStringList header; header << tr("Node") << tr("Label") << dataColumnHeaders; outText << header.join(",") << "\n"; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { GraphVertex *v = *it; if (isSkipped && isSkipped(v)) continue; QStringList row; row << QString::number(v->number()) << TableExport::csvQuote(v->label().simplified()); if (isBlanked && isBlanked(v)) { for (int c = 0; c < dataColumnHeaders.size(); ++c) row << QString(); } else { const QVector values = rowValues(v); for (const qreal &val : values) row << ((val == RAND_MAX) ? infinity : QString::number(val, 'f', m_reportsRealPrecision)); } outText << row.join(",") << "\n"; } } /** * @brief Writes the information centralities to file * @param fileName * @param considerWeights * @param inverseWeights */ bool Graph::writeCentralityInformation(const QString fileName, const bool considerWeights, const bool inverseWeights, const int &format) { qCDebug(lcReporting) << "Writing Information Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); centralityInformation(considerWeights, inverseWeights); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"IC", "IC'", "%IC"}, [](GraphVertex *v) -> QVector { return {v->IC(), v->SIC(), 100 * v->SIC()}; }, [](GraphVertex *v) { return v->isIsolated(); }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::IC, m_reportsChartType, distImageFileName); bool dropIsolates = true; // by default IC needs to exclude isolates int N = vertices(dropIsolates, false, true); QString pMsg = tr("Writing Information Centralities to file. \nPlease wait..."); progressStatus(pMsg); outText.setRealNumberPrecision(m_reportsRealPrecision); outText << htmlHead; outText << "

"; outText << tr("INFORMATION CENTRALITY (IC)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The IC index, introduced by Stephenson and Zelen (1991), measures the " "information flow through all paths between actors weighted by " "strength of tie and distance.") << "
" << tr("IC' is the standardized index (IC divided by the sumIC).") << "
" << tr("Warning: To compute this index, SocNetV drops all isolated " "nodes and symmetrizes (if needed) the adjacency matrix.
" "Read the Manual for more.") << "

"; outText << "

" << "" << tr("IC range: ") << "" << tr("0 ≤ IC ≤ \xE2\x88\x9E") << "

"; outText << "

" << "" << tr("IC' range: ") << "" << tr("0 ≤ IC' ≤ 1") << "

"; writeScoreTableHTML(outText, {"IC", "IC'", "%IC"}, [](GraphVertex *v) -> QVector { return {v->IC(), v->SIC(), 100 * v->SIC()}; }, [](GraphVertex *v) { return v->isIsolated(); }); if (minIC == maxIC) { outText << "

" << tr("All nodes have the same IC score.") << "

"; } else { outText << "

"; outText << "" << tr("Max IC' = ") << "" << maxIC << " (node " << maxNodeIC << ")" << "
" << "" << tr("Min IC' = ") << "" << minIC << " (node " << minNodeIC << ")" << "
" << "" << tr("IC classes = ") << "" << classesIC << "

"; } outText << "

"; outText << "" << tr("IC' Sum = ") << "" << sumIC << "
" << "" << tr("IC' Mean = ") << "" << meanIC << "
" << "" << tr("IC' Variance = ") << "" << varianceIC << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("IC' DISTRIBUTION") << "

"; outText << "

"; outText << ""; } outText << "

"; outText << tr("GROUP INFORMATION CENTRALIZATION (GIC)") << "

"; outText << "

" << tr("Since there is no way to compute Group Information Centralization,
" "you can use Variance as a general centralization index.

") << "" << tr("Variance = ") << "" << varianceIC << "

"; outText << "

" << tr("Variance = 0, when all nodes have the same IC value, i.e. a " "complete or a circle graph).
") << tr("Larger values of variance suggest larger variability between the " "IC' values.
") << "(Wasserman & Faust, formula 5.20, p. 197)\n\n" << "

"; outText << "

 

"; outText << "

"; outText << tr("Information Centrality report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the eigenvector centralities to a file * @param fileName * @param considerWeights * @param inverseWeights * @param dropIsolates */ bool Graph::writeCentralityEigenvector(const QString fileName, const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing Eigenvector Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); centralityEigenvector(considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { // No isolate-blanking here, matching this report's existing HTML behaviour - // unlike its siblings, Eigenvector never blanked isolate rows even though it // takes a dropIsolates parameter. writeScoreTableCSV(outText, {"EVC", "EVC'", "EVC''", "%EVC'"}, [this](GraphVertex *v) -> QVector { return {v->EVC(), v->SEVC(), v->EVC() / (sumEVC ? sumEVC : 1), 100 * v->SEVC()}; }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::EVC, m_reportsChartType, distImageFileName); int N = vertices(); QString pMsg = tr("Writing Eigenvector Centrality scores to file. \nPlease wait..."); progressStatus(pMsg); outText.setRealNumberPrecision(m_reportsRealPrecision); outText << htmlHead; outText << "

"; outText << tr("EIGENVECTOR CENTRALITY (EVC)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The Eigenvector Centrality of each node is the ith element of " "the leading eigenvector of the adjacency matrix, that is the " "eigenvector corresponding to the largest positive eigenvalue.
" "Proposed by Bonacich (1972), the Eigenvector Centrality is " "an extension of the simpler Degree Centrality because it gives " "each actor a score proportional to the scores of its neighbors. " "Thus, a node may have high EVC score if it has lots of ties or " "it has ties to other nodes with high EVC.
" "The eigenvector centralities are also known as Gould indices.") << "
" << tr("EVC' is the scaled EVC (EVC divided by max EVC).") << "
" << tr("EVC'' is the standardized index (EVC divided by the sum of all EVCs).") << "
" << "

"; outText << "

" << "" << tr("EVC range: ") << "" << tr("0 ≤ EVC < 1 (The eigenvector has unit euclidean length) ") << "

"; outText << "

" << "" << tr("EVC' range: ") << "" << tr("0 ≤ EVC' ≤ 1") << "

"; writeScoreTableHTML(outText, {"EVC", "EVC'", "EVC''", "%EVC'"}, [this](GraphVertex *v) -> QVector { return {v->EVC(), v->SEVC(), v->EVC() / (sumEVC ? sumEVC : 1), 100 * v->SEVC()}; }); if (minEVC == maxEVC) { outText << "

" << tr("All nodes have the same EVC score.") << "

"; } else { outText << "

"; outText << "" << tr("Max EVC = ") << "" << maxEVC << " (node " << maxNodeEVC << ")" << "
" << "" << tr("Min EVC = ") << "" << minEVC << " (node " << minNodeEVC << ")" << "
" << "" << tr("EVC classes = ") << "" << classesEVC << "

"; } outText << "

"; outText << "" << tr("EVC Sum = ") << "" << sumEVC << "
" << "" << tr("EVC Mean = ") << "" << meanEVC << "
" << "" << tr("EVC Variance = ") << "" << varianceEVC << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("EVC' DISTRIBUTION") << "

"; outText << "

"; outText << ""; } outText << "

"; outText << tr("GROUP EIGENVECTOR CENTRALIZATION (GEC)") << "

"; outText << "

" << tr("Since there is no way to compute Group Eigenvector Centralization,
" "you can use Variance as a general centralization index.

") << "" << tr("Variance = ") << "" << varianceEVC << "

"; outText << "

" << tr("Variance = 0, when all nodes have the same EVC value, i.e. a " "complete or a circle graph).
") << tr("Larger values of variance suggest larger variability between the " "EVC' values.
") << "

"; outText << "

 

"; outText << "

"; outText << tr("Eigenvector Centrality report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the Katz Centrality report to a file, then displays it. * @param fileName * @param alpha * @param considerWeights * @param inverseWeights * @param dropIsolates * @param format */ bool Graph::writeCentralityKatz(const QString fileName, const qreal &alpha, const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing Katz Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); centralityKatz(alpha, considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"KC", "KC'", "KC''", "%KC'"}, [this](GraphVertex *v) -> QVector { return {v->KC(), v->SKC(), v->KC() / (sumKC ? sumKC : 1), 100 * v->SKC()}; }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::KATZ, m_reportsChartType, distImageFileName); int N = vertices(); QString pMsg = tr("Writing Katz Centrality scores to file. \nPlease wait..."); progressStatus(pMsg); outText.setRealNumberPrecision(m_reportsRealPrecision); outText << htmlHead; outText << "

"; outText << tr("KATZ CENTRALITY (KC)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "
" << "" << tr("Alpha: ") << "" << alpha << "

"; outText << "

" << tr("Katz Centrality gives each actor credit for indirect connections, not just " "direct ones, discounted the further away they are: a direct tie counts full " "price, a 2-hop tie counts alpha times as much, a 3-hop tie counts " "alpha2 times as much, and so on.
" "Proposed by Katz (1953), it generalizes Degree Centrality by counting " "walks of every length, not just length 1.") << "
" << tr("KC' is the scaled KC (KC divided by max KC).") << "
" << tr("KC'' is the standardized index (KC divided by the sum of all KCs).") << "
" << "

"; outText << "

" << "" << tr("KC' range: ") << "" << tr("0 ≤ KC' ≤ 1") << "

"; writeScoreTableHTML(outText, {"KC", "KC'", "KC''", "%KC'"}, [this](GraphVertex *v) -> QVector { return {v->KC(), v->SKC(), v->KC() / (sumKC ? sumKC : 1), 100 * v->SKC()}; }); if (minKC == maxKC) { outText << "

" << tr("All nodes have the same KC score.") << "

"; } else { outText << "

"; outText << "" << tr("Max KC = ") << "" << maxKC << " (node " << maxNodeKC << ")" << "
" << "" << tr("Min KC = ") << "" << minKC << " (node " << minNodeKC << ")" << "
" << "" << tr("KC classes = ") << "" << classesKC << "

"; } outText << "

"; outText << "" << tr("KC Sum = ") << "" << sumKC << "
" << "" << tr("KC Mean = ") << "" << meanKC << "
" << "" << tr("KC Variance = ") << "" << varianceKC << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("KC' DISTRIBUTION") << "

"; outText << "

"; outText << ""; } outText << "

 

"; outText << "

"; outText << tr("Katz Centrality report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } bool Graph::writeCentralityBonacich(const QString fileName, const qreal &alpha, const qreal &beta, const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing Bonacich Power Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); centralityBonacich(alpha, beta, considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"BPC", "BPC'", "BPC''", "%BPC'"}, [this](GraphVertex *v) -> QVector { return {v->BPC(), v->SBPC(), v->BPC() / (sumBPC ? sumBPC : 1), 100 * v->SBPC()}; }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::BPC, m_reportsChartType, distImageFileName); int N = vertices(); QString pMsg = tr("Writing Bonacich Power Centrality scores to file. \nPlease wait..."); progressStatus(pMsg); outText.setRealNumberPrecision(m_reportsRealPrecision); outText << htmlHead; outText << "

"; outText << tr("BONACICH POWER CENTRALITY (BPC)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "
" << "" << tr("Alpha: ") << "" << alpha << "
" << "" << tr("Beta: ") << "" << beta << "

"; outText << "

" << tr("Bonacich Power Centrality is like Katz Centrality - credit for indirect " "connections, discounted the further away they are - but beta can be " "negative, in which case being tied to well-connected others " "hurts rather than helps a node's score (e.g. a buyer connected to " "powerful sellers has less bargaining power the more powerful those sellers " "are). alpha is a free overall scale factor that does not change the ranking, " "only the numbers' size.") << "
" << tr("BPC' is the scaled BPC (BPC divided by max BPC).") << "
" << tr("BPC'' is the standardized index (BPC divided by the sum of all BPCs).") << "
" << "

"; if (beta < 0) { outText << "

" << "" << tr("Note: ") << "" << tr("beta is negative, so BPC scores may be negative and BPC' is not " "guaranteed to fall within [0, 1].") << "

"; } else { outText << "

" << "" << tr("BPC' range: ") << "" << tr("0 ≤ BPC' ≤ 1") << "

"; } writeScoreTableHTML(outText, {"BPC", "BPC'", "BPC''", "%BPC'"}, [this](GraphVertex *v) -> QVector { return {v->BPC(), v->SBPC(), v->BPC() / (sumBPC ? sumBPC : 1), 100 * v->SBPC()}; }); if (minBPC == maxBPC) { outText << "

" << tr("All nodes have the same BPC score.") << "

"; } else { outText << "

"; outText << "" << tr("Max BPC = ") << "" << maxBPC << " (node " << maxNodeBPC << ")" << "
" << "" << tr("Min BPC = ") << "" << minBPC << " (node " << minNodeBPC << ")" << "
" << "" << tr("BPC classes = ") << "" << classesBPC << "

"; } outText << "

"; outText << "" << tr("BPC Sum = ") << "" << sumBPC << "
" << "" << tr("BPC Mean = ") << "" << meanBPC << "
" << "" << tr("BPC Variance = ") << "" << varianceBPC << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("BPC' DISTRIBUTION") << "

"; outText << "

"; outText << ""; } outText << "

 

"; outText << "

"; outText << tr("Bonacich Power Centrality report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the Degree Centrality to a file * @param fileName * @param considerWeights * @param dropIsolates */ bool Graph::writeCentralityDegree(const QString fileName, const bool considerWeights, const bool dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing Degree Centrality report to file:" << fileName << "considerWeights:" << considerWeights << "dropIsolates:" << dropIsolates; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); centralityDegree(considerWeights, dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"DC", "DC'", "%DC'"}, [](GraphVertex *v) -> QVector { return {v->DC(), v->SDC(), 100 * v->SDC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::DC, m_reportsChartType, distImageFileName); qreal maxIndexDC = vertices(dropIsolates) - 1.0; int N = vertices(); outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); QString pMsg = tr("Writing out-Degree Centralities. \nPlease wait..."); progressStatus(pMsg); outText << "

"; outText << tr("DEGREE CENTRALITY (DC) REPORT"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("In undirected networks, the DC index is the sum of edges attached to a node u.
" "In directed networks, the index is the sum of outbound arcs from node u " "to all adjacent nodes (also called \"outDegree Centrality\").
" "If the network is weighted, the DC score is the sum of weights of outbound " "edges from node u to all adjacent nodes.
" "Note: To compute inDegree Centrality, use the Degree Prestige measure.") << "
" << tr("DC' is the standardized index (DC divided by N-1 (non-valued nets) or by sumDC (valued nets).") << "

"; outText << "

" << "" << tr("DC range: ") << "" << tr("0 ≤ DC ≤ "); if (considerWeights) outText << infinity; else outText << maxIndexDC; outText << "

"; outText << "

" << "" << tr("DC' range: ") << "" << tr("0 ≤ DC' ≤ 1") << "

"; writeScoreTableHTML(outText, {"DC", "DC'", "%DC'"}, [](GraphVertex *v) -> QVector { return {v->DC(), v->SDC(), 100 * v->SDC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); if (minSDC == maxSDC) { outText << "

" << tr("All nodes have the same DC score.") << "

"; } else { outText << "

"; outText << "" << tr("DC Sum = ") << "" << sumDC << "

"; outText << "

"; outText << "" << tr("Max DC' = ") << "" << maxSDC << " (node " << maxNodeSDC << ")" << "
" << "" << tr("Min DC' = ") << "" << minSDC << " (node " << minNodeSDC << ")" << "
" << "" << tr("DC' classes = ") << "" << classesSDC << "

"; } outText << "

"; outText << "" << tr("DC' Sum = ") << "" << sumSDC << "
" << "" << tr("DC' Mean = ") << "" << meanSDC << "
" << "" << tr("DC' Variance = ") << "" << varianceSDC << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("DC' DISTRIBUTION") << "

"; outText << "

"; outText << ""; } if (!considerWeights) { outText << "

"; outText << tr("GROUP DEGREE CENTRALIZATION (GDC)") << "

"; outText << "

"; outText << "" << tr("GDC = ") << "" << groupDC << "

"; outText << "

" << "" << tr("GDC range: ") << "" << " 0 ≤ GDC ≤ 1" << "

"; outText << "

" << tr("GDC = 0, when all out-degrees are equal (i.e. regular lattice).") << "
" << tr("GDC = 1, when one node completely dominates or overshadows the other nodes.") << "
" << "(Wasserman & Faust, formula 5.5, p. 177)" << "
" << "(Wasserman & Faust, p. 101)" << "

"; } else outText << "

" << tr("Because this graph is weighted, we cannot compute Group Centralization") << "
" << tr("You can use variance as a group-level centralization measure.") << "

"; outText << "

 

"; outText << "

"; outText << tr("Degree Centrality report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the closeness centralities to a file * @param fileName * @param considerWeights * @param inverseWeights * @param dropIsolates */ bool Graph::writeCentralityCloseness(const QString fileName, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, const int &format) { QElapsedTimer computationTimer; computationTimer.start(); qCDebug(lcReporting) << "Writing closeness Centrality report to file:" << fileName << "considerWeights" << considerWeights << "inverseWeights" << inverseWeights << "dropIsolates" << dropIsolates << "m_reportsLabelLength" << m_reportsLabelLength; QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); graphDistancesGeodesic(true, considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"CC", "CC'", "%CC'"}, [](GraphVertex *v) -> QVector { return {v->CC(), v->SCC(), 100 * v->SCC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::CC, m_reportsChartType, distImageFileName); int N = vertices(); QString pMsg = tr("Writing Closeness Centrality scores to file. \nPlease wait ..."); progressStatus(pMsg); outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); outText << "

"; outText << tr("CLOSENESS CENTRALITY (CC) REPORT"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The CC index is the inverted sum of geodesic distances " "from each node u to all other nodes. ") << "
" << tr("Note: The CC index considers outbound arcs only and " "isolate nodes are dropped by default. ") << "
" << tr("Read the Manual for more.") << "
" << tr("CC' is the standardized index (CC multiplied by (N-1 minus isolates)).") << "

"; outText << "

" << "" << tr("CC range: ") << "" << tr("0 ≤ CC ≤ ") << 1.0 / maxIndexCC << tr(" ( 1 / Number of node pairs excluding u)") << "

"; outText << "

" << "" << tr("CC' range: ") << "" << tr("0 ≤ CC' ≤ 1 (CC'=1 when a node is the center of a star graph)") << "

"; writeScoreTableHTML(outText, {"CC", "CC'", "%CC'"}, [](GraphVertex *v) -> QVector { return {v->CC(), v->SCC(), 100 * v->SCC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); if (minSCC == maxSCC) { outText << "

" << tr("All nodes have the same CC score.") << "

"; } else { outText << "

"; outText << "" << tr("CC Sum = ") << "" << sumCC << "

"; outText << "

"; outText << "" << tr("Max CC' = ") << "" << maxSCC << " (node " << maxNodeSCC << ")" << "
" << "" << tr("Min CC' = ") << "" << minSCC << " (node " << minNodeSCC << ")" << "
" << "" << tr("CC' classes = ") << "" << classesSCC << "

"; } outText << "

"; outText << "" << tr("CC' Sum = ") << "" << sumSCC << "
" << "" << tr("CC' Mean = ") << "" << meanSCC << "
" << "" << tr("CC' Variance = ") << "" << varianceSCC << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("CC' DISTRIBUTION") << "

"; outText << "

"; outText << ""; } if (!considerWeights) { outText << "

"; outText << tr("GROUP CLOSENESS CENTRALIZATION (GCC)") << "

"; outText << "

"; outText << "" << tr("GCC = ") << "" << groupCC << "

"; outText << "

" << "" << tr("GCC range: ") << "" << " 0 ≤ GCC ≤ 1" << "

"; outText << "

" << tr("GCC = 0, when the lengths of the geodesics are all equal, " "i.e. a complete or a circle graph.") << "
" << tr("GCC = 1, when one node has geodesics of length 1 to all the " "other nodes, and the other nodes have geodesics of length 2. " "to the remaining (N-2) nodes.") << "
" << tr("This is exactly the situation realised by a star graph.") << "
" << "(Wasserman & Faust, formula 5.9, p. 186-187)" << "

"; } else outText << "

" << tr("Because this graph is weighted, we cannot compute Group Centralization") << "
" << tr("You can use variance as a group-level centralization measure.") << "

"; outText << "

 

"; outText << "

"; outText << tr("Closeness Centrality report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the "improved" closeness centrality indices to a file * @param fileName * @param considerWeights * @param inverseWeights * @param dropIsolates */ bool Graph::writeCentralityClosenessInfluenceRange(const QString fileName, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing IR Closeness Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); centralityClosenessIR(considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"IRCC", "%IRCC'"}, [](GraphVertex *v) -> QVector { return {v->IRCC(), 100 * v->SIRCC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::IRCC, m_reportsChartType, distImageFileName); int N = vertices(); QString pMsg = tr("Writing Influence Range Centrality scores. \n" "Please wait"); progressStatus(pMsg); outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); outText << "

"; outText << tr("INFLUENCE RANGE CLOSENESS CENTRALITY (IRCC)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The IRCC index of a node u is the ratio of the fraction of nodes " "reachable by node u to the average distance of these nodes from u " "(Wasserman & Faust, formula 5.22, p. 201)
" "Thus, this measure is similar to Closeness Centrality " "but it counts only outbound distances from each actor to other reachable nodes.
" "This measure is useful for directed networks which are " "not strongly connected (thus the ordinary CC index cannot be computed).
" "In undirected networks, the IRCC has the same properties and yields " "the same results as the ordinary Closeness Centrality.
" "Read the Manual for more. ") << "
" << tr("IRCC is standardized.") << "

"; outText << "

" << "" << tr("IRCC range: ") << "" << tr("0 ≤ IRCC ≤ 1 (IRCC is a ratio)") << "

"; writeScoreTableHTML(outText, {"IRCC", "%IRCC'"}, [](GraphVertex *v) -> QVector { return {v->IRCC(), 100 * v->SIRCC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); if (minIRCC == maxIRCC) { outText << "

" << tr("All nodes have the same IRCC score.") << "

"; } else { outText << "

"; outText << "" << tr("Max IRCC = ") << "" << maxIRCC << " (node " << maxNodeIRCC << ")" << "
" << "" << tr("Min IRCC = ") << "" << minIRCC << " (node " << minNodeIRCC << ")" << "
" << "" << tr("IRCC classes = ") << "" << classesIRCC << "

"; } outText << "

"; outText << "" << tr("IRCC Sum = ") << "" << sumIRCC << "
" << "" << tr("IRCC Mean = ") << "" << meanIRCC << "
" << "" << tr("IRCC Variance = ") << "" << varianceIRCC << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("IRCC DISTRIBUTION") << "

"; outText << "

"; outText << ""; } outText << "

 

"; outText << "

"; outText << tr("Influence Range Closeness Centrality report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes Betweenness centralities to file * @param fileName * @param considerWeights * @param inverseWeights * @param dropIsolates */ bool Graph::writeCentralityBetweenness(const QString fileName, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing Betweenness Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); graphDistancesGeodesic(true, considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"BC", "BC'", "%BC'"}, [](GraphVertex *v) -> QVector { return {v->BC(), v->SBC(), 100 * v->SBC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::BC, m_reportsChartType, distImageFileName); int N = vertices(); QString pMsg = tr("Writing Betweenness Centrality scores to file. \nPlease wait..."); progressStatus(pMsg); outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); outText << "

"; outText << tr("BETWEENNESS CENTRALITY (BC)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The BC index of a node u is the sum of δ(s,t,u) for all s,t ∈ V ") << tr("where δ(s,t,u) is the ratio of all geodesics between " "s and t which run through u. ") << "
" << tr("Read the Manual for more.") << "
" << tr("BC' is the standardized index (BC divided by (N-1)(N-2)/2 in symmetric nets or (N-1)(N-2) otherwise.") << "

"; outText << "

" << "" << tr("BC range: ") << "" << tr("0 ≤ BC ≤ ") << maxIndexBC << tr(" (Number of pairs of nodes excluding u)") << "

"; outText << "

" << "" << tr("BC' range: ") << "" << tr("0 ≤ BC' ≤ 1 (BC'=1 when the node falls on all geodesics)") << "

"; writeScoreTableHTML(outText, {"BC", "BC'", "%BC'"}, [](GraphVertex *v) -> QVector { return {v->BC(), v->SBC(), 100 * v->SBC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); if (minSBC == maxSBC) { outText << "

" << tr("All nodes have the same BC score.") << "

"; } else { outText << "

"; outText << "" << tr("BC Sum = ") << "" << sumBC << "

"; outText << "

"; outText << "" << tr("Max BC' = ") << "" << maxSBC << " (node " << maxNodeSBC << ")" << "
" << "" << tr("Min BC' = ") << "" << minSBC << " (node " << minNodeSBC << ")" << "
" << "" << tr("BC' classes = ") << "" << classesSBC << "

"; } outText << "

"; outText << "" << tr("BC' Sum = ") << "" << sumSBC << "
" << "" << tr("BC' Mean = ") << "" << meanSBC << "
" << "" << tr("BC' Variance = ") << "" << varianceSBC << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("BC' DISTRIBUTION") << "

"; outText << "

"; outText << ""; } if (!considerWeights) { outText << "

"; outText << tr("GROUP BETWEENNESS CENTRALIZATION (GBC)") << "

"; outText << "

"; outText << "" << tr("GBC = ") << "" << groupSBC << "

"; outText << "

" << "" << tr("GBC range: ") << "" << " 0 ≤ GBC ≤ 1" << "

"; outText << "

" << tr("GBC = 0, when all the nodes have exactly the same betweenness index.") << "
" << tr("GBC = 1, when one node falls on all other geodesics between " "all the remaining (N-1) nodes. ") << "
" << tr("This is exactly the situation realised by a star graph.") << "
" << "(Wasserman & Faust, formula 5.13, p. 192)" << "

"; } else outText << "

" << tr("Because this graph is weighted, we cannot compute Group Centralization") << "
" << tr("You can use variance as a group-level centralization measure.") << "

"; outText << "

 

"; outText << "

"; outText << tr("Betweenness Centrality report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the Stress centralities to a file * @param fileName * @param considerWeights * @param inverseWeights * @param dropIsolates */ bool Graph::writeCentralityStress(const QString fileName, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing Stress Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); graphDistancesGeodesic(true, considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"SC", "SC'", "%SC'"}, [](GraphVertex *v) -> QVector { return {v->SC(), v->SSC(), 100 * v->SSC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::SC, m_reportsChartType, distImageFileName); int N = vertices(); QString pMsg = tr("Writing Stress Centralities. \nPlease wait..."); progressStatus(pMsg); outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); outText << "

"; outText << tr("STRESS CENTRALITY (SC)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The SC index of each node u is the sum of σ(s,t,u)):
" "the number of geodesics from s to t through u.") << "
" << tr("SC' is the standardized index (SC divided by sumSC).") << "

"; outText << "

" << "" << tr("SC range: ") << "" << tr("0 ≤ SC ≤ ") << maxIndexSC << "

"; outText << "

" << "" << tr("SC' range: ") << "" << tr("0 ≤ SC' ≤ 1 (SC'=1 when the node falls on all geodesics)") << "

"; writeScoreTableHTML(outText, {"SC", "SC'", "%SC'"}, [](GraphVertex *v) -> QVector { return {v->SC(), v->SSC(), 100 * v->SSC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); if (minSSC == maxSSC) { outText << "

" << tr("All nodes have the same SC score.") << "

"; } else { outText << "

"; outText << "" << tr("SC Sum = ") << "" << sumSC << "

"; outText << "

"; outText << "" << tr("Max SC' = ") << "" << maxSSC << " (node " << maxNodeSSC << ")" << "
" << "" << tr("Min SC' = ") << "" << minSSC << " (node " << minNodeSSC << ")" << "
" << "" << tr("BC classes = ") << "" << classesSSC << "

"; } outText << "

"; outText << "" << tr("SC' Sum = ") << "" << sumSSC << "
" << "" << tr("SC' Mean = ") << "" << meanSSC << "
" << "" << tr("SC' Variance = ") << "" << varianceSSC << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("SC' DISTRIBUTION") << "

"; outText << "

"; outText << ""; } outText << "

"; outText << tr("Stress Centrality report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the Eccentricity centralities (aka Harary Graph Centrality) to a file * @param fileName * @param considerWeights * @param inverseWeights * @param dropIsolates */ bool Graph::writeCentralityEccentricity(const QString fileName, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing Eccentricity Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); graphDistancesGeodesic(true, considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"EC=EC'", "%EC'"}, [](GraphVertex *v) -> QVector { return {v->EC(), 100 * v->SEC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::EC, m_reportsChartType, distImageFileName); int N = vertices(); QString pMsg = tr("Writing Eccentricity Centralities to file. \nPlease wait..."); progressStatus(pMsg); outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); outText << "

"; outText << tr("ECCENTRICITY CENTRALITY (EC)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The EC score of a node u is the inverse maximum geodesic distance " "from u to all other nodes in the network.") << "
" << tr("This index is also known as Harary Graph Centrality. ") << tr("EC is standardized.") << "

"; outText << "

" << "" << tr("EC range: ") << "" << tr("0 ≤ EC ≤ 1 ") << tr(" (EC=1 when the actor has ties to all other nodes)") << "

"; writeScoreTableHTML(outText, {"EC=EC'", "%EC'"}, [](GraphVertex *v) -> QVector { return {v->EC(), 100 * v->SEC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); if (minEC == maxEC) { outText << "

" << tr("All nodes have the same EC score.") << "

"; } else { outText << "

"; outText << "" << tr("Max EC = ") << "" << maxEC << " (node " << maxNodeEC << ")" << "
" << "" << tr("Min EC = ") << "" << minEC << " (node " << minNodeEC << ")" << "
" << "" << tr("EC classes = ") << "" << classesEC << "

"; } outText << "

"; outText << "" << tr("EC Sum = ") << "" << sumEC << "
" << "" << tr("EC Mean = ") << "" << meanEC << "
" << "" << tr("EC Variance = ") << "" << varianceEC << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("EC DISTRIBUTION") << "

"; outText << "

"; outText << ""; } outText << "

 

"; outText << "

"; outText << tr("Eccentricity Centrality report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes Power Centralities to a file * @param fileName * @param considerWeights * @param inverseWeights * @param dropIsolates */ bool Graph::writeCentralityPower(const QString fileName, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing Power Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); graphDistancesGeodesic(true, considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"PC", "PC'", "%PC'"}, [](GraphVertex *v) -> QVector { return {v->PC(), v->SPC(), 100 * v->SPC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::PC, m_reportsChartType, distImageFileName); int N = vertices(); QString pMsg = tr("Writing Gil-Schmidt Power Centralities to file. \nPlease wait..."); progressStatus(pMsg); outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); outText << "

"; outText << tr("POWER CENTRALITY (PC)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The PC index, introduced by Gil and Schmidt, of a node u is the sum of the sizes of all Nth-order " "neighbourhoods with weight 1/n.") << "
" << tr("PC' is the standardized index: The PC score divided by the total number " "of nodes in the same component minus 1") << "

"; outText << "

" << "" << tr("PC range: ") << "" << tr("0 ≤ PC ≤ ") << maxIndexPC << "

"; outText << "

" << "" << tr("PC' range: ") << "" << tr("0 ≤ PC' ≤ 1 (PC'=1 when the node is connected to all (star).)") << "

"; writeScoreTableHTML(outText, {"PC", "PC'", "%PC'"}, [](GraphVertex *v) -> QVector { return {v->PC(), v->SPC(), 100 * v->SPC()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); if (minSPC == maxSPC) { outText << "

" << tr("All nodes have the same PC score.") << "

"; } else { outText << "

"; outText << "" << tr("PC Sum = ") << "" << sumPC << "

"; outText << "

"; outText << "" << tr("Max PC' = ") << "" << maxSPC << " (node " << maxNodeSPC << ")" << "
" << "" << tr("Min PC' = ") << "" << minSPC << " (node " << minNodeSPC << ")" << "
" << "" << tr("PC classes = ") << "" << classesSPC << "

"; } outText << "

"; outText << "" << tr("PC' Sum = ") << "" << sumSPC << "
" << "" << tr("PC' Mean = ") << "" << meanSPC << "
" << "" << tr("PC' Variance = ") << "" << varianceSPC << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("PC' DISTRIBUTION") << "

"; outText << "

"; outText << ""; } if (!considerWeights) { outText << "

"; outText << tr("GROUP POWER CENTRALIZATION (GPC)") << "

"; outText << "

"; outText << "" << tr("GPC = ") << "" << groupSPC << "

"; outText << "

" << "" << tr("GPC range: ") << "" << " 0 ≤ GPC ≤ 1" << "

"; outText << "

" << tr("GPC = 0, when all in-degrees are equal (i.e. regular lattice).") << "
" << tr("GPC = 1, when one node is linked to all other nodes (i.e. star). ") << "
" << "

"; } else outText << "

" << tr("Because this graph is weighted, we cannot compute Group Centralization") << "
" << tr("Use mean or variance instead.") << "

"; outText << "

 

"; outText << "

"; outText << tr("Power Centrality report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the Degree Prestige of each node to a file * @param fileName * @param considerWeights * @param dropIsolates */ bool Graph::writePrestigeDegree(const QString fileName, const bool considerWeights, const bool dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing Degree Prestige report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); prestigeDegree(considerWeights, dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"DP", "DP'", "%DP'"}, [](GraphVertex *v) -> QVector { return {v->DP(), v->SDP(), 100 * v->SDP()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::DP, m_reportsChartType, distImageFileName); int N = vertices(); qreal maxIndexDP = N - 1.0; QString pMsg = tr("Writing Degree Prestige (in-Degree) scores to file. \nPlease wait ..."); progressStatus(pMsg); outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); outText << "

"; outText << tr("DEGREE PRESTIGE (DP)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The DP index, also known as InDegree Centrality, of a node u " "is the sum of inbound edges to that node from all adjacent nodes.
" "If the network is weighted, DP is the sum of inbound arc " "weights (Indegree) to node u from all adjacent nodes. ") << "
" << tr("DP' is the standardized index (DP divided by N-1).") << "

"; outText << "

" << "" << tr("DP range: ") << "" << tr("0 ≤ DP ≤ "); if (considerWeights) outText << infinity; else outText << maxIndexDP; outText << "

"; outText << "

" << "" << tr("DP' range: ") << "" << tr("0 ≤ DP' ≤ 1") << "

"; writeScoreTableHTML(outText, {"DP", "DP'", "%DP'"}, [](GraphVertex *v) -> QVector { return {v->DP(), v->SDP(), 100 * v->SDP()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); if (minSDP == maxSDP) { outText << "

" << tr("All nodes have the same DP score.") << "

"; } else { outText << "

"; outText << "" << tr("DP Sum = ") << "" << sumDP << "

"; outText << "

" << "" << tr("Max DP' = ") << "" << maxSDP << " (node " << maxNodeDP << ")" << "
" << "" << tr("Min DP' = ") << "" << minSDP << " (node " << minNodeDP << ")" << "
" << "" << tr("DP' classes = ") << "" << classesSDP << "

"; } outText << "

"; outText << "" << tr("DP' Sum = ") << "" << sumSDP << "
" << "" << tr("DP' Mean = ") << "" << meanSDP << "
" << "" << tr("DP' Variance = ") << "" << varianceSDP << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("DP' DISTRIBUTION") << "

"; outText << "

"; outText << ""; } if (!considerWeights) { outText << "

"; outText << tr("GROUP DEGREE PRESTIGE (GDP)") << "

"; outText << "

"; outText << "" << tr("GDP = ") << "" << groupDP << "

"; outText << "

" << "" << tr("GDP range: ") << "" << " 0 ≤ GDP ≤ 1" << "

"; outText << "

" << tr("GDP = 0, when all in-degrees are equal (i.e. regular lattice).") << "
" << tr("GDP = 1, when one node is chosen by all other nodes (i.e. star). ") << "
" << tr("This is exactly the situation realised by a star graph.") << "
" << "(Wasserman & Faust, p. 203)" << "

"; } else outText << "

" << tr("Because this graph is weighted, we cannot compute Group Centralization") << "
" << tr("You can use variance as a group-level centralization measure.") << "

"; outText << "

 

"; outText << "

"; outText << tr("Degree Prestige report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief * Writes the proximity prestige indices to a file * @param fileName * @param considerWeights * @param inverseWeights * @param dropIsolates */ bool Graph::writePrestigeProximity(const QString fileName, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing Proximity Prestige report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); prestigeProximity(considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"PP=PP'", "%PP"}, [](GraphVertex *v) -> QVector { return {v->PP(), 100 * v->SPP()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::PP, m_reportsChartType, distImageFileName); int N = vertices(); QString pMsg = tr("Writing Proximity Prestige scores to file. \nPlease wait ..."); progressStatus(pMsg); outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); outText << "

"; outText << tr("PROXIMITY PRESTIGE (PP)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The PP index of a node u is the ratio of the proportion of " "nodes who can reach u to the average distance these nodes are from u " "(Wasserman & Faust, formula 5.25, p. 204)
" "Thus, it is similar to Closeness Centrality but it counts " "only inbound distances to each actor, thus it is a measure of actor prestige.
" "This metric is useful for directed networks which are " "not strongly connected (thus the ordinary CC index cannot be computed).
" "In undirected networks, the PP has the same properties and yields " "the same results as Closeness Centrality.
" "Read the Manual for more.
") << "

"; outText << "

" << "" << tr("PP range: ") << "" << tr("0 ≤ PP ≤ 1 (PP is a ratio)") << "

"; writeScoreTableHTML(outText, {"PP=PP'", "%PP"}, [](GraphVertex *v) -> QVector { return {v->PP(), 100 * v->SPP()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); if (minPP == maxPP) { outText << "

" << tr("All nodes have the same PP score.") << "

"; } else { outText << "

"; outText << "" << tr("Max PP = ") << "" << maxPP << " (node " << maxNodePP << ")" << "
" << "" << tr("Min PP = ") << "" << minPP << " (node " << minNodePP << ")" << "
" << "" << tr("PP classes = ") << "" << classesPP << "

"; } outText << "

"; outText << "" << tr("PP Sum = ") << "" << sumPP << "
" << "" << tr("PP Mean = ") << "" << meanPP << "
" << "" << tr("PP Variance = ") << "" << variancePP << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("PP DISTRIBUTION") << "

"; outText << "

"; outText << ""; } outText << "

 

"; outText << "

"; outText << tr("Proximity Prestige report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the PageRank scores of vertices to a file * @param fileName * @param dropIsolates */ bool Graph::writePrestigePageRank(const QString fileName, const bool dropIsolates, const int &format) { qCDebug(lcReporting) << "Writing PageRank Prestige report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); prestigePageRank(dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"PRP", "PRP'", "%PRP'"}, [](GraphVertex *v) -> QVector { return {v->PRP(), v->SPRP(), 100 * v->SPRP()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); file.close(); return true; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::PRP, m_reportsChartType, distImageFileName); int N = vertices(); QString pMsg = tr("Writing PageRank scores to file. \nPlease wait ..."); progressStatus(pMsg); outText.setRealNumberPrecision(m_reportsRealPrecision); outText << htmlHead; outText << "

"; outText << tr("PAGERANK PRESTIGE (PRP)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The PRP is an importance ranking index for each node based on the structure " "of its incoming links/edges and the rank of the nodes linking to it.
" "For each node u the algorithm counts all inbound links (edges) to it, but it " "normalizes each inbound link from a node v by the outDegree of v.
" "The PR values correspond to the principal eigenvector of the normalized link matrix.
" "Note: In weighted relations, each backlink to a node u from another node v is considered " "to have weight=1 but it is normalized by the sum of outbound edge weights of v. " "Therefore, nodes with high outLink weights give smaller percentage of their PR to node u.") << "
" << tr("PRP' is the scaled PRP (PRP divided by max PRP).") << "

"; outText << "

" << "" << tr("PRP range: ") << "" << tr("(1-d)/N = ") << ((1 - d_factor) / N) << tr(" ≤ PRP ") << "

"; outText << "

" << "" << tr("PRP' range: ") << "" << tr("0 ≤ PRP' ≤ 1") << "

"; writeScoreTableHTML(outText, {"PRP", "PRP'", "%PRP'"}, [](GraphVertex *v) -> QVector { return {v->PRP(), v->SPRP(), 100 * v->SPRP()}; }, [dropIsolates](GraphVertex *v) { return dropIsolates && v->isIsolated(); }); if (minPRP == maxPRP) { outText << "

" << tr("All nodes have the same PRP score.") << "

"; } else { outText << "

"; outText << "" << tr("Max PRP = ") << "" << maxPRP << " (node " << maxNodePRP << ")" << "
" << "" << tr("Min PRP = ") << "" << minPRP << " (node " << minNodePRP << ")" << "
" << "" << tr("PRP classes = ") << "" << classesPRP << "

"; } outText << "

"; outText << "" << tr("PRP Sum = ") << "" << sumPRP << "
" << "" << tr("PRP Mean = ") << "" << meanPRP << "
" << "" << tr("PRP Variance = ") << "" << variancePRP << "
"; outText << "

"; if (m_reportsChartType != ChartType::None) { outText << "

"; outText << tr("PRP' DISTRIBUTION") << "

"; outText << "

"; outText << ""; } outText << "

 

"; outText << "

"; outText << tr("PageRank Prestige report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the walks of given length matrix to a file in HTML. * If length = 0, it writes the Total Walks matrix. * @param fn * @param length * @param simpler */ bool Graph::writeMatrixWalks(const QString &fn, const int &length, const bool &simpler, const int &format) { qCDebug(lcReporting) << "I will write walks of length:" << length << "to file:" << fn; QElapsedTimer computationTimer; computationTimer.start(); Q_UNUSED(simpler); QFile file(fn); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fn); return false; } int N = vertices(); progressStatus(tr("Computing Walks...")); graphWalksMatrixCreate(N, length); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } QTextStream outText(&file); if (format == ReportFormat::Csv) { writeMatrixCSVTable(outText, (length > 0) ? XM : XSM, true); file.close(); return true; } outText << htmlHead; outText << "

"; if (length > 0) { outText << tr("WALKS OF LENGTH %1 MATRIX").arg(length); } else { outText << tr("TOTAL WALKS MATRIX"); } outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; if (length > 0) { outText << "

" << tr("The Walks of length %1 matrix is a NxN matrix " "where each element (i,j) is the number of walks of " "length %1 between actor i and actor j, " "or 0 if no walk exists.
" "A walk is a sequence of edges and vertices, where each edge's " "endpoints are the two vertices adjacent to it. In a walk, " "vertices and edges may repeat.
" "Warning: Walks count unordered pairs of nodes. ") .arg(length) << "

"; } else { outText << "

" << tr("The Total Walks matrix of a social network is a NxN matrix " "where each element (i,j) is the total number of walks of any " "length (less than or equal to %1) between actor i and actor j, " "or 0 if no walk exists.
" "A walk is a sequence of edges and vertices, where each edge's " "endpoints are the two vertices adjacent to it. In a walk, " "vertices and edges may repeat.
" "Warning: Walks count unordered pairs of nodes. ") .arg(N - 1) << "

"; } progressStatus(tr("Writing Walks matrix to file:") + fn); qCDebug(lcReporting) << "Graph::writeMatrixWalks() - Writing XM to file"; if (length > 0) { writeMatrixHTMLTable(outText, XM, true); } else { writeMatrixHTMLTable(outText, XSM, true); } outText << "

 

"; outText << "

"; outText << tr("Walks report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the clustering coefficients to a file * @param fileName * @param considerWeights */ bool Graph::writeClusteringCoefficient(const QString fileName, const bool considerWeights, const int &format) { QElapsedTimer computationTimer; computationTimer.start(); Q_UNUSED(considerWeights); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); int N = vertices(); averageCLC = clusteringCoefficient(); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } if (format == ReportFormat::Csv) { writeScoreTableCSV(outText, {"CLC", "CLC'", "%CLC'"}, [this](GraphVertex *v) -> QVector { return {v->CLC(), v->CLC() / maxCLC, 100 * v->CLC() / maxCLC}; }); file.close(); return true; } QString pMsg = tr("Writing Clustering Coefficients to file. \nPlease wait..."); progressStatus(pMsg); outText.setRealNumberPrecision(m_reportsRealPrecision); outText << htmlHead; outText << "

"; outText << tr("CLUSTERING COEFFICIENT (CLC) REPORT"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The local Clustering Coefficient, introduced by Watts and Strogatz (1998) " "quantifies how close each node and its neighbors are to being a complete subgraph (clique).") << "
" << tr("For each node u, the local CLC score is the proportion of actual links between " "its neighbors divided by the number of links that could possibly exist between them.
" "The CLC index is used to characterize the transitivity of a network. A value close to one " "indicates that the node is involved in many transitive relations. " "CLC' is the normalized CLC, divided by maximum CLC found in this network.") << "

"; outText << "

" << "" << tr("CLC range: ") << "" << tr("0 ≤ CLC ≤ 1 ") << "

"; outText << "

" << "" << tr("CLC range: ") << "" << tr("0 ≤ CLC' ≤ 1 ") << "

"; writeScoreTableHTML(outText, {"CLC", "CLC'", "%CLC'"}, [this](GraphVertex *v) -> QVector { return {v->CLC(), v->CLC() / maxCLC, 100 * v->CLC() / maxCLC}; }); if (minCLC == maxCLC) { outText << "

" << tr("All nodes have the same local CLC score.") << "

"; } else { outText << "

"; outText << "" << tr("Max CLC = ") << "" << maxCLC << " (node " << maxNodeCLC << ")" << "
" << "" << tr("Min CLC = ") << "" << minCLC << " (node " << minNodeCLC << ")" << "
" << "

"; } outText << "

" << "" << tr("CLC Mean = ") << "" << averageCLC << "
" << "" << tr("CLC Variance = ") << "" << varianceCLC << "
"; outText << "

"; outText << "

"; outText << tr("GROUP / NETWORK AVERAGE CLUSTERING COEFFICIENT (GCLC)") << "

"; outText << "

" << "" << tr("GCLC = ") << "" << averageCLC << "

"; outText << "

" << tr("Range: 0 < GCLC < 1
") << tr("GCLC = 0, when there are no cliques (i.e. acyclic tree).
") << tr("GCLC = 1, when every node and its neighborhood are complete cliques.") << "

"; outText << "

 

"; outText << "

"; outText << tr("Clustering Coefficient report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } // Writes the triad census to a file bool Graph::writeTriadCensus(const QString fileName, const bool considerWeights, const int &format) { qCDebug(lcReporting) << "Graph::writeTriadCensus()"; QElapsedTimer computationTimer; computationTimer.start(); Q_UNUSED(considerWeights); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); progressStatus((tr("Computing triad census. Please wait...."))); if (!calculatedTriad) { if (!graphTriadCensus()) { qCDebug(lcReporting) << "Error in graphTriadCensus(). Exiting..."; file.close(); return false; } if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } } int rowCount = 0; int N = vertices(); QList triadTypes; triadTypes << "003"; triadTypes << "012"; triadTypes << "102"; triadTypes << "021D"; triadTypes << "021U"; triadTypes << "021C"; triadTypes << "111D"; triadTypes << "111U"; triadTypes << "030T"; triadTypes << "030C"; triadTypes << "201"; triadTypes << "120D"; triadTypes << "120U"; triadTypes << "120C"; triadTypes << "210"; triadTypes << "300"; if (format == ReportFormat::Csv) { // Small fixed-shape table (16 triad types x Census count) - not per-node, so this // doesn't go through writeScoreTableCSV(). Type strings are fixed known values with // no commas/quotes, so no escaping is needed. outText << tr("Type") << "," << tr("Census") << "\n"; for (int i = 0; i <= 15; i++) { outText << triadTypes[i] << "," << triadTypeFreqs[i] << "\n"; } file.close(); return true; } QString pMsg = tr("Writing Triad Census to file. \nPlease wait..."); progressStatus(pMsg); outText << htmlHead; outText << "

"; outText << tr("TRIAD CENSUS (TRC) REPORT"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("A Triad Census counts all the different types (classes) of observed triads within a network.
" "The triad types are coded and labeled according to their number of mutual, asymmetric and non-existent (null) dyads.
" "SocNetV follows the M-A-N labeling scheme, as described by Holland, Leinhardt and Davis in their studies.
" "In the M-A-N scheme, each triad type has a label with four characters:
") << tr("- The first character is the number of mutual (M) dyads in the triad. Possible values: 0, 1, 2, 3.
" "- The second character is the number of asymmetric (A) dyads in the triad. Possible values: 0, 1, 2, 3.
" "- The third character is the number of null (N) dyads in the triad. Possible values: 0, 1, 2, 3.
" "- The fourth character is inferred from features or the nature of the triad, i.e. presence of cycle or transitivity. " "Possible values: none, D (\"Down\"), U (\"Up\"), C (\"Cyclic\"), T (\"Transitive\")") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << ""; for (int i = 0; i <= 15; i++) { rowCount = i + 1; outText << "" << "" << ""; } outText << "
" << tr("Type") << "" << tr("Census") // << "" // << tr("Expected Value") << "
" << triadTypes[i] << "" << triadTypeFreqs[i] << "
"; outText << "

 

"; outText << "

"; outText << tr("Triad Census report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Calls graphCliques() to compute all cliques (maximal connected subgraphs) of the network. * Then writes the results into a file, along with the Actor by clique analysis, * the Co-membership matrix and the Hierarchical clustering of overlap matrix * @param fileName * @param considerWeights */ bool Graph::writeCliqueCensus(const QString &fileName, const bool considerWeights) { QElapsedTimer computationTimer; computationTimer.start(); qCDebug(lcReporting) << "Graph::writeCliqueCensus() "; Q_UNUSED(considerWeights); QString varLocation = "Both"; bool dendrogram = true; QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } int N = vertices(); int cliqueCounter = 0; int rowCounter = 0; int cliqueSize = 0; int actor2 = 0, actor1 = 0, index1 = 0, index2 = 0; qreal numerator = 0; QString listString; VList::const_iterator it, it2; QString pMsg = tr("Computing Clique Census and writing it to a file. \nPlease wait..."); progressStatus(pMsg); // compute clique census pMsg = tr("Computing Clique Census. Please wait.."); progressStatus(pMsg); qCDebug(lcReporting) << "Graph::writeCliqueCensus() - calling graphCliques"; csRecDepth = 0; // Call graphCliques() to compute all cliques (maximal connected subgraphs) of the network. graphCliques(); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } pMsg = tr("Writing Clique Census to file. Please wait.."); progressStatus(pMsg); QTextStream outText(&file); outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); outText << "

"; outText << tr("CLIQUE CENSUS (CLQs) REPORT"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("A clique is the largest subgroup of actors in the social network who are all " "directly connected to each other (maximal complete subgraph).
" "SocNetV applies the Bron–Kerbosch algorithm to produce a census of all maximal cliques " "in the network and reports some useful statistics such as disaggregation by vertex " "and co-membership information.
") << "

"; outText << "

" << "" << tr("Maximal Cliques found: ") << "" << m_cliques.size() << "

"; outText << ""; outText << "" << "" << "" << "" << "" << ""; foreach (QList clique, m_cliques) { ++cliqueCounter; outText << ""; listString.truncate(0); while (!clique.empty()) { listString += QString::number(clique.takeFirst()); if (!clique.empty()) listString += " "; } outText << "" << ""; } outText << "
" << tr("Clique No") << "" << tr("Clique members") << "
" << cliqueCounter << "" << listString << "
"; /** * Write the actor by clique analysis matrix. * For each actor-clique pair, we compute the proportion of clique members adjacent */ outText << "

" << "" << tr("Actor by clique analysis: ") << "" << tr("Proportion of clique members adjacent") << "

"; outText << ""; outText << "" << "" << ""; for (int listIndex = 0; listIndex < cliqueCounter; listIndex++) { outText << ""; } outText << "" << "" << ""; rowCounter = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { rowCounter++; actor1 = (*it)->number(); outText << "" << ""; foreach (QList clique, m_cliques) { numerator = 0; if (clique.contains(actor1)) { outText << ""; } else { cliqueSize = clique.size(); while (!clique.empty()) { actor2 = clique.takeFirst(); if (edgeExists(actor1, actor2)) { numerator++; } } outText << ""; } } outText << ""; } outText << "
" << tr("Actor/Clique") << "" << listIndex + 1 << "
" << actor1 << "" << "1.000" << "" << Qt::fixed << (numerator / (qreal)cliqueSize) << "
"; /** * Write the actor by actor analysis matrix. * For each pair, we compute their clique co-membership */ outText << "

" << "" << tr("Actor by actor analysis: ") << "" << tr(" Co-membership matrix") << "

"; outText << ""; outText << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { actor1 = (*it)->number(); outText << ""; } outText << "" << "" << ""; rowCounter = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { actor1 = (*it)->number(); index1 = vpos[actor1]; rowCounter++; outText << "" << ""; for (it2 = m_graph.cbegin(); it2 != m_graph.cend(); ++it2) { actor2 = (*it2)->number(); index2 = vpos[actor2]; outText << ""; } outText << ""; } outText << "
" << tr("Actor/Actor") << "" << actor1 << "
" << actor1 << "" << qSetRealNumberPrecision(0) << CLQM.item(index1, index2) << "
"; /** * Write the Hierarchical clustering of overlap matrix */ outText << "

" << "" << tr("Hierarchical clustering of overlap matrix: ") << "" << tr("Actors") << "

"; pMsg = tr("Computing HCA for Cliques. Please wait.."); progressStatus(pMsg); if (!graphClusteringHierarchical(CLQM, varLocation, graphMetricStrToType("Euclidean"), Clustering::Complete_Linkage, false, true, true, false, true)) { file.close(); progressStatus("Error completing HCA analysis"); return false; } pMsg = tr("Writing HCA for Cliques. Please wait.."); progressStatus(pMsg); writeClusteringHierarchicalResultsToStream(outText, N, dendrogram); outText << "

" << "" << tr("Clique by clique analysis: ") << "" << tr("Co-membership matrix") << "

"; outText << "

" << "" << tr("Hierarchical clustering of overlap matrix: ") << "" << tr("Clique") << "

"; outText << "

 

"; outText << "

"; outText << tr("Clique Census Report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Performs Hierarchical Cluster Analysis (HCA) and writes the results to an HTML report file. * * The analysis proceeds in three phases: * 1. Build the structural equivalence matrix (adjacency or geodesic distances). * 2. Run the hierarchical clustering algorithm on the equivalence matrix. * 3. Write the HTML report including the equivalence matrix and dendrogram. * * @param fileName Path to the output HTML report file. * @param varLocation Whether variables are in rows or columns ("rows"/"cols"). * @param matrix Input matrix type: "adjacency" or "distances". * @param metric Distance/dissimilarity metric (e.g., "euclidean", "manhattan"). * @param method Clustering linkage method (e.g., "single", "complete", "average"). * @param diagonal If true, include the diagonal of the matrix in computations. * @param dendrogram If true, include dendrogram output in the report. * @param considerWeights If true, edge weights are used in distance computations. * @param inverseWeights If true, edge weights are inverted before use. * @param dropIsolates If true, isolate nodes are excluded from the analysis. * @return true on success, false if the computation was cancelled or an error occurred. */ bool Graph::writeClusteringHierarchical(const QString &fileName, const QString &varLocation, const QString &matrix, const QString &metric, const QString &method, const bool &diagonal, const bool &dendrogram, const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates) { QElapsedTimer computationTimer; computationTimer.start(); qCDebug(lcReporting) << "Graph::writeClusteringHierarchical() - matrix:" << matrix << "varLocation" << varLocation << "metric" << metric << "method" << method << "considerWeights:" << considerWeights << "inverseWeights:" << inverseWeights << "dropIsolates:" << dropIsolates; int N = vertices(dropIsolates, false, true); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } progressStatus(tr("Computing hierarchical clustering. Please wait... ")); Matrix STR_EQUIV; switch (graphMatrixStrToType(matrix)) { case MATRIX_ADJACENCY: createMatrixAdjacency(dropIsolates); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } STR_EQUIV = AM; break; case MATRIX_DISTANCES: if (!graphMatrixDistanceGeodesicCreate(considerWeights, inverseWeights, dropIsolates)) { file.close(); progressStatus(tr("Computation canceled.")); return false; } STR_EQUIV = DM; break; default: file.close(); progressStatus(tr("Error: unsupported matrix type.")); return false; } if (!graphClusteringHierarchical(STR_EQUIV, varLocation, graphMetricStrToType(metric), graphClusteringMethodStrToType(method), diagonal, dendrogram, considerWeights, inverseWeights, dropIsolates)) { qCDebug(lcReporting) << "Graph::writeClusteringHierarchical() - HCA failed. Returning..."; file.close(); progressStatus("Error completing HCA analysis"); return false; } QTextStream outText(&file); QString pMsg = tr("Writing Hierarchical Cluster Analysis to file. \nPlease wait... "); progressStatus(pMsg); outText.setRealNumberPrecision(m_reportsRealPrecision); outText.reset(); outText << htmlHead; outText << "

"; outText << tr("HIERARCHICAL CLUSTERING (HCA)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << "" << tr("Input matrix: ") << "" << matrix << "

"; outText << "

" << "" << tr("Distance/dissimilarity metric: ") << "" << metric << "

"; outText << "

" << "" << tr("Clustering method/criterion: ") << "" << method << "

"; outText << "

 

"; outText << "

" << "" << tr("Analysis results") << "" << "

"; outText << "

" << "" << tr("Structural Equivalence Matrix: ") << "" << "

"; if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } writeMatrixHTMLTable(outText, STR_EQUIV, true, false, false, dropIsolates); outText << "

" << "" << tr("Hierarchical Clustering of Equivalence Matrix: ") << "" << "

"; if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } writeClusteringHierarchicalResultsToStream(outText, N, dendrogram); outText << "

 

"; outText << "

"; outText << tr("Hierarchical Cluster Analysis report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); qCDebug(lcReporting) << "Graph::writeClusteringHierarchical() - finished"; return true; } /** * @brief Writes Hierarchical Clustering results to given output stream * Before running this method, the method Graph::graphClusteringHierarchical() * must execute and return true. Otherwise, the result is unpredictable... * @param outText * @param N * @param dendrogram */ void Graph::writeClusteringHierarchicalResultsToStream(QTextStream &outText, const int N, const bool &dendrogram) { qCDebug(lcReporting) << "Writing Hierarchical Clustering results to stream. " << "N" << N << "dendrogram" << dendrogram; QMap::const_iterator it; qreal level; outText << "
";
    outText << "Seq" << "\t" << "Level" << "\t" << "Actors" << "\n";

    for (it = m_clustersPerSequence.constBegin(); it != m_clustersPerSequence.constEnd(); ++it)
    {
        level = m_clusteringLevel.at(it.key() - 1);
        outText << it.key() << "\t"
                << level << "\t";

        foreach (int item, it.value())
        {
            outText << item << " ";
        }
        outText << "\n";
    }
    outText << Qt::reset << "
"; if (dendrogram) { qCDebug(lcReporting) << "Writing SVG dendrogram..."; outText << "

" << "" << tr("Clustering Dendrogram (SVG)") << "" << "

"; int diagramMaxWidth = 1000; int diagramPaddingLeft = 30; int diagramPaddingTop = 30; int rowHeight = 15; int rowPaddingLeft = 5; int headerHeight = 10; int headerTextSize = 9; int actorTextSize = 12; int legendTextSize = 7; int maxSVGWidth = diagramMaxWidth + diagramPaddingLeft + rowPaddingLeft; int maxSVGHeight = 2 * diagramPaddingTop + (rowHeight * N); QMap clusterEndPoint; QPoint endPoint1, endPoint2, endPointLevel; QMap::const_iterator pit; // cluster names pair iterator int actorNumber; qreal maxLevelValue; QString clusterName; QList legendLevelsDone; it = m_clustersPerSequence.constEnd(); it--; maxLevelValue = m_clusteringLevel.last(); qCDebug(lcReporting) << "m_clustersPerSequence" << m_clustersPerSequence << "\n" << "maxLevelValue" << maxLevelValue << "\n" << "m_clusterPairNamesPerSeq" << m_clusterPairNamesPerSeq << "\n" << "m_clustersByName" << m_clustersByName; outText << "
"; outText << ""; // print a legend on top outText << "" << "Actor" << ""; outText << "" << "Clusterings" << ""; // print actor numbers // and compute initial cluster end points for them. for (int i = 0; i < it.value().size(); ++i) { actorNumber = it.value().at(i); clusterEndPoint[QString::number(actorNumber)] = QPoint(diagramPaddingLeft, diagramPaddingTop + rowHeight * (i)); outText << ""; outText << "" << actorNumber << ""; outText << ""; // end actor name } // end for rows // begin drawing clustering paths/lines for (pit = m_clusterPairNamesPerSeq.constBegin(); pit != m_clusterPairNamesPerSeq.constEnd(); ++pit) { level = m_clusteringLevel.at(pit.key() - 1); qCDebug(lcReporting) << "seq" << pit.key() << "level" << level << "cluster pair" << pit.value(); for (int i = 0; i < pit.value().size(); ++i) { clusterName = pit.value().at(i); qCDebug(lcReporting) << "clusterName" << clusterName; if (i == 0) { endPoint1 = clusterEndPoint.value(clusterName, QPoint()); qCDebug(lcReporting) << "endPoint1" << endPoint1; } else { endPoint2 = clusterEndPoint.value(clusterName, QPoint()); qCDebug(lcReporting) << "endPoint2" << endPoint2; } } if (endPoint1.isNull() || endPoint2.isNull()) { continue; } // compute and save new endPoint endPointLevel = QPoint(ceil(diagramPaddingLeft + diagramMaxWidth * (level / maxLevelValue)), ceil(endPoint1.y() + endPoint2.y()) / 2); clusterEndPoint.insert("c" + QString::number(pit.key()), endPointLevel); qCDebug(lcReporting) << "(pit.key() / maxLevelValue)" << (diagramPaddingLeft + level / maxLevelValue) << "endPointLevel" << endPointLevel; // print path outText << ""; // stroke-dasharray=\"5,5\" // print level vertical dashed line outText << ""; // print legend if (!legendLevelsDone.contains(level)) { outText << "" << Qt::fixed << level << ""; legendLevelsDone.append(level); } } outText << ""; // end dendrogram svg outText << "
"; // end dendrogram div } // end if dendrogram } /** * @brief Writes dissimilarity matrix based on a metric/measure to given html file * @param fileName * @param measure * @param varLocation * @param diagonal * @param considerWeights */ bool Graph::writeMatrixDissimilarities(const QString fileName, const QString &metricStr, const QString &varLocation, const bool &diagonal, const bool &considerWeights, const int &format) { qCDebug(lcReporting) << "Graph::writeMatrixDissimilarities()" << "metric" << metricStr << "varLocation" << varLocation << "diagonal" << diagonal; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); Matrix DSM; int N = vertices(); createMatrixAdjacency(); if (progressCanceled()) { file.close(); return false; } progressStatus((tr("Examining pair-wise tie profile dissimilarities of actors..."))); int metric = graphMetricStrToType(metricStr); createMatrixDissimilarities(AM, DSM, metric, varLocation, diagonal, considerWeights); if (progressCanceled()) { file.close(); return false; } progressStatus(tr("Writing tie profile dissimilarities to file: ") + fileName); if (format == ReportFormat::Csv) { outText.setRealNumberPrecision(m_reportsRealPrecision); writeMatrixCSVTable(outText, DSM, true); file.close(); return true; } outText.setRealNumberPrecision(m_reportsRealPrecision); outText << htmlHead; outText << "

"; outText << tr("DISSIMILARITIES MATRIX"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << "" << tr("Variables in: ") << "" << ((varLocation != "Rows" && varLocation != "Columns") ? "Concatenated rows + columns " : varLocation) << "

"; outText << "

" << "" << tr("Metric: ") << "" << metricStr << "

"; outText << "

" << "" << tr("Diagonal: ") << "" << ((diagonal) ? "Included" : "Not included") << "

"; outText << "

" << "" << tr("Range: ") << ""; if (metric == METRIC_JACCARD_INDEX) outText << tr("0 < C < 1"); else outText << tr("0 < C "); outText << "

"; outText << "

" << "
" << "" << tr("Analysis results ") << "" << "

"; writeMatrixHTMLTable(outText, DSM, true); outText << "

"; outText << "" << tr("DSM = 0 ") << "" << tr("when two actors have no tie profile dissimilarities. The actors have the same ties to all others.") << "
" << "" << tr("DSM > 0 ") << "" << tr("when the two actors have differences in their ties to other actors."); outText << "

"; outText << "

 

"; outText << "

"; outText << tr("Dissimilarity Matrix Report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes similarity matrix based on a matching measure to given html file * @param fileName * @param measure * @param matrix * @param varLocation * @param diagonal * @param considerWeights */ bool Graph::writeMatrixSimilarityMatching(const QString fileName, const QString &measure, const QString &matrix, const QString &varLocation, const bool &diagonal, const bool &considerWeights, const int &format) { qCDebug(lcReporting) << "Writing similarity matrix to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); int measureInt = graphMetricStrToType(measure); Q_UNUSED(considerWeights); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open (for writing) file:" << fileName; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); progressStatus((tr("Examining pair-wise similarity of actors..."))); Matrix SCM; int N = vertices(); if (matrix == "Adjacency") { createMatrixAdjacency(); if (progressCanceled()) { file.close(); return false; } createMatrixSimilarityMatching(AM, SCM, measureInt, varLocation, diagonal, considerWeights); } else if (matrix == "Distances") { graphDistancesGeodesic(); createMatrixSimilarityMatching(DM, SCM, measureInt, varLocation, diagonal, considerWeights); } else { file.close(); return false; } if (progressCanceled()) { file.close(); return false; } QString pMsg = tr("Writing Similarity coefficients to file. \nPlease wait..."); progressStatus(pMsg); if (format == ReportFormat::Csv) { outText.setRealNumberPrecision(m_reportsRealPrecision); writeMatrixCSVTable(outText, SCM, true); file.close(); return true; } outText.setRealNumberPrecision(m_reportsRealPrecision); outText << htmlHead; outText << "

"; outText << tr("SIMILARITY MATRIX: MATCHING COEFFICIENTS (SMMC)"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << "" << tr("Input matrix: ") << "" << matrix << "

"; outText << "

" << "" << tr("Variables in: ") << "" << ((varLocation != "Rows" && varLocation != "Columns") ? "Concatenated rows + columns " : varLocation) << "

"; outText << "

" << "" << tr("Matching measure: ") << "" << measure << "

"; outText << "

" << "" << tr("Diagonal: ") << "" << ((diagonal) ? "Included" : "Not included") << "

"; outText << "

" << "" << tr("SMMC range: ") << ""; if (measureInt == METRIC_HAMMING_DISTANCE) outText << tr("0 < C"); else outText << tr("0 < C < 1"); outText << "

"; outText << "

" << "
" << "" << tr("Analysis results ") << "" << "

"; writeMatrixHTMLTable(outText, SCM, true); outText << "

"; if (measureInt == METRIC_HAMMING_DISTANCE) { outText << "" << tr("SMMC = 0 ") << "" << tr("when two actors are absolutely similar (no tie/distance differences).") << "
" << "" << tr("SMMC > 0 ") << "" << tr("when two actors have some differences in their ties/distances, " "i.e. SMMC = 3 means the two actors have 3 differences in their tie/distance profiles to other actors."); } else { outText << "" << tr("SMMC = 0 ") << "" << tr("when there is no tie profile similarity at all.") << "
" << "" << tr("SMMC > 0 ") << "" << tr("when two actors have some matches in their ties/distances, " "i.e. SMMC = 1 means the two actors have their ties to other actors exactly the same all the time."); } outText << "

"; outText << "

 

"; outText << "

"; outText << tr("Similarity Matrix by Matching Measure Report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Calls Graph::createMatrixSimilarityPearson() and * writes Pearson Correlation Coefficients to given file * @param fileName * @param considerWeights */ bool Graph::writeMatrixSimilarityPearson(const QString fileName, const bool considerWeights, const QString &matrix, const QString &varLocation, const bool &diagonal, const int &format) { qCDebug(lcReporting) << "Writing Pearson Correlation coefficients to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); Q_UNUSED(considerWeights); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open (for writing) file:" << fileName; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); progressStatus((tr("Calculating Pearson Correlations..."))); Matrix PCC; int N = vertices(); if (matrix == "Adjacency") { createMatrixAdjacency(); if (progressCanceled()) { file.close(); return false; } createMatrixSimilarityPearson(AM, PCC, varLocation, diagonal); } else if (matrix == "Distances") { graphDistancesGeodesic(); if (progressCanceled()) { file.close(); return false; } createMatrixSimilarityPearson(DM, PCC, varLocation, diagonal); } else { file.close(); return false; } if (progressCanceled()) { file.close(); return false; } progressStatus(tr("Writing Pearson coefficients to file: ") + fileName); if (format == ReportFormat::Csv) { outText.setRealNumberPrecision(m_reportsRealPrecision); writeMatrixCSVTable(outText, PCC, true); file.close(); return true; } outText.setRealNumberPrecision(m_reportsRealPrecision); outText << htmlHead; outText << "

"; outText << tr("PEARSON CORRELATION COEFFICIENTS (PCC) MATRIX"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << "" << tr("Input matrix: ") << "" << matrix << "

"; outText << "

" << "" << tr("Variables in: ") << "" << ((varLocation != "Rows" && varLocation != "Columns") ? "Concatenated rows + columns " : varLocation) << "

"; outText << "

" << "" << tr("Diagonal: ") << "" << ((diagonal) ? "Included" : "Not included") << "

"; outText << "

" << "" << tr("PCC range: ") << "" << "-1 < C < 1" << "

"; outText << "

" << "" << "
" << tr("Analysis results ") << "
" << "

"; writeMatrixHTMLTable(outText, PCC, true); outText << "

"; outText << "" << tr("PCC = 0 ") << "" << tr("when there is no correlation at all.") << "
" << "" << tr("PCC > 0 ") << "" << tr("when there is positive correlation, " "i.e. +1 means actors with same patterns of ties/distances.") << "
" << "" << tr("PCC < 0 ") << "" << tr("when there is negative correlation, " "i.e. -1 for actors with exactly opposite patterns of ties.") << "
"; outText << "

"; outText << "

 

"; outText << "

"; outText << tr("Pearson Correlation Coefficients Report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes a "famous" dataset to the given file * Datasets are hardcoded! They are exported in the given fileName... * * TODO: Move all these datasets to a separate class * * @param fileName */ void Graph::writeDataSetToFile(const QString dir, const QString fileName) { qCDebug(lcReporting) << "Writing famous dataset to file:" << dir + fileName; QFile file(dir + fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open (for writing) file:" << fileName; progressStatus(tr("Error. Could not write to ") + fileName); return; } QTextStream outText(&file); QString datasetDescription = QString(); QFile virtualFile(":/data/" + fileName); if (virtualFile.open(QIODevice::ReadOnly | QIODevice::Text)) { outText << virtualFile.readAll(); } else { qWarning() << "Could not load resource: " << fileName; return; } virtualFile.close(); qCDebug(lcReporting) << " ... writing dataset "; if (fileName == "Campnet.paj") { qCDebug(lcReporting) << " ... to " << fileName; datasetDescription = tr("Campnet dataset\n\n" "The dataset is the interactions among 18 people, " "including 4 instructors, " "participating in a 3-week workshop. \n" "Each person was asked to rank everyone else in terms of " "how much time they spent with them.\n" "This dataset shows only top 3 choices for each respondent" "(week 2 and week 3). Thus, there is a 1 for xij " "if person i listed person j as one of their top 3 interactors.\n\n" "The Camp data were collected by Steve Borgatti, " "Russ Bernard and Bert Pelto in 1992 at the NSF Summer " "Institute for Ethnographic Research Methods.\n " "During the 3-week workshop, all the participants and " "instructors were housed at the same motel and spent " "a great deal of time together. \n" "The participants were all faculty in Anthropology " "except Holly, who was a PhD student. "); } if (fileName == "Herschel_Graph.paj") { qCDebug(lcReporting) << " ... to " << fileName; datasetDescription = tr("Herschel graph \n\n" "The Herschel graph is the smallest nonhamiltonian " "polyhedral graph. \n" "It is the unique such graph on 11 nodes, " "and has 18 edges."); } else if (fileName == "Krackhardt_High-tech_managers.paj") { qCDebug(lcReporting) << " ... to " << fileName; datasetDescription = tr("High-tech Managers\n\n" "Krackhardt's High-tech Managers is a famous social network " "of 21 managers of a high-tech US company. \n\n" "The company manufactured high-tech equipment " "and had just over 100 employees with 21 managers. " "David Krackhardt collected the data to assess the effects " "of a recent management intervention program. \n\n" "The network consists of 3 relations:\n" "- Advice\n" "- Friendship\n" "- Reports To\n" "Each manager was asked to whom do you go to for advice and who is your friend. " "Data for the \"whom do you report\" relation was taken from company documents. \n\n" "This data is used by Wasserman and Faust in their seminal network analysis book.\n\n" "Krackhardt D. (1987). Cognitive social structures. Social Networks, 9, 104-134."); } else if (fileName == "Padgett_Florentine_Families.paj") { datasetDescription = tr("Padgett's Florentine_Families\n\n" "This famous data set includes 16 families who were fighting " "each other to gain political control of the city of Florence " "circa 1430. Among the 16 families, the Medicis and the Strozzis " "were the two most prominent with factions formed around them.\n\n" "The data set is actually a subset of the original data on social " "relations among 116 Renaissance Florentine Families collected " "by John Padgett. This subset was used by Breiger & Pattison (1986) " "in their paper about local role analysis.\n\n" "Padgett researched historical documents to code two relations: " "Business ties (loans, credits, partnerships)\n" "Marrital ties (marriage alliances).\n\n" "Breiger R. and Pattison P. (1986). Cumulated social roles: The " "duality of persons and their algebras. Social Networks, 8, 215-256. "); } else if (fileName == "Zachary_Karate_Club.dl") { datasetDescription = tr("Zachary Karate Club \n\n" "The Zachary Karate Club is a well-known social network of 34 members" " of a university karate club studied by Wayne W. Zachary from 1970 to 1972.\n\n" "During the study, disputes among two members led to club splitting into two groups. " "Zachary documented 78 ties between members who interacted outside the club and " "used the collected data and an information flow model to explain the split-up. \n\n" "There are two relations (matrices) in this network:" "The ZACHE relation represents the presence or absence of ties among the actors. " "The ZACHC relation indicates the relative strength of their associations " "(number of situations in and outside the club in which interactions occurred).\n\n" "Zachary W. (1977). An information flow model for conflict and fission in small groups. " "Journal of Anthropological Research, 33, 452-473. "); } else if (fileName == "Galaskiewicz_CEOs_and_clubs_affiliation_network_data.2sm") { datasetDescription = tr("Galaskiewicz's CEOs and Clubs\n\n" "The affiliation network of the chief executive officers " "and their spouses from 26 corporations and banks in 15 clubs, " "corporate and cultural boards. " "Membership was during the period 1978-1981\n\n" "This is a 26x15 affiliation matrix, where the rows " "correspond to the 26 CEOs and the columns to the 15 clubs. \n\n" "This data was originally collected by Galaskiewicz (1985) " "and is used by Wasserman and Faust in Social Network Analysis: Methods and Applications (1994).\n\n" "Galaskiewicz, J. (1985). Social Organization of an Urban Grants Economy. New York: Academic Press. "); } else if (fileName == "Thurman_Office_Networks_Coalitions.dl") { datasetDescription = tr("Thurman's Office Networks and Coalitions\n\n" "In the late 70s, B. Thurman spent 16 months " "observing the interactions among employees in " "the overseas office of a large international " "corporation. \n" "During this time, two major disputes erupted " "in a subgroup of fifteen people. \n" "Thurman analyzed the outcome of these disputes " "in terms of the network of formal and informal " "associations among those involved.\n" "\n" "This labeled dataset contains two relations (15x15 matrices): \n" "THURA is a 15x15 non-symmetric, binary matrix showing " "the formal organizational chart of the employees.\n\n" "THURM is a 15x15 symmetric binary matrix which " "shows the actors linked by multiplex ties. \n\n" "Thurman B. (1979). In the office: Networks and coalitions. Social Networks, 2, 47-63"); } else if (fileName == "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl") { datasetDescription = tr("Corporate Interlocks in Netherlands\n\n" "A 16x16 symmetric, binary matrix." "This data represent corporate interlocks among " "the major business entities in the Netherlands. " "The data were gathered during a 6-year research " "project which was concluded in 1976 in nine " "European countries and the USA \n\n" "Stokman F., Wasseur F. and Elsas D. (1985). " "The Dutch network: " "Types of interlocks and network structure. " "In F. Stokman, R. Ziegler & J. Scott (eds), " "Networks of corporate power. Cambridge: Polity Press, 1985"); } else if (fileName == "Stokman_Ziegler_Corporate_Interlocks_West_Germany.dl") { datasetDescription = tr("Corporate Interlocks in West Germany\n\n" "A 15x15 symmetric, binary matrix." "This data represent corporate interlocks among " "the major business entities in the West Germany. " "The data were gathered during a 6-year research " "project which was concluded in 1976 in nine " "European countries and the USA \n\n" "Ziegler R., Bender R. and Biehler H. (1985). " "Industry and banking in the German corporate " "network. " "In F. Stokman, R. Ziegler & J. Scott (eds), " "Networks of corporate power. Cambridge: Polity Press, 1985. "); } else if (fileName == "Bernard_Killworth_Fraternity.dl") { datasetDescription = tr("Bernard and Killworth Fraternity\n\n" "Bernard & Killworth recorded the interactions among students living in a fraternity at " "a West Virginia college. Subjects had been residents in the fraternity from 3 months to 3 years. " "This network dataset contains two relations: \n\n" "The BKFRAB relation is symmetric and valued. It counts the number of times a pair of subjects were " "seen in conversation by an unobtrusive observer (observation time: 21 hours a day, for five days). \n\n" "The BKFRAC relation is non-symmetric and valued. Contains rankings made by the subjects themselves of " "how frequently they interacted with other subjects in the observation week. \n\n" "Knoke D. and Wood J. (1981). Organized for action: Commitment in voluntary associations. " "New Brunswick, NJ: Rutgers University Press. Knoke D. and Kuklinski J. (1982). " "Network analysis, Beverly Hills, CA: Sage"); } else if (fileName == "Freeman_EIES_networks_32actors.dl") { qCDebug(lcReporting) << " ... to " << fileName; datasetDescription = tr( "Freeman's EIES Networks\n\n" "This data comes from an early experiment on computer mediated communication. \n" "Fifty academics were allowed to contact each other via an " "Electronic Information Exchange System (EIES). " "The data collected consisted of all messages sent plus acquaintance " "relationships at two time periods.\n\n" "The data includes the 32 actors who completed the study and \n" "the following three 32x32 relations: \n\n" "TIME_1 non-symmetric, valued\n" "TIME_2 non-symmetric, valued\n" "NUMBER_OF_MESSAGES non-symmetric, valued\n\n" "TIME_1 and TIME_2 give the acquaintance information at the beginning " "and end of the study. This is coded as follows: \n" "4 = close personal fiend, \n" "3 = friend, \n" "2= person I've met, \n" "1 = person I've heard of but not met, and \n" "0 = person unknown to me (or no reply). \n\n" "NUMBER_OF MESSAGES is the total number of messages person i \n" "sent to j over the entire period of the study. "); } else if (fileName == "Freeman_EIES_network_48actors_Acquaintanceship_at_time_1.dl") { qCDebug(lcReporting) << " ... to " << fileName; datasetDescription = tr("Freeman's EIES network (Acquaintanceship) at time 1"); } else if (fileName == "Freeman_EIES_network_48actors_Acquaintanceship_at_time_2.dl") { qCDebug(lcReporting) << " ... to " << fileName; datasetDescription = tr("Freeman's EIES network (Acquaintanceship) at time 2"); } else if (fileName == "Freeman_EIES_network_48actors_Messages.dl") { datasetDescription = tr("Freeman's EIES network (Messages)"); qCDebug(lcReporting) << " ... to " << fileName; } else if (fileName == "Freeman_34_possible_graphs_with_N_5_multirelational.paj") { datasetDescription = tr("Freeman's 34 possible graphs of N=5\n\n" "This data comes from Freeman's (1979) seminal paper " "\"Centrality in social networks\".\n" "It illustrates all 34 possible graphs of five nodes. \n" "Freeman used them to calculate and compare the three measures " "of Centrality: Degree, Betweenness and Closeness. \n" "Use Relation buttons on the toolbar to move between the graphs."); } else if (fileName == "Mexican_Power_Network_1940s.lst") { datasetDescription = tr("Mexican Power Network in the 1940s\n\n"); } else if (fileName == "Knoke_Bureaucracies_Network.pajek") { datasetDescription = tr("Knoke Bureaucracies\n\n" "In 1978, Knoke & Wood collected data from workers at 95 organizations in Indianapolis. " "Respondents indicated with which other organizations their own organization had any " "of 13 different types of relationships. \n" "Knoke and Kuklinski (1982) selected a subset of 10 organizations and two relationships: " "information exchange and money exchange.\n" "This dataset is directed and not symmetric.\n" "Information exchange is recorded in KNOKI relation while money exchange in KNOKM ."); } else if (fileName == "Stephenson_Zelen_40_AIDS_patients_sex_contact.paj") { qCDebug(lcReporting) << "Stephenson_Zelen_40_AIDS_patiens"; datasetDescription = tr("Stephenson & Zelen's AIDS patients network (sex contact)\n\n" "The data described by Auerbach et al. (1984) and Klovdahl (1985) consists of information on 40 homosexual men diagnosed with AIDS. " "Initially, 19 men residing in the Los Angeles and Orange County area were interviewed about their previous sexual contacts. " "This information led to the subsequent identification of an additional 21 sexual partners in San Francisco, New York and other parts of the United States. " "All 40 homosexual men were linked to each other through sexual contact."); } else if (fileName == "Stephenson_Zelen_5actors_6edges_IC_test_dataset.paj") { qCDebug(lcReporting) << "Stephenson_Zelen_5actors_6edges_IC_test_dataset.paj"; } else if (fileName == "Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj") { datasetDescription = tr("Galada baboon colony network (H22a) \n\n" "A network of the Galada baboon colony, as described by Dunbar and Dunbar (1975). This is the first set of observations (H22a) and was made on 12 baboons.\n\n" "The lines connecting two points (baboons) represent nonagonistic interactions (generally grooming behavior) and the frequency of such interactions is recorded by the edge weight. " "Data derived from Stephenson & Zelen seminal 1989 paper where they introduced Information Centrality."); } else if (fileName == "Wasserman_Faust_7actors_star_circle_line_graphs.paj") { qCDebug(lcReporting) << "Wasserman_Faust_7actors_star_circle_line_graphs.paj"; datasetDescription = tr("Wasserman & Faust's 7 actors graphs\n\n"); } else if (fileName == "Wasserman_Faust_Countries_Trade_Data_Basic_Manufactured_Goods.pajek") { datasetDescription = tr("Wasserman & Faust's Countries Trade Data (manufactured goods)\n\n"); qCDebug(lcReporting) << " Wasserman_Faust_Countries_Trade_Data_Basic_Manufactured_Goods.pajek written... "; } else if (fileName == "Petersen_Graph.paj") { qCDebug(lcReporting) << " Petersen_Graph.paj written... "; datasetDescription = tr("This data set is just a famous non-planar mathematical graph, \n" "named after Julius Petersen, who constructed it in 1898.\n" "The Petersen graph is undirected with 10 vertices and 15 edges \n" "and the smallest bridgeless cubic graph with no three-edge-coloring.\n" "This small graph serves as a useful example and counterexample \n" "for many problems in graph theory. "); } file.close(); if (!datasetDescription.isEmpty()) { emit signalDatasetDescription(datasetDescription); } } /** * @brief Computes and writes the specified matrix of the social network to an HTML report file. * * Supported matrix types: adjacency, laplacian, degree, geodesic distances, * shortest paths (geodesics), inverse adjacency, reachability, transpose, * cocitation, and tie-profile distance matrices (Euclidean, Hamming, Jaccard, * Manhattan, Chebyshev). * * @param fn Path to the output HTML report file. * @param matrix Matrix type constant (e.g., MATRIX_ADJACENCY, MATRIX_DISTANCES). * @param considerWeights If true, edge weights are used in distance computations. * @param inverseWeights If true, edge weights are inverted before use. * @param dropIsolates If true, isolate nodes are excluded from the analysis. * @param varLocation Whether variables are in rows or columns ("rows"/"cols"), * used for tie-profile distance matrices. * @param simpler Reserved for future use. * @return true on success, false if the computation was cancelled or an error occurred. */ bool Graph::writeMatrix(const QString &fn, const int &matrix, const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates, const QString &varLocation, const bool &simpler, const int &format) { qCDebug(lcReporting) << "Writing specified matrix:" << matrix << "to file:" << fn << " -- dropIsolates:" << dropIsolates; QElapsedTimer computationTimer; computationTimer.start(); Q_UNUSED(simpler); QFile file(fn); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open (for writing) file:" << fn; progressStatus(tr("Error. Could not write to ") + fn); return false; } bool inverseResult = false; int N = vertices(); switch (matrix) { case MATRIX_ADJACENCY: createMatrixAdjacency(); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } progressStatus(tr("Adjacency recomputed. Writing Adjacency Matrix...")); break; case MATRIX_LAPLACIAN: progressStatus(tr("Need to recompute Adjacency Matrix. Please wait...")); createMatrixAdjacency(); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } progressStatus(tr("Adjacency recomputed. Writing Laplacian Matrix...")); break; case MATRIX_DEGREE: progressStatus(tr("Need to recompute Adjacency Matrix. Please wait...")); createMatrixAdjacency(); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } progressStatus(tr("Adjacency recomputed. Writing Degree Matrix...")); break; case MATRIX_DISTANCES: if (!graphMatrixDistanceGeodesicCreate(considerWeights, inverseWeights, dropIsolates)) { file.close(); progressStatus(tr("Computation canceled.")); return false; } progressStatus(tr("Distances recomputed. Writing Distances Matrix...")); break; case MATRIX_GEODESICS: graphMatrixShortestPathsCreate(considerWeights, inverseWeights, false); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } progressStatus(tr("Distances recomputed. Writing Shortest Paths Matrix...")); break; case MATRIX_ADJACENCY_INVERSE: progressStatus(tr("Computing Inverse Adjacency Matrix. Please wait...")); inverseResult = createMatrixAdjacencyInverse(QString("lu")); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } progressStatus(tr("Inverse Adjacency Matrix computed. Writing Matrix...")); break; case MATRIX_REACHABILITY: createMatrixReachability(); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } progressStatus(tr("Writing Reachability Matrix...")); break; case MATRIX_ADJACENCY_TRANSPOSE: progressStatus(tr("Need to recompute Adjacency Matrix. Please wait...")); createMatrixAdjacency(); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } progressStatus(tr("Adjacency recomputed. Writing Adjacency Matrix...")); break; case MATRIX_COCITATION: progressStatus(tr("Need to recompute Adjacency Matrix. Please wait...")); createMatrixAdjacency(); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } progressStatus(tr("Adjacency recomputed. Writing Adjacency Matrix...")); break; case MATRIX_DISTANCES_HAMMING: case MATRIX_DISTANCES_JACCARD: case MATRIX_DISTANCES_MANHATTAN: case MATRIX_DISTANCES_EUCLIDEAN: case MATRIX_DISTANCES_CHEBYSHEV: progressStatus(tr("Need to recompute tie profile distances. Please wait...")); createMatrixAdjacency(); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } progressStatus(tr("Tie profile distances recomputed. Writing matrix...")); break; default: file.close(); progressStatus(tr("Error: unsupported matrix type.")); return false; } QTextStream outText(&file); if (format == ReportFormat::Csv) { // Lean CSV branch: same Matrix& reference and printInfinity flag per case as the HTML // branch below, just no htmlHead/title/description/footer prose - a CSV consumer has no // use for it. Kept as its own self-contained switch (rather than interleaved with the // HTML switches below) so the existing HTML path stays completely untouched. switch (matrix) { case MATRIX_ADJACENCY: writeMatrixCSVTable(outText, AM, false); break; case MATRIX_LAPLACIAN: writeMatrixCSVTable(outText, AM.laplacianMatrix(), false); break; case MATRIX_DEGREE: writeMatrixCSVTable(outText, AM.degreeMatrix(), false); break; case MATRIX_DISTANCES: writeMatrixCSVTable(outText, DM, true); break; case MATRIX_GEODESICS: writeMatrixCSVTable(outText, SIGMA, true); break; case MATRIX_ADJACENCY_INVERSE: if (inverseResult) { writeMatrixCSVTable(outText, invAM, true); } break; case MATRIX_REACHABILITY: writeMatrixCSVTable(outText, XRM, true); break; case MATRIX_ADJACENCY_TRANSPOSE: writeMatrixCSVTable(outText, AM.transpose(), true); break; case MATRIX_COCITATION: writeMatrixCSVTable(outText, AM.cocitationMatrix(), true); break; case MATRIX_DISTANCES_EUCLIDEAN: writeMatrixCSVTable(outText, AM.distancesMatrix(METRIC_EUCLIDEAN_DISTANCE, varLocation, false, true), false); break; case MATRIX_DISTANCES_HAMMING: writeMatrixCSVTable(outText, AM.distancesMatrix(METRIC_HAMMING_DISTANCE, varLocation, false, true), false); break; case MATRIX_DISTANCES_JACCARD: writeMatrixCSVTable(outText, AM.distancesMatrix(METRIC_JACCARD_INDEX, "Rows", false, true), false); break; case MATRIX_DISTANCES_MANHATTAN: writeMatrixCSVTable(outText, AM.distancesMatrix(METRIC_MANHATTAN_DISTANCE, varLocation, false, true), false); break; case MATRIX_DISTANCES_CHEBYSHEV: writeMatrixCSVTable(outText, AM.distancesMatrix(METRIC_CHEBYSHEV_MAXIMUM, varLocation, false, true), false); break; default: break; } file.close(); return true; } outText << htmlHead; outText << "

"; switch (matrix) { case MATRIX_ADJACENCY: outText << tr("ADJACENCY MATRIX REPORT"); break; case MATRIX_LAPLACIAN: outText << tr("LAPLACIAN MATRIX REPORT"); break; case MATRIX_DEGREE: outText << tr("DEGREE MATRIX REPORT"); break; case MATRIX_DISTANCES: outText << tr("DISTANCES MATRIX REPORT"); break; case MATRIX_GEODESICS: outText << tr("SHORTEST PATHS (GEODESICS) MATRIX REPORT"); break; case MATRIX_ADJACENCY_INVERSE: outText << tr("INVERSE ADJACENCY MATRIX REPORT"); break; case MATRIX_REACHABILITY: outText << tr("REACHABILITY MATRIX REPORT"); break; case MATRIX_ADJACENCY_TRANSPOSE: outText << tr("TRANSPOSE OF ADJACENCY MATRIX REPORT"); break; case MATRIX_COCITATION: outText << tr("COCITATION MATRIX REPORT"); break; case MATRIX_DISTANCES_EUCLIDEAN: outText << tr("EUCLIDEAN DISTANCE MATRIX REPORT"); break; case MATRIX_DISTANCES_HAMMING: outText << tr("HAMMING DISTANCE MATRIX REPORT"); break; case MATRIX_DISTANCES_JACCARD: outText << tr("JACCARD DISTANCE MATRIX REPORT"); break; case MATRIX_DISTANCES_MANHATTAN: outText << tr("MANHATTAN DISTANCE MATRIX REPORT"); break; default: break; } outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; switch (matrix) { case MATRIX_ADJACENCY: outText << "

" << tr("The adjacency matrix, AM, of a social network is a NxN matrix ") << tr("where each element (i,j) is the value of the edge from " "actor i to actor j, or 0 if no edge exists.") << "
" << "

"; writeMatrixHTMLTable(outText, AM, true, false, false); break; case MATRIX_LAPLACIAN: outText << "

" << tr("The laplacian matrix L of a social network is a NxN matrix ") << tr("with L = D - A, where D the degree matrix and A the " "adjacency matrix. ") << "
" << tr("The elements of L are: " "
" "- Li,j = di, if i = j,
" "- Li,j = -1, if i ≠ j and there is an edge (i,j)
" "- and all other elements zero.
") << "
" << "

"; writeMatrixHTMLTable(outText, AM.laplacianMatrix(), true, false, false); break; case MATRIX_DEGREE: outText << "

" << tr("The degree matrix D of a social network is a NxN matrix ") << tr("where each element (i,i) is the degree of actor i " "and all other elements are zero.") << "
" << "

"; writeMatrixHTMLTable(outText, AM.degreeMatrix(), true, false, false); break; case MATRIX_DISTANCES: outText << "

" << tr("The distance matrix of a social network is a NxN matrix " "where each element (i,j) is the geodesic distance " "(length of shortest path) from actor i to actor j, " "or infinity if no shortest path exists.") << "
" << "

"; writeMatrixHTMLTable(outText, DM, true); break; case MATRIX_GEODESICS: outText << "

" << tr("The geodesics matrix of a social network is a NxN matrix ") << tr("where each element (i,j) is the number of shortest paths" "(geodesics) from actor i to actor j, " "or infinity if no shortest path exists.") << "
" << "

"; writeMatrixHTMLTable(outText, SIGMA, true); break; case MATRIX_ADJACENCY_INVERSE: if (!inverseResult) { outText << "

" << tr("The adjacency matrix is singular.") << "
" << "

"; } else { writeMatrixHTMLTable(outText, invAM, true); } break; case MATRIX_REACHABILITY: outText << "

" << tr("The reachability matrix R of a social network is a NxN matrix " "where each element R(i,j) is 1 if actors j is reachable from i " "otherwise 0.
" "Two nodes are reachable if there is a walk between them " "(their geodesic distance is non-zero).
" "Essentially the reachability matrix is a dichotomized " "geodesics matrix.") << "
" << "

"; writeMatrixHTMLTable(outText, XRM, true); break; case MATRIX_ADJACENCY_TRANSPOSE: outText << "

" << tr("The adjacency matrix AM of a social network is a NxN matrix " "where each element (i,j) is the value of the edge from " "actor i to actor j, or 0 if no edge exists. ") << "
" << tr("This is the transpose of the adjacency matrix, AMT, " "a matrix whose (i,j) element is the (j,i) element of AM.") << "

"; writeMatrixHTMLTable(outText, AM.transpose(), true); break; case MATRIX_COCITATION: outText << "

" << tr("The Cocitation matrix, C = AT * A, is a " "NxN matrix where each element (i,j) is the number of " "actors that have outbound ties/links to both actors i and j.") << "
" << tr("The diagonal elements, Cii, of the Cocitation " "matrix are equal to the number of inbound edges of i (inDegree).") << "
" << tr("C is a symmetric matrix.") << "

"; writeMatrixHTMLTable(outText, AM.cocitationMatrix(), true); break; case MATRIX_DISTANCES_EUCLIDEAN: outText << "

" << tr("The Euclidean distances matrix is a " "NxN matrix where each element (i,j) is the Euclidean distance" "of the tie profiles between actors i and j, namely the " "square root of the sum of their squared differences.") << "
" << "

"; writeMatrixHTMLTable(outText, AM.distancesMatrix(METRIC_EUCLIDEAN_DISTANCE, varLocation, false, true), true, false, false); break; case MATRIX_DISTANCES_HAMMING: outText << "

" << tr("The Hamming distances matrix is a " "NxN matrix where each element (i,j) is the Hamming distance" "of the tie profiles between actors i and j, namely the " "number of different ties to other actors.") << "
" << "

"; writeMatrixHTMLTable(outText, AM.distancesMatrix(METRIC_HAMMING_DISTANCE, varLocation, false, true), true, false, false); break; case MATRIX_DISTANCES_JACCARD: outText << "

" << tr("The Jaccard distances matrix is a " "NxN matrix where each element (i,j) is the Jaccard distance" "of the tie profiles between actors i and j.") << "
" << "

"; writeMatrixHTMLTable(outText, AM.distancesMatrix(METRIC_JACCARD_INDEX, "Rows", false, true), true, false, false); break; case MATRIX_DISTANCES_MANHATTAN: outText << "

" << tr("The Manhattan distances matrix is a " "NxN matrix where each element (i,j) is the Manhattan distance" "of the tie profiles between actors i and j, namely the " "sum of their absolute differences.") << "
" << "

"; writeMatrixHTMLTable(outText, AM.distancesMatrix(METRIC_MANHATTAN_DISTANCE, varLocation, false, true), true, false, false); break; case MATRIX_DISTANCES_CHEBYSHEV: outText << "

" << tr("The Chebyshev distances matrix is a " "NxN matrix where each element (i,j) is the Chebyshev distance" "of the tie profiles between actors i and j, namely the greatest of their differences.") << "
" << "

"; writeMatrixHTMLTable(outText, AM.distancesMatrix(METRIC_CHEBYSHEV_MAXIMUM, varLocation, false, true), true, false, false); break; default: break; } outText << "

 

"; outText << "

"; outText << tr("Matrix report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes the matrix M as HTML to specified text stream outText * It is the same as Matrix::printHTMLTable except that * this method omits disabled vertices, thus the table header is correct * @param outText * @param M * @param markDiag * @param plain * @param printInfinity */ void Graph::writeMatrixHTMLTable(QTextStream &outText, Matrix &M, const bool &markDiag, const bool &plain, const bool &printInfinity, const bool &dropIsolates) { Q_UNUSED(plain); qCDebug(lcReporting) << "Graph::writeMatrixHTMLTable() -" << "markDiag" << markDiag << "plain" << plain << " dropIsolates " << dropIsolates; int rowCount = 0, i = 0, j = 0; qreal maxVal, minVal, element; bool hasRealNumbers = false; VList::const_iterator it, jt; QString pMsg = tr("Writing matrix to file. \nPlease wait..."); progressStatus(pMsg); M.findMinMaxValues(minVal, maxVal, hasRealNumbers); outText << ((hasRealNumbers) ? qSetRealNumberPrecision(3) : qSetRealNumberPrecision(0)); qCDebug(lcReporting) << "Graph::writeMatrixHTMLTable() - minVal" << minVal << "maxVal" << maxVal << "hasRealNumbers" << hasRealNumbers; outText << "
" << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled() || (dropIsolates && (*it)->isIsolated())) { continue; } outText << ""; } outText << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled() || (dropIsolates && (*it)->isIsolated())) { continue; } rowCount++; if (progressCanceled()) { return; } outText << ""; outText << ""; for (jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { if (!(*jt)->isEnabled() || (dropIsolates && (*jt)->isIsolated())) { continue; } outText << "number() == (*jt)->number()) ? " class=\"diag\">" : ">"); element = M.item(i, j); qCDebug(lcReporting) << "Graph::writeMatrixHTMLTable() - M(" << i << "," << j << ") =" << M.item(i, j); if ((element == RAND_MAX) && printInfinity) { // print inf symbol instead of RAND_MAX (distances matrix). outText << infinity; } else if (qAbs(element) > 1000.0) { // Beyond this magnitude, fixed notation prints a wall of digits with no real // meaning past qreal's ~15-17 significant digits of actual precision (e.g. the // Walks Total matrix, which sums the adjacency matrix's powers up to N-1 and // grows combinatorially). Scientific notation is honest about the precision // that's actually there instead of implying false exactness. See #266. outText << Qt::scientific << qSetRealNumberPrecision(3) << Qt::right << element; } else { outText << Qt::fixed << qSetRealNumberPrecision(hasRealNumbers ? 3 : 0) << Qt::right << element; } outText << ""; j++; } outText << ""; i++; j = 0; } outText << "
" << tr("Actor/Actor") << "" << (*it)->number() << "
" << (*it)->number() << "
"; outText << qSetFieldWidth(0) << "\n"; outText << "

" << "" << ("Values: ") << "" << ((hasRealNumbers) ? ("real numbers (printed decimals 3)") : ("integers only")) << "
" << "" << ("- Max value: ") << "" << ((maxVal == RAND_MAX) ? ((printInfinity) ? infinity : QString::number(maxVal)) + " (=not connected nodes, in distance matrix)" : QString::number(maxVal)) << "
" << "" << ("- Min value: ") << "" << ((minVal == RAND_MAX) ? ((printInfinity) ? infinity : QString::number(minVal)) + +" (usually denotes unconnected nodes, in distance matrix)" : QString::number(minVal)) << "

"; } /** * @brief Writes the matrix M as a comma-separated table to the specified text stream outText. * * CSV sibling of writeMatrixHTMLTable() (same vertex filtering, same RAND_MAX/large-magnitude/ * precision value-formatting rules), but lean: header row of vertex numbers, one data row per * vertex, no markup, no min/max summary paragraph - a CSV consumer (spreadsheet app) has no use * for prose. Row/column headers are always plain vertex numbers, never labels, so no CSV * quoting/escaping is needed here (unlike the per-node score tables planned for centrality/ * prestige reports, where free-text labels will need it). * * @param outText * @param M * @param printInfinity * @param dropIsolates */ void Graph::writeMatrixCSVTable(QTextStream &outText, Matrix &M, const bool &printInfinity, const bool &dropIsolates) { qCDebug(lcReporting) << "Graph::writeMatrixCSVTable() -" << " dropIsolates " << dropIsolates; int i = 0, j = 0; qreal maxVal, minVal, element; bool hasRealNumbers = false; VList::const_iterator it, jt; QString pMsg = tr("Writing matrix to file. \nPlease wait..."); progressStatus(pMsg); M.findMinMaxValues(minVal, maxVal, hasRealNumbers); Q_UNUSED(maxVal); Q_UNUSED(minVal); // Header row: blank corner cell, then one column per enabled (non-isolate, if dropIsolates) // vertex number. QStringList headerRow; headerRow << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled() || (dropIsolates && (*it)->isIsolated())) { continue; } headerRow << QString::number((*it)->number()); } outText << headerRow.join(",") << "\n"; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled() || (dropIsolates && (*it)->isIsolated())) { continue; } if (progressCanceled()) { return; } QStringList row; row << QString::number((*it)->number()); for (jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { if (!(*jt)->isEnabled() || (dropIsolates && (*jt)->isIsolated())) { continue; } element = M.item(i, j); if ((element == RAND_MAX) && printInfinity) { row << infinity; } else if (qAbs(element) > 1000.0) { // Same #266 rationale as writeMatrixHTMLTable(): past qreal's ~15-17 significant // digits of real precision, scientific notation is honest, fixed notation isn't. row << QString::number(element, 'e', 3); } else { row << QString::number(element, 'f', hasRealNumbers ? 3 : 0); } j++; } outText << row.join(",") << "\n"; i++; j = 0; } } /** Exports the adjacency matrix to a given textstream */ void Graph::writeMatrixAdjacencyTo(QTextStream &os, const bool &saveEdgeWeights) { qCDebug(lcReporting, "Graph: adjacencyMatrix(), writing matrix with %i vertices", vertices()); VList::const_iterator it, it1; qreal weight = RAND_MAX; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) continue; for (it1 = m_graph.cbegin(); it1 != m_graph.cend(); ++it1) { if (!(*it1)->isEnabled()) continue; if ((weight = edgeExists((*it)->number(), (*it1)->number())) != 0) { // os << static_cast (weight) << " "; os << ((saveEdgeWeights) ? weight : 1) << " "; } else os << "0 "; } os << "\n"; } } /** Writes the adjacency matrix of G to a specified file fn */ bool Graph::writeMatrixAdjacency(const QString fn, const bool &markDiag, const int &format) { qCDebug(lcReporting) << "Writing adjacency matrix to file:" << fn; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fn); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open (for writing) file:" << fn; progressStatus(tr("Error. Could not write to ") + fn); return false; } QTextStream outText(&file); int sum = 0; qreal weight = 0; int rowCount = 0; int N = vertices(); VList::const_iterator it, it1; QString pMsg = tr("Writing Adjacency Matrix to file. \nPlease wait..."); progressStatus(pMsg); if (format == ReportFormat::Csv) { // Doesn't reuse writeMatrixCSVTable() - this function (unlike writeMatrix(fn, // MATRIX_ADJACENCY)) deliberately computes each cell live via edgeExists() rather than // from a pre-built Matrix, to preserve real node numbers after deletions (see this // function's own doc comment). Same double-loop shape as the HTML branch below, just // comma-delimited. QStringList headerRow; headerRow << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) continue; headerRow << QString::number((*it)->number()); } outText << headerRow.join(",") << "\n"; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) continue; QStringList row; row << QString::number((*it)->number()); for (it1 = m_graph.cbegin(); it1 != m_graph.cend(); ++it1) { if (!(*it1)->isEnabled()) continue; weight = edgeExists((*it)->number(), (*it1)->number()); row << QString::number(weight != 0 ? weight : 0); } outText << row.join(",") << "\n"; } file.close(); return true; } outText << htmlHead; outText << "

"; outText << tr("ADJACENCY MATRIX"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("The adjacency matrix of a social network is a NxN matrix ") << tr("where each element (i,j) is the value of the edge from " "actor i to actor j, or 0 if no edge exists.") << "
" << "

"; outText << "" << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) continue; outText << ""; } outText << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { rowCount++; if (!(*it)->isEnabled()) continue; outText << ""; outText << ""; for (it1 = m_graph.cbegin(); it1 != m_graph.cend(); ++it1) { if (!(*it1)->isEnabled()) continue; outText << "number() == (*it1)->number()) ? " class=\"diag\">" : ">"); if ((weight = edgeExists((*it)->number(), (*it1)->number())) != 0) { sum++; outText << (weight); } else { outText << 0; } outText << ""; } outText << ""; } outText << "
" << tr("Actor/Actor") << "" << (*it)->number() << "
" << (*it)->number() << "
"; qCDebug(lcReporting, "Graph: Found a total of %i edge", sum); if (sum != edgesEnabled()) qCDebug(lcReporting, "Error in edge count found!!!"); else qCDebug(lcReporting, "Edge count OK!"); outText << "

 

"; outText << "

"; outText << tr("Adjacency matrix report,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); return true; } /** * @brief Writes a visual representation of the adjacency matrix of the graph to the specified file * * The resulting matrix HAS NO spaces between elements. * * @param fn * @param simpler */ void Graph::writeMatrixAdjacencyPlot(const QString fn, const bool &simpler) { qCDebug(lcReporting) << "Writing adjacency matrix plot to file:" << fn; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fn); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcReporting) << "Could not open (for writing) file:" << fn; progressStatus(tr("Error. Could not write to ") + fn); return; } QTextStream outText(&file); VList::const_iterator it, it1; int sum = 0; int rowCount = 0; int N = vertices(); qreal weight = 0; QString pMsg = tr("Plotting Adjacency Matrix. \nPlease wait..."); progressStatus(pMsg); if (!simpler) { outText << htmlHead; } else outText << htmlHeadLight; outText << "

"; outText << tr("ADJACENCY MATRIX PLOT"); outText << "

"; outText << "

" << "" << tr("Network name: ") << "" << getName() << "
" << "" << tr("Actors: ") << "" << N << "

"; outText << "

" << tr("This a plot of the network's adjacency matrix, a NxN matrix ") << tr("where each element (i,j) is filled if there is an edge from " "actor i to actor j, or not filled if no edge exists.") << "
" << "

"; if (!simpler) { outText << ""; outText << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { continue; } rowCount++; outText << ""; for (it1 = m_graph.cbegin(); it1 != m_graph.cend(); ++it1) { if (!(*it1)->isEnabled()) continue; if ((weight = edgeExists((*it)->number(), (*it1)->number())) != 0) { sum++; outText << ""; } else { outText << ""; } } outText << ""; } outText << "
" << QString("\xe2\x96\xa0") << "" // << "  " << QString("\xe2\x96\xa1") << "
"; } else { outText << "

"; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { continue; } rowCount++; for (it1 = m_graph.cbegin(); it1 != m_graph.cend(); ++it1) { if (!(*it1)->isEnabled()) continue; if ((weight = edgeExists((*it)->number(), (*it1)->number())) != 0) { sum++; outText << QString("\xe2\x96\xa0") << " "; } else { outText << QString("\xe2\x96\xa1") << " "; } } outText << "
" << "\n"; } outText << "

"; } qCDebug(lcReporting, "Graph: Found a total of %i edge", sum); if (sum != edgesEnabled()) qCDebug(lcReporting, "Error in edge count found!!!"); else qCDebug(lcReporting, "Edge count OK!"); outText << "

 

"; outText << "

"; outText << tr("Adjacency matrix plot,
"); outText << tr("Created by Social Network Visualizer v%1: %2") .arg(VERSION) .arg(actualDateTime.currentDateTime().toString(QString("ddd, dd.MMM.yyyy hh:mm:ss"))); outText << "
"; outText << tr("Computation time: %1 msecs").arg(computationTimer.elapsed()); outText << "

"; outText << htmlEnd; file.close(); } SocNetV-3.7/src/graph/reporting/graph_reports_settings.cpp000066400000000000000000000042511524402210300241030ustar00rootroot00000000000000 /** * @file graph_reports_settings.cpp * @brief Implements reporting configuration setters for the Graph class * (reports output directory, numeric precision, label length, chart type). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" /** * @brief Sets the directory where reports are saved * This is used when exporting prominence distribution images to be used in * HTML reports. * @param dir */ void Graph::setReportsDataDir(const QString &dir) { m_reportsDataDir = dir; } /** * @brief Sets the precision (number of fraction digits) the app will use * when writing real numbers in reports. * @param precision */ void Graph::setReportsRealNumberPrecision(const int &precision) { m_reportsRealPrecision = precision; } /** * @brief Sets the length of labels in reports * @param length */ void Graph::setReportsLabelLength(const int &length) { m_reportsLabelLength = length; } /** * @brief Sets the chart type in reports * @param type */ void Graph::setReportsChartType(const int &type) { qCDebug(lcReporting) << "Graph::setReportsChartType() - type:" << type; if (type == -1) { m_reportsChartType = ChartType::None; } else if (type == 0) { m_reportsChartType = ChartType::Spline; } else if (type == 1) { m_reportsChartType = ChartType::Area; } else if (type == 2) { m_reportsChartType = ChartType::Bars; } } /** * @brief Sets the output format (HTML or CSV) new reports are written in * @param format */ void Graph::setReportsOutputFormat(const int &format) { qCDebug(lcReporting) << "Graph::setReportsOutputFormat() - format:" << format; m_reportsOutputFormat = (format == ReportFormat::Csv) ? ReportFormat::Csv : ReportFormat::Html; } SocNetV-3.7/src/graph/similarity/000077500000000000000000000000001524402210300167535ustar00rootroot00000000000000SocNetV-3.7/src/graph/similarity/graph_similarity_matrices.cpp000066400000000000000000000110131524402210300247110ustar00rootroot00000000000000/** * @file graph_similarity_matrices.cpp * @brief Implements similarity and dissimilarity matrix construction * methods for the Graph class (matching, Pearson, and related metrics). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Calls Matrix:distancesMatrix to compute the dissimilarities matrix DSM * of the variables (rows, columns, both) in given input matrix using the * user defined metric * @param INPUT_MATRIX * @param DSM * @param metric * @param varLocation * @param diagonal * @param considerWeights */ void Graph::createMatrixDissimilarities(Matrix &INPUT_MATRIX, Matrix &DSM, const int &metric, const QString &varLocation, const bool &diagonal, const bool &considerWeights) { qCDebug(lcSimilarity) << "Graph::createMatrixDissimilarities() -metric" << metric; DSM = INPUT_MATRIX.distancesMatrix(metric, varLocation, diagonal, considerWeights, [this] { return progressCanceled(); }); // DSM.printMatrixConsole(true); } /** * @brief Calls Matrix:similarityMatrix to compute the similarity matrix SCM * of the variables (rows, columns, both) in given input matrix using the * selected matching measure. * * @param AM * @param SCM * @param rows */ void Graph::createMatrixSimilarityMatching(Matrix &AM, Matrix &SCM, const int &measure, const QString &varLocation, const bool &diagonal, const bool &considerWeights) { qCDebug(lcSimilarity) << "Graph::createMatrixSimilarityMatching()"; SCM.similarityMatrix(AM, measure, varLocation, diagonal, considerWeights, [this] { return progressCanceled(); }); } /** * @brief * The Pearson product-moment correlation coefficient (PPMCC, PCC or Pearson's r) * is a measure of the linear dependence between two variables X and Y. * * As a normalized version of the covariance, the PPMCC is computed with the formula: * r =\frac{\sum ^n _{i=1}(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum ^n _{i=1}(x_i - \bar{x})^2} \sqrt{\sum ^n _{i=1}(y_i - \bar{y})^2}} * * It gives a value between +1 and βˆ’1 inclusive, where 1 is total positive linear * correlation, 0 is no linear correlation, and βˆ’1 is total negative linear correlation. * * In SNA, Pearson correlations can be used to track the similarity between actors, * in terms of structural equivalence. * * This method creates an actor by actor NxN matrix PCC where the (i,j) element * is the Pearson correlation coefficient of actor i and actor j. * If the input matrix is the adjacency matrix, the PCC of two nodes measures * how related (similar, inverse or not related at all) their patterns of ties tend to be. * A positive value means there is strong linear association of the two actors, * while a negative value means the inverse. For instance a value of -1 means * the two actors have exactly opposite ties to other actors, while a value of 1 * means the actors have identical patterns of ties to other actors * (they are connected to the same actors). * * The correlation measure of similarity is particularly useful when the data on ties are valued * @param AM * @param PCC * @param rows */ void Graph::createMatrixSimilarityPearson(Matrix &AM, Matrix &PCC, const QString &varLocation, const bool &diagonal) { qCDebug(lcSimilarity) << "Graph::createMatrixSimilarityPearson()"; PCC.pearsonCorrelationCoefficients(AM, varLocation, diagonal, [this] { return progressCanceled(); }); qCDebug(lcSimilarity) << "Graph::createMatrixSimilarityPearson() - matrix PCC"; // PCC.printMatrixConsole(true); } SocNetV-3.7/src/graph/storage/000077500000000000000000000000001524402210300162315ustar00rootroot00000000000000SocNetV-3.7/src/graph/storage/graph_edges.cpp000066400000000000000000000614571524402210300212220ustar00rootroot00000000000000/** * @file graph_edges.cpp * @brief Implements edge storage and CRUD operations for the Graph class * (create/add/remove, enable/disable, existence queries, weights). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" // // Create/add/remove // /** * @brief Checks if edge (v1,v2) already exists, then creates it and signals the UI to draw it. * * This is the main entry point for edge creation, called from: * - Parser::signalCreateEdge (when loading a network file) * - MainWindow (when user clicks "add link" button) * - GraphicsWidget (when user middle-clicks two nodes) * * Edge type semantics: * - EdgeType::Undirected : stores both v1->v2 and v2->v1 internally (via edgeAdd), * draws a single undirected edge in the UI. * - EdgeType::Directed : stores only v1->v2. If v2->v1 already exists, * upgrades both to EdgeType::Reciprocated. * - EdgeType::Reciprocated: both directions exist with equal weight. * * Mixed-section Pajek files (*Arcs followed by *Edges): * Some Pajek files declare directed arcs in an *Arcs section AND undirected edges * in a subsequent *Edges section. The same node pair (v1,v2) may appear in both. * When *Arcs are processed first, v1->v2 is stored as a directed arc. * When *Edges is processed later, edgeExists(v1,v2) returns non-zero, so we must * NOT silently drop the request. If v2->v1 is still missing, we store the reverse * arc and upgrade the existing forward arc visual to Reciprocated. * * In that special case: * - We store the reverse arc v2->v1 using the undirected edge's weight/color/label. * The forward arc (from *Arcs) is left untouched to preserve its original data. * - We emit signalDrawEdge(v2, v1, ..., Reciprocated) β€” note the swapped arguments. * GraphicsWidget::drawEdge() with Reciprocated looks up the existing arc via * edgeKey(targetNum, sourceNum). Since edgeKey() is order-sensitive * (packs relation/v1/v2 in that order), passing (v2,v1) makes targetNum=v1, sourceNum=v2, * so the lookup resolves to edgeKey(v1,v2) which matches the key stored during *Arcs * processing. No new GraphicsEdge is created; setDirectionType() is called instead. * - drawArrows is forced true: a reciprocated edge always shows arrows on both ends * regardless of what the undirected request's drawArrows value was. * - We do NOT set m_graphIsDirected: adding the missing reverse makes the pair more * symmetric, not less. Directedness is determined by the overall graph state after * full import completes. * - We return true because a new arc was genuinely added to the graph. * * @param v1 Source node number * @param v2 Target node number * @param weight Edge weight * @param color Edge color * @param type Edge type: EdgeType::Undirected, Directed, or Reciprocated * @param drawArrows Whether to draw arrowheads in the UI * @param bezier Whether to draw the edge as a bezier curve * @param label Edge label (optional) * @param signalMW Whether to signal MainWindow after modifying graph state * @return true if a new edge (or reverse arc) was created, false if fully skipped */ bool Graph::edgeCreate(const int &v1, const int &v2, const qreal &weight, const QString &color, const int &type, const bool &drawArrows, const bool &bezier, const QString &label, const bool &signalMW, const QHash &edgeCustomAttributes) { // #30: zero-weight edges are visual-only. When the user has disabled // "Show zero-weight edges" in Settings, skip them entirely so no ghost // edges appear and the data model stays clean. if (weight == 0 && !initShowZeroWeightEdges) return false; // // GUARD: Check if v1->v2 already exists. // // This can legitimately happen in mixed Pajek files that have both an *Arcs // section and an *Edges section. The *Arcs section is processed first, // creating directed arcs. When the *Edges section is processed later, // some pairs may already be present as directed arcs. // if (edgeExists(v1, v2)) { // // Special case: caller wants an Undirected edge but v1->v2 was // previously stored as a directed arc (e.g. from a *Arcs section in // a mixed Pajek file). An undirected edge requires BOTH directions to // be stored internally. Check if the reverse v2->v1 is also present. // if (type == EdgeType::Undirected && !edgeExists(v2, v1)) { // v2->v1 is missing. Store it to complete the undirected pair. // // Weight/color/label policy: // We use the undirected edge's values for the reverse arc. // The forward arc (from *Arcs) is left untouched to preserve // its original explicitly declared data. The two directions may // therefore differ in metadata, which is acceptable: we prefer // not to silently overwrite explicitly declared arc data. // // Visual update: // We emit Reciprocated with swapped arguments (v2,v1) so that // drawEdge()'s internal lookup edgeKey(targetNum,sourceNum) // resolves to edgeKey(v1,v2), matching the key under which // the original forward arc was stored during *Arcs processing. // This upgrades the existing GraphicsEdge to bidirectional without // creating any duplicate visual object. // drawArrows is forced true since a reciprocated edge always // shows arrows on both ends. // // Directedness: // m_graphIsDirected is NOT set here. Adding the missing reverse // makes the pair more symmetric. Directedness is determined by // the overall graph state after full import completes. // // Store the reverse arc in the data model. edgeAdd(v2, v1, weight, EdgeType::Directed, label, ((weight == 0) ? initEdgeColorZero : color)); // Upgrade the existing forward arc visual to bidirectional. // Arguments are swapped (v2,v1) intentionally β€” see comment above. emit signalDrawEdge(v2, v1, weight, label, ((weight == 0) ? initEdgeColorZero : color), EdgeType::Reciprocated, true, // force drawArrows for reciprocated bezier, initEdgeWeightNumbers); // Update edge count and notify MainWindow since a new arc was added. setModStatus(ModStatus::EdgeCount, signalMW); // Return true: a new arc was genuinely added to the graph. return true; } // v1->v2 exists and either: // a) the request is not Undirected, or // b) v2->v1 also already exists (pair is already complete). // Nothing to do. return false; } // // v1->v2 does not exist yet. Proceed with normal edge creation. // if (type == EdgeType::Undirected) { // Undirected edge: edgeAdd stores BOTH v1->v2 and v2->v1 internally. // The UI draws a single undirected edge between the two nodes. edgeAdd(v1, v2, weight, type, label, ((weight == 0) ? initEdgeColorZero : color)); emit signalDrawEdge(v1, v2, weight, label, ((weight == 0) ? initEdgeColorZero : color), type, drawArrows, bezier, initEdgeWeightNumbers); } else if (edgeExists(v2, v1)) { // v1->v2 does not exist, but v2->v1 already exists. // Adding v1->v2 now makes the relationship reciprocal (bidirectional). // Upgrade the edge type to Reciprocated so both the data model and // the UI reflect the bidirectional relationship. edgeAdd(v1, v2, weight, EdgeType::Reciprocated, label, color); emit signalDrawEdge(v1, v2, weight, label, color, EdgeType::Reciprocated, drawArrows, bezier, initEdgeWeightNumbers); m_graphIsDirected = true; } else { // Neither v1->v2 nor v2->v1 exists. // Create a plain directed arc from v1 to v2. edgeAdd(v1, v2, weight, EdgeType::Directed, label, ((weight == 0) ? initEdgeColorZero : color)); emit signalDrawEdge(v1, v2, weight, label, ((weight == 0) ? initEdgeColorZero : color), EdgeType::Directed, drawArrows, bezier, initEdgeWeightNumbers); m_graphIsDirected = true; m_graphIsSymmetric = false; } // Persist the color of the last created edge so that new edges drawn // interactively by the user on the canvas inherit the same color // as the edges loaded from the file. initEdgeColor = color; if (!edgeCustomAttributes.isEmpty()) edgeCustomAttributesSet(v1, v2, edgeCustomAttributes); // Update edge count and notify MainWindow if needed. setModStatus(ModStatus::EdgeCount, signalMW); return true; } /** * @brief Called from WebCrawler when it finds an new link * Calls edgeCreate() method with initEdgeColor * @param source * @param target */ void Graph::edgeCreateWebCrawler(const int &source, const int &target) { qreal weight = 1.0; bool drawArrows = true; bool bezier = false; edgeCreate(source, target, weight, initEdgeColor, EdgeType::Directed, drawArrows, bezier); } /** * @brief Adds a directed arc from v1 to v2 into the internal graph data structures. * * This is the low-level storage function. It does NOT signal the UI. * All UI signaling is handled by the caller (edgeCreate / signalDrawEdge). * * Internally, each vertex maintains two adjacency lists: * - OutEdges: arcs going OUT from this vertex * - InEdges: arcs coming IN to this vertex * * For a directed arc v1->v2: * - v1's OutEdges gains v2 (with weight, color, label) * - v2's InEdges gains v1 (with weight) * * For an undirected edge (EdgeType::Undirected), the edge is stored as * two symmetric directed arcs: * - v1->v2 (OutEdge of v1, InEdge of v2) * - v2->v1 (OutEdge of v2, InEdge of v1) * This matches SocNetV's internal convention: undirected edges are always * represented as a pair of reciprocal directed arcs in the adjacency structure. * * For EdgeType::Reciprocated, the reverse arc v2->v1 already exists in the * data model (created earlier as a directed arc). No additional storage is * needed here; the caller is responsible for updating type semantics if required. * * Weight handling: * If weight != 1 and weight != 0, the graph is marked as weighted. * Weight == 0 is treated as a visual-only "null" edge drawn in initEdgeColorZero (#30). * * @param v1 Source node number (external node number, not internal index) * @param v2 Target node number (external node number, not internal index) * @param weight Edge weight * @param type EdgeType::Directed, Undirected, or Reciprocated * @param label Edge label (optional, stored on the out-arc of v1) * @param color Edge color (optional, stored on the out-arc of v1) */ void Graph::edgeAdd(const int &v1, const int &v2, const qreal &weight, const int &type, const QString &label, const QString &color) { // Resolve external node numbers to internal m_graph indices. // vpos[] maps external node number -> index in m_graph vector. int source = vpos[v1]; int target = vpos[v2]; // Store the forward arc v1->v2. // OutEdge on v1: carries weight, color, and label (full arc metadata). // InEdge on v2: carries weight only (no label/color needed on the receiving end). m_graph[source]->addOutEdge(v2, weight, color, label); m_graph[target]->addInEdge(v1, weight); // If weight is non-trivial (neither the default 1 nor the null-edge 0), // mark the graph as weighted so algorithms use weight values. if (weight != 1 && weight != 0) { setWeighted(true); } if (type == EdgeType::Reciprocated) { // The reverse arc v2->v1 already exists in the data model // (it was created in a prior edgeAdd call as a directed arc). // Nothing additional needs to be stored here. // The caller (edgeCreate) is responsible for any type-upgrade // semantics at the GraphVertex level if needed in the future. } else if (type == EdgeType::Undirected) { // Undirected edge: store the reverse arc v2->v1 as well, // so that both vertices see each other in their adjacency lists. // SocNetV represents undirected edges as two symmetric directed arcs. // // Note: only weight is stored on the reverse arc. // Color and label are not duplicated on the reverse direction. m_graph[target]->addOutEdge(v1, weight); m_graph[source]->addInEdge(v2, weight); } } /** * @brief Toggles the status of outbound edge source -> target at source vertex * @param v1 * @param v2 * @param toggle * @return */ void Graph::edgeOutboundStatusSet(const int &source, const int &target, const bool &toggle) { m_graph[vpos[source]]->setOutEdgeEnabled(target, toggle); } /** * @brief Toggles the status of inbound edge target <- source at target vertex * @param v1 * @param v2 * @param toggle * @return */ void Graph::edgeInboundStatusSet(const int &target, const int &source, const bool &toggle) { m_graph[vpos[target]]->setInEdgeEnabled(source, toggle); } /** * @brief Plain relay to signalSetEdgeVisibility, for GraphVertex (a QtCore-only value class, * not a QObject) to notify the UI layer of a single edge's visibility change. See #WS3 M2. */ void Graph::notifyEdgeVisibilityChanged(const int &relation, const int &source, const int &target, const bool &toggle, const bool &preserveReverseEdge, const int &edgeWeight, const int &reverseEdgeWeight) { emit signalSetEdgeVisibility(relation, source, target, toggle, preserveReverseEdge, edgeWeight, reverseEdgeWeight); } /** * @brief Plain relay to signalSetEdgesVisibilityBatch, for GraphVertex to notify the UI layer * of many edges' visibility changes as a single queued dispatch. See #WS3 M2. */ void Graph::notifyEdgesVisibilityBatch(const QList &changes) { if (changes.isEmpty()) return; emit signalSetEdgesVisibilityBatch(changes); } /** * @brief Removes the directed arc v1->v2 or, if the graph is undirected, the edge v1 <->v2 * * Emits signal to GW to delete the graphics item. * * @param v1 * @param v2 * @param removeReverse if true also removes the reverse edge */ void Graph::edgeRemove(const int &v1, const int &v2, const bool &removeReverse) { qCDebug(lcStorage) << "Graph::edgeRemove() - edge" << v1 << "[" << vpos[v1] << "] -->" << v2 << " to be removed. removeReverse:" << removeReverse; m_graph[vpos[v1]]->removeOutEdge(v2); m_graph[vpos[v2]]->removeInEdge(v1); if (isUndirected() || removeReverse) { // remove reverse edge too m_graph[vpos[v2]]->removeOutEdge(v1); m_graph[vpos[v1]]->removeInEdge(v2); m_graphIsSymmetric = true; } else { if (edgeExists(v2, v1) != 0) { m_graphIsSymmetric = false; } } // Undirected graphs always remove both directions (handled above); for directed graphs, // only remove the reverse arc when the caller explicitly asked for it. Using isDirected() // here always evaluated true for the (much more common) directed case, forcing the GW to // always delete the whole reciprocated edge instead of respecting removeReverse. See #251. emit signalRemoveEdge(v1, v2, (isUndirected() || removeReverse)); setModStatus(ModStatus::EdgeCount); } /** * @brief Removes a SelectedEdge * @param selectedEdge * @param removeReverse */ void Graph::edgeRemoveSelected(SelectedEdge &selectedEdge, const bool &removeReverse) { qCDebug(lcStorage) << "Graph::edgeRemoveSelected()" << selectedEdge; edgeRemove(selectedEdge.first, selectedEdge.second, removeReverse); } /** * @brief Removes all selected edges */ void Graph::edgeRemoveSelectedAll() { qCDebug(lcStorage) << "Graph::edgeRemoveSelectedAll()"; foreach (SelectedEdge edgeToRemove, getSelectedEdges()) { qCDebug(lcStorage) << "Graph::edgeRemoveSelectedAll() - About to remove" << edgeToRemove; edgeRemoveSelected(edgeToRemove, true); } } // // Existence / symmetry / counts // /** * @brief Checks if there is an edge from v1 to v2 and returns the weight, if the edge exists. * * Complexity: O(logN) for vpos retrieval + O(1) for QList index retrieval + O(logN) for checking edge(v2) * * @param v1 * @param v2 * @param reciprocated: if true, checks if the edge is reciprocated (v1<->v2) with the same weight * @return zero if edge or reciprocated edge does not exist or non-zero if arc /reciprocated edge exists */ qreal Graph::edgeExists(const int &v1, const int &v2, const bool &checkReciprocal) { edgeWeightTemp = m_graph[vpos[v1]]->hasEdgeTo(v2); if (!checkReciprocal) { return edgeWeightTemp; } else if (edgeWeightTemp != 0) { edgeReverseWeightTemp = m_graph[vpos[v2]]->hasEdgeTo(v1); if (edgeWeightTemp == edgeReverseWeightTemp) { return edgeWeightTemp; } } return 0; } /** * @brief Checks if there is an edge from v1 to v2, even weight = 0 and returns the weight, if the edge exists * or RAND_MAX if the edge does not exist at all. * * This is only used in GraphML saving if the user has selected the Settings option to save zero-weight edges * * @see https://github.com/socnetv/app/issues/151 * * @param v1 * @param v2 */ qreal Graph::edgeExistsVirtual(const int &v1, const int &v2) { qreal m_weight = RAND_MAX; bool edgeStatus = false; H_edges::const_iterator it1; GraphVertex *source = m_graph[vpos[v1]]; H_edges source_outEdges = source->m_outEdges; it1 = source_outEdges.constFind(v2); while (it1 != source_outEdges.constEnd() && it1.key() == v2) { if (it1.value().first == m_curRelation) { edgeStatus = it1.value().second.second; if (edgeStatus == true) { m_weight = it1.value().second.first; } } ++it1; } return m_weight; } /** * @brief Returns TRUE if edge(v1, v2) is symmetric, i.e. (v1,v2) == (v2,v1). * @param v1 * @param v2 * @return */ bool Graph::edgeSymmetric(const int &v1, const int &v2) { if ((edgeExists(v1, v2, true)) != 0) { qCDebug(lcStorage) << "Edge" << v1 << "->" << v2 << "is symmetric"; return true; } else { qCDebug(lcStorage) << "Edge" << v1 << "->" << v2 << "is not symmetric"; return false; } } /** * @brief Returns the number of enabled ties in the current relation. * * IMPORTANT: Naming vs semantics * - Internally, SocNetV stores adjacency as directed arcs in each vertex's out-edges. * - For an UNDIRECTED graph, each undirected edge is represented as TWO symmetric arcs * (v1->v2 and v2->v1). Therefore, summing outEdgesCount() over all vertices yields * 2*E, and we must divide by 2 to return the logical undirected edge count E. * - For a DIRECTED graph, summing outEdgesCount() over all vertices yields A (the arc * count), and we return it as-is. * * Caching: * - m_totalEdges caches the *internal* count (sum of enabled out-arcs). * - edgesEnabled() returns the *logical* count: * - E for undirected graphs * - A for directed graphs * * TODO / THINK: Self-loops (v->v) * - A self-loop contributes exactly 1 outbound arc in outEdgesCount(). * - In an UNDIRECTED graph, dividing m_totalEdges by 2 assumes every tie is a symmetric pair. * A loop is NOT a symmetric pair, so it would be mishandled by the /2 rule. * - Decide on a loop policy: * (a) forbid loops in undirected graphs (and filter them out here), or * (b) count loops separately and adjust the formula to: E = (nonLoopArcs/2) + loopArcs * (where loopArcs is the number of enabled v->v arcs). * * @return int Logical enabled ties: E (undirected) or A (directed) */ int Graph::edgesEnabled() { int enabledEdges = 0; if (calculatedEdges) { enabledEdges = (isUndirected()) ? (m_totalEdges / 2) : m_totalEdges; return enabledEdges; } // Compute internal tie count from scratch: sum enabled outbound arcs for current relation. m_totalEdges = 0; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { m_totalEdges += (*it)->outEdgesCount(); } calculatedEdges = true; // Convert internal arc count to logical tie count. enabledEdges = (isUndirected()) ? (m_totalEdges / 2) : m_totalEdges; return enabledEdges; } /** * @brief Returns the number of outbound edges (arcs) from vertex v1 * @param v1 * @return */ int Graph::vertexEdgesOutbound(int v1) { qCDebug(lcStorage, "Graph: vertexEdgesOutbound()"); return m_graph[vpos[v1]]->outEdgesCount(); } /** * @brief Returns the number of inbound edges (arcs) to vertex v1 * @param v1 * @return int */ int Graph::vertexEdgesInbound(int v1) { qCDebug(lcStorage, "Graph: vertexEdgesInbound()"); return m_graph[vpos[v1]]->inEdgesCount(); } // // Weight core // /** * @brief Changes the weight of the edge from vertex v1 to v2 (and optionally of the reverse edge) * * @param v1 * @param v2 * @param weight * @param undirected */ void Graph::edgeWeightSet(const int &v1, const int &v2, const qreal &weight, const bool &undirected) { qCDebug(lcStorage) << "Changing the weight of edge" << v1 << "[" << vpos[v1] << "]->" << v2 << "[" << vpos[v2] << "]" << " to new weight " << weight; m_graph[vpos[v1]]->setOutEdgeWeight(v2, weight); if (undirected) { qCDebug(lcStorage) << "Changing the weight of the reverse edge too"; m_graph[vpos[v2]]->setOutEdgeWeight(v1, weight); } emit setEdgeWeight(v1, v2, weight); setModStatus(ModStatus::EdgeCount); } /** * @brief Returns the weight of the edge v1->v2 * @param v1 * @param v2 * @return qreal */ qreal Graph::edgeWeight(const int &v1, const int &v2) const { return m_graph[vpos[v1]]->hasEdgeTo(v2); } /** * @brief Changes the direction type of an existing edge * * @param v1 * @param v2 * @param weight */ void Graph::edgeTypeSet(const int &v1, const int &v2, const qreal &weight, const int &dirType) { qCDebug(lcStorage) << "Changing the direction type of edge: " << v1 << "->" << v2 << "new edgeType:" << dirType; if (dirType != EdgeType::Directed) { // check if reverse edge exists qreal revEdgeWeight = edgeExists(v2, v1); if (revEdgeWeight == 0) { // Reverse edge does not exist, add it qCDebug(lcStorage) << "reverse edge" << v1 << " <- " << v2 << " does not exist - Adding it..."; // Note: Even if dirType=EdgeType::Undirected we add the opposite edge as EdgeType::Reciprocated edgeAdd(v2, v1, weight, EdgeType::Reciprocated, "", initEdgeColor); } else { // Reverse edge does exist if (dirType == EdgeType::Undirected) { // Make the edge weights equal // TOFIX: how do we decide which of the two weights to keep? qCDebug(lcStorage) << "Graph::edgeTypeSet(): opposite " << v1 << " <- " << v2 << " exists - equaling weights."; if (weight != revEdgeWeight) { edgeWeightSet(v2, v1, weight); } } else { // if dirType is EdgeType::Reciprocated we don't need to equalize weights } } emit signalEdgeType(v1, v2, dirType); } } SocNetV-3.7/src/graph/storage/graph_subgraphs.cpp000066400000000000000000000176751524402210300221340ustar00rootroot00000000000000/** * @file graph_subgraphs.cpp * @brief Implements subgraph extraction for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include "graphvertex.h" #include #include /** * @brief Core helper: builds an independent Graph copy from an explicit list * of vertex numbers. * * Shared by subgraphExtract() (visible nodes) and subgraphExtractFromSelection() * (selected nodes). Vertices are renumbered from 1. For every relation, enabled * edges whose both endpoints are in @p vertexNums are copied. All visual * properties and, when @p includeCustomAttributes is true, node and edge custom * attribute maps are preserved verbatim. * * The caller takes ownership of the returned Graph object. * * @param vertexNums Source vertex numbers to include. * @param name Name assigned to the new graph. * @param includeCustomAttributes When true, node and edge custom * attribute maps are copied verbatim. * @return Heap-allocated Graph* containing the subgraph, or nullptr * if @p vertexNums is empty. */ Graph *Graph::subgraphFromVertexList(const QList &vertexNums, const QString &name, const bool &includeCustomAttributes) { if (vertexNums.isEmpty()) { qCDebug(lcStorage) << "subgraphFromVertexList(): vertex list is empty β€” returning nullptr"; return nullptr; } // Build old-number β†’ new-number map (renumber from 1) QHash oldToNew; int newNum = 1; for (int oldNum : vertexNums) oldToNew[oldNum] = newNum++; // --- Create the new graph ------------------------------------------- // No UI signals: signalMW is false throughout; the new graph has no // GraphicsWidget attached, so emitting draw signals would be a no-op // anyway, but passing false is explicit and safe. Graph *sub = new Graph(vertexNums.size(), m_reserveEdgesPerVertexSize); sub->setName(name); sub->setDirected(isDirected(), false); sub->setWeighted(isWeighted()); // Propagate canvas dimensions so that IO routines normalize pixel // coordinates correctly on export (default is 700x600 which is // smaller than the actual canvas and produces out-of-range values). sub->canvasSizeSetQuiet(canvasWidth, canvasHeight); // Mirror every relation that exists in the source graph. // relationAdd() with changeRelation=false keeps the current relation at 0. const int relCount = m_relationsList.size(); for (int r = 0; r < relCount; ++r) sub->relationAdd(m_relationsList.at(r), false); // Activate relation 0 in the subgraph (matches the default after construction). sub->relationSet(0, false); // --- Pass 1: create vertices ----------------------------------------- for (int oldNum : vertexNums) { GraphVertex *v = vertexPtr(oldNum); sub->vertexCreate( oldToNew[oldNum], v->size(), v->color(), v->numberColor(), v->numberSize(), v->label(), v->labelColor(), v->labelSize(), v->pos(), v->shape(), v->shapeIconPath(), false, // signalMW includeCustomAttributes ? v->customAttributes() : QHash()); } // --- Pass 2: create edges -------------------------------------------- // Iterate every source vertex; for each out-edge whose enabled flag is // true and whose target is also in the vertex list, add it to the subgraph. // We handle each relation index independently so multi-relation networks // are preserved correctly. for (int oldSrc : vertexNums) { GraphVertex *sv = vertexPtr(oldSrc); const H_edges &oe = sv->outEdges(); for (auto ei = oe.constBegin(); ei != oe.constEnd(); ++ei) { const int oldTgt = ei.key(); const int relIdx = ei.value().first; const qreal weight = ei.value().second.first; const bool edgeEnabled = ei.value().second.second; if (!edgeEnabled) continue; if (!oldToNew.contains(oldTgt)) continue; // target is not in the vertex list const int newSrc = oldToNew[oldSrc]; const int newTgt = oldToNew[oldTgt]; // Temporarily switch subgraph to the right relation so edgeCreate // stores the edge under the correct relation index. sub->relationSet(relIdx, false); sub->edgeCreate( newSrc, newTgt, weight, sv->outLinkColor(oldTgt), EdgeType::Directed, // always directed internally; Graph::isDirected() // controls the logical interpretation true, // drawArrows false, // bezier sv->outEdgeLabel(oldTgt), false, // signalMW includeCustomAttributes ? sv->outEdgeCustomAttributes(oldTgt) : QHash()); } } // Restore subgraph to relation 0 before handing it back. sub->relationSet(0, false); qCDebug(lcStorage) << "subgraphFromVertexList(): created subgraph" << name << "β€” vertices:" << sub->vertices() << "edges:" << sub->edgesEnabled(); return sub; } /** * @brief Extracts currently visible (non-filtered) nodes and their * inter-edges into an independent Graph copy. * * Collects all enabled vertices, then delegates to subgraphFromVertexList(). * * @param name Name assigned to the new graph. * @param includeCustomAttributes When true, node and edge custom * attribute maps are copied verbatim. * @return Heap-allocated Graph* containing the subgraph, or nullptr * if no visible vertices exist. */ Graph *Graph::subgraphExtract(const QString &name, const bool &includeCustomAttributes) { // --- Collect visible vertices ---------------------------------------- QList visibleNums; for (VList::const_iterator vi = verticesBegin(); vi != verticesEnd(); ++vi) { if ((*vi)->isEnabled()) visibleNums.append((*vi)->number()); } if (visibleNums.isEmpty()) qCDebug(lcStorage) << "subgraphExtract(): no visible vertices"; return subgraphFromVertexList(visibleNums, name, includeCustomAttributes); } /** * @brief Extracts currently selected nodes and their inter-edges into an * independent Graph copy. * * Uses the current canvas selection (getSelectedVertices()), then delegates * to subgraphFromVertexList(). Only edges between selected nodes are included. * * @param name Name assigned to the new graph. * @param includeCustomAttributes When true, node and edge custom * attribute maps are copied verbatim. * @return Heap-allocated Graph* containing the subgraph, or nullptr * if no nodes are selected. */ Graph *Graph::subgraphExtractFromSelection(const QString &name, const bool &includeCustomAttributes) { const QList selected = getSelectedVertices(); if (selected.isEmpty()) qCDebug(lcStorage) << "subgraphExtractFromSelection(): no selected vertices"; return subgraphFromVertexList(selected, name, includeCustomAttributes); } SocNetV-3.7/src/graph/storage/graph_vertices.cpp000066400000000000000000001044331524402210300217470ustar00rootroot00000000000000/** * @file graph_vertices.cpp * @brief Implements vertex storage and CRUD operations for the Graph class * (create/remove/find/existence, iterators, vertex positions, isolation). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" // // Vertex CRUD operations // /** * @brief Creates a new vertex * * Main vertex creation slot, associated with homonymous signal from Parser. * Adds a vertex to the Graph and signals drawNode to GW * The new vertex has number num and specific color, label, label color, shape and position p. * * @param num * @param size * @param nodeColor * @param numColor * @param numSize * @param label * @param lColor * @param lSize * @param p * @param nodeShape * @param signalMW */ void Graph::vertexCreate(const int &number, const int &size, const QString &color, const QString &numColor, const int &numSize, const QString &label, const QString &labelColor, const int &labelSize, const QPointF &p, const QString &shape, const QString &iconPath, const bool &signalMW, const QHash &customAttributes) { qCDebug(lcStorage) << "Creating a new vertex:" << number << "shape:" << shape << "icon:" << iconPath << "signalMW:" << signalMW << "- Appending the new vertex and signaling to GW to create the node"; if (order) vpos[number] = m_totalVertices; else vpos[number] = m_graph.size(); m_graph.append( new GraphVertex( this, number, m_curRelation, size, color, numColor, numSize, label, labelColor, labelSize, p, shape, iconPath, m_reserveEdgesPerVertexSize, customAttributes)); m_totalVertices++; emit signalDrawNode(p, number, size, shape, iconPath, color, numColor, numSize, initVertexNumberDistance, label, labelColor, labelSize, initVertexLabelDistance); qCDebug(lcStorage) << "Finished creating new vertex:" << number << "Setting graph mod status"; setModStatus(ModStatus::VertexCount, signalMW); // to draw new vertices by user with the same style of the file loaded: // save color, size and shape as init values initVertexColor = color; initVertexSize = size; initVertexShape = shape; if (shape == "custom") { initVertexIconPath = iconPath; } } /** * @brief Creates a new vertex in the given position * * Called from GW, with i and p as parameters. * Calls the main creation slot with init node values. * * @param QPointF The clicked pos of the new node. */ void Graph::vertexCreateAtPos(const QPointF &p) { int i = vertexNumberMax() + 1; qCDebug(lcStorage) << "Creating a new vertex:" << i << " in given position:" << p; vertexCreate(i, initVertexSize, initVertexColor, initVertexNumberColor, initVertexNumberSize, QString(), initVertexLabelColor, initVertexLabelSize, p, initVertexShape, initVertexIconPath, true); progressStatus(tr("New node (numbered %1) added at position (%2,%3). Double-click on it to start a new edge from it.") .arg(vertexNumberMax()) .arg(p.x()) .arg(p.y())); } /** * @brief Creates a new randomly positioned vertex with default values * * Computes a random position p inside the useable canvas area * Then calls the main creation slot with init node values. * * @param bool * */ void Graph::vertexCreateAtPosRandom(const bool &signalMW) { QPointF p; p.setX(canvasRandomX()); p.setY(canvasRandomY()); qCDebug(lcStorage) << "Creating a new random positioned vertex at:" << p; vertexCreate(vertexNumberMax() + 1, initVertexSize, initVertexColor, initVertexNumberColor, initVertexNumberSize, QString(), initVertexLabelColor, initVertexLabelSize, p, initVertexShape, initVertexIconPath, signalMW); } /** * @brief Creates a new randomly positioned vertex with specific number and label. * All other values are from the defaults. * * Called from WebCrawler and Parser with parameters label and i. * Computes a random position p the useable canvas area * Then calls the main creation slot with init node values. * * @param i * @param label * @param signalMW */ void Graph::vertexCreateAtPosRandomWithLabel(const int &i, const QString &label, const bool &signalMW) { qCDebug(lcStorage) << "Creates a new randomly positioned vertex:" << i << "with label:" << label; QPointF p; p.setX(canvasRandomX()); p.setY(canvasRandomY()); vertexCreate((i < 0) ? vertexNumberMax() + 1 : i, initVertexSize, initVertexColor, initVertexNumberColor, initVertexNumberSize, label, initVertexLabelColor, initVertexLabelSize, p, initVertexShape, initVertexIconPath, signalMW); } /** * @brief Removes the vertex v1 from the graph * First, it removes all edges to doomed from other vertices * Then it changes the vpos of all subsequent vertices inside m_graph * Finally, it removes the vertex. * @param int v1 */ void Graph::vertexRemove(const int &v1) { qCDebug(lcStorage) << "Removing vertex:" << m_graph[vpos[v1]]->number() << "vpos:" << vpos[v1] << "Removing all inbound and outbound edges "; int doomedPos = vpos[v1]; // Remove links to v1 from each other vertex VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (qAbs((*it)->hasEdgeTo(v1)) > 0) { qCDebug(lcStorage) << "another vertex" << (*it)->number() << " has outbound Edge to " << v1 << ". Removing it."; (*it)->removeOutEdge(v1); } if (qAbs((*it)->hasEdgeFrom(v1)) > 0) { qCDebug(lcStorage) << "another vertex" << (*it)->number() << " has inbound Edge from " << v1 << ". Removing it."; (*it)->removeInEdge(v1); } } qCDebug(lcStorage) << "Finished with vertices. " "Update the vpos which maps vertices inside m_graph "; int prevIndex = doomedPos; qCDebug(lcStorage) << "Updating vpos of all subsequent vertices "; H_Int::const_iterator it1 = vpos.cbegin(); while (it1 != vpos.cend()) { if (it1.value() > doomedPos) { prevIndex = it1.value(); qCDebug(lcStorage) << "vertex" << it1.key() << "had prevIndex:" << prevIndex << " > doomedPos" << doomedPos << "Setting new vpos. vpos size was: " << vpos.size(); vpos.insert(it1.key(), --prevIndex); qCDebug(lcStorage) << "vertex" << it1.key() << "new vpos:" << vpos.value(it1.key(), -666) << "vpos size now:" << vpos.size(); } else { qCDebug(lcStorage) << "vertex" << it1.key() << "with vpos" << it1.value() << " =< doomedPos. CONTINUE"; } ++it1; } // Now remove vertex Doomed from m_graph qCDebug(lcStorage) << "graph vertices=size=" << vertices() << "=" << m_graph.size() << "removing vertex at vpos " << doomedPos; m_graph.removeAt(doomedPos); m_totalVertices--; qCDebug(lcStorage) << "Now graph vertices=size=" << vertices() << "=" << m_graph.size(); order = false; // Check if this was the clicked vertex and unset it if (vertexClicked() == v1) { vertexClickedSet(0, QPointF(0, 0)); } setModStatus(ModStatus::VertexCount); emit signalRemoveNode(v1); } /** * @brief Deletes a dummy node * * This is called from Parser (as pajek) to delete any redundant (dummy) nodes. * * @param int i number of node */ void Graph::vertexRemoveDummyNode(int i) { qCDebug(lcStorage) << "Removing dummy node from graph: " << i; vertexRemove(i); } // // Vertex access and retrieval // /** * @brief Returns the index of a vertex by its number * * Returns the vpos or -1 * * Complexity: O(logN) for vpos retrieval * * @param vertex number * @return vertex pos or -1 */ int Graph::vertexIndexByNumber(int v) const { return vpos.value(v, -1); } /** * @brief Returns the vertex at a given index * @param idx * @return GraphVertex* */ GraphVertex *Graph::vertexAtIndex(int idx) { return m_graph[idx]; } /** * @brief Returns the vertex at a given index * @param idx * @return GraphVertex* */ const GraphVertex *Graph::vertexAtIndex(int idx) const { return m_graph[idx]; } // // Vertex iterators and positions // /** * @brief iterator helpers */ VList::const_iterator Graph::verticesBegin() const { return m_graph.cbegin(); } VList::const_iterator Graph::verticesEnd() const { return m_graph.cend(); } /** * @brief Returns a list of all isolated vertices inside the graph * * @return QList */ QList Graph::verticesListIsolated() { if (calculatedIsolates) { qCDebug(lcStorage) << "Graph::verticesListIsolated() - graph not modified and " "already calculated isolates. Returning list as is:" << m_verticesIsolatedList; return m_verticesIsolatedList; } VList::const_iterator it; m_verticesIsolatedList.clear(); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { // if ( ! (*it)->isEnabled() ) // continue; if ((*it)->isIsolated()) { m_verticesIsolatedList << (*it)->number(); qCDebug(lcStorage) << "Graph::verticesListIsolated() - node " << (*it)->number() << " is isolated. Marking it."; } } qCDebug(lcStorage) << "Graph::verticesListIsolated() - isolated vertices list:" << m_verticesIsolatedList; calculatedIsolates = true; return m_verticesIsolatedList; } /** * @brief Returns a list of all vertices numbers inside the graph * * @return QList */ QList Graph::verticesList() { qCDebug(lcStorage) << "Graph::verticesList()"; if (!m_verticesList.isEmpty() && calculatedVerticesList) { return m_verticesList; } VList::const_iterator it; m_verticesList.clear(); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) continue; m_verticesList << (*it)->number(); } calculatedVerticesList = true; return m_verticesList; } /** * @brief Returns a QSet of all vertices numbers inside the graph * @return */ QSet Graph::verticesSet() { qCDebug(lcStorage) << "Graph::verticesSet()"; if (!m_verticesSet.isEmpty() && calculatedVerticesSet) { return m_verticesSet; } VList::const_iterator it; m_verticesSet.clear(); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) continue; m_verticesSet << (*it)->number(); } calculatedVerticesSet = true; return m_verticesSet; } /** * @brief Creates a subgraph (clique, star, cycle, line) with vertices in vList * Iff vList is empty, then fallbacks to the m_verticesSelected. * @param vList */ void Graph::verticesCreateSubgraph(QList vList, const int &type, const int ¢er) { if (relations() == 1 && edgesEnabled() == 0) { QString newRelationName = QString::number(vList.size()) + tr("-clique"); relationCurrentRename(newRelationName, true); } if (vList.isEmpty()) { vList = m_verticesSelected; } qCDebug(lcStorage) << "Graph::verticesCreateSubgraph() - type:" << type << "vList:" << vList; QString pMsg = tr("Creating subgraph. \nPlease wait..."); progressStatus(pMsg); qreal weight; bool drawArrows = isDirected(); int edgeType = (isUndirected()) ? EdgeType::Undirected : EdgeType::Reciprocated; if (type == SUBGRAPH_CLIQUE) { for (int i = 0; i < vList.size(); ++i) { if (progressCanceled()) { return; } for (int j = i + 1; j < vList.size(); ++j) { if (!(weight = edgeExists(vList.value(i), vList.value(j)))) { if ((weight = edgeExists(vList.value(j), vList.value(i)))) { edgeTypeSet(vList.value(j), vList.value(i), weight, edgeType); } else { edgeCreate(vList.value(i), vList.value(j), 1.0, initEdgeColor, EdgeType::Undirected, drawArrows); edgeTypeSet(vList.value(i), vList.value(j), weight, edgeType); } } else { edgeTypeSet(vList.value(i), vList.value(j), weight, edgeType); } } } } else if (type == SUBGRAPH_STAR) { for (int j = 0; j < vList.size(); ++j) { if (progressCanceled()) { return; } if (!(weight = edgeExists(center, vList.value(j)))) { if (center == vList.value(j)) continue; if ((weight = edgeExists(vList.value(j), center))) { edgeTypeSet(vList.value(j), center, weight, edgeType); } else { edgeCreate(center, vList.value(j), 1.0, initEdgeColor, EdgeType::Undirected, drawArrows); edgeTypeSet(center, vList.value(j), weight, edgeType); } } else { edgeTypeSet(center, vList.value(j), weight, edgeType); } } } else if (type == SUBGRAPH_CYCLE) { int j = 0; for (int i = 0; i < vList.size(); ++i) { if (progressCanceled()) { return; } j = (i == vList.size() - 1) ? 0 : i + 1; if (!(weight = edgeExists(vList.value(i), vList.value(j)))) { if ((weight = edgeExists(vList.value(j), vList.value(i)))) { edgeTypeSet(vList.value(j), vList.value(i), weight, edgeType); } else { edgeCreate(vList.value(i), vList.value(j), 1.0, initEdgeColor, EdgeType::Undirected, drawArrows); edgeTypeSet(vList.value(i), vList.value(j), weight, edgeType); } } else { edgeTypeSet(vList.value(i), vList.value(j), weight, edgeType); } } } else if (type == SUBGRAPH_LINE) { int j = 0; for (int i = 0; i < vList.size(); ++i) { if (progressCanceled()) { return; } if (i == vList.size() - 1) break; j = i + 1; if (!(weight = edgeExists(vList.value(i), vList.value(j)))) { if ((weight = edgeExists(vList.value(j), vList.value(i)))) { edgeTypeSet(vList.value(j), vList.value(i), weight, edgeType); } else { edgeCreate(vList.value(i), vList.value(j), 1.0, initEdgeColor, EdgeType::Undirected, drawArrows); edgeTypeSet(vList.value(i), vList.value(j), weight, edgeType); } } else { edgeTypeSet(vList.value(i), vList.value(j), weight, edgeType); } } } else { return; } } // // Vertex numbers // /** * @brief Returns the number of the last vertex in the graph. * * @return int */ int Graph::vertexNumberMax() { if (m_totalVertices > 0) return m_graph.back()->number(); else return 0; } /** * @brief Returns the number of the first vertex in the graph. * * @return int */ int Graph::vertexNumberMin() { if (m_totalVertices > 0) return m_graph.front()->number(); else return 0; } // // Vertex existence and find operations // /** * @brief Gets the number of vertices in the graph * * If countAll = true, returns |V| where V the set of all (enabled or not) vertices * If countAll = false, it skips disabled vertices * If countAll = false and dropIsolates = true, it skips both disabled and isolated vertices * * The cache is keyed on (dropIsolates, countAll): a cached count computed under one combination * of flags must never be handed back to a caller asking with a different combination, since * callers size fixed allocations off this return value (see #274). * * @param dropIsolates * @param countAll * @return */ int Graph::vertices(const bool &dropIsolates, const bool &countAll, const bool &recount) { if (m_totalVertices != 0 && calculatedVertices && !recount && m_verticesCacheDropIsolates == dropIsolates && m_verticesCacheCountAll == countAll) { qCDebug(lcStorage) << "Graph not modified, returning static number: " << m_totalVertices; return m_totalVertices; } m_totalVertices = 0; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (countAll) { ++m_totalVertices; } else { if (dropIsolates && (*it)->isIsolated()) { qCDebug(lcStorage) << "Skipping isolated vertex:" << (*it)->number(); continue; } if (!(*it)->isEnabled()) { qCDebug(lcStorage) << "Skipping disabled vertex:" << (*it)->number(); continue; } ++m_totalVertices; } } qCDebug(lcStorage) << "Graph size:" << m_graph.size() << "vertices" << m_totalVertices; calculatedVertices = true; m_verticesCacheDropIsolates = dropIsolates; m_verticesCacheCountAll = countAll; return m_totalVertices; } /** * @brief Returns true if the current graph has no vertices at all */ bool Graph::isEmpty() const { return m_graph.isEmpty(); } /** * @brief Checks if the given vertex exists in the graph. * * @param vertex number * @return true if it exists */ bool Graph::vertexExists(const int &v1) { return vertexIndexIfExists(v1) != -1; } /** * @brief Returns the internal position of the given vertex in m_graph, if it exists. * * Complexity: O(logN) for vpos retrieval * * @param vertex number * @return vertex pos or -1 */ int Graph::vertexIndexIfExists(const int &v1) { if (vpos.contains(v1)) { if (m_graph[vpos[v1]]->number() == v1) { return vpos[v1]; } else { qCritical() << "Error in vpos for vertex number v:" << v1; } } return -1; } /** * @brief Checks if there is a vertex with a specific label in the graph * * @param label * @return true if it exists */ bool Graph::vertexExists(const QString &label) { return vertexIndexIfExists(label) != -1; } /** * @brief Returns the internal position of the vertex with the given label, if it exists. * * Complexity: O(N) * * @param label * @return vertex pos or -1 */ int Graph::vertexIndexIfExists(const QString &label) { qCDebug(lcStorage) << "Checking if vertex exists, with label:" << label.toUtf8(); VList::const_iterator it; int i = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if ((*it)->label().contains(label, Qt::CaseInsensitive)) { return i; } i++; } return -1; } /** * @brief Finds vertices in strList by their number * @param QStringList * @return */ bool Graph::vertexFindByNumber(const QStringList &numList) { qCDebug(lcStorage) << "Finding vertices by number - searchList:" << numList; QString vStr; QList foundList; QStringList notFound; int v = -1; bool intOk = false; bool searchResult = false; for (int i = 0; i < numList.size(); ++i) { vStr = numList.at(i); v = vStr.toInt(&intOk); if (intOk) { if (vertexExists(v)) { qCDebug(lcStorage) << "vertex number" << v << "exists. Adding it to found list"; foundList << v; } else { qCDebug(lcStorage) << "vertex number" << v << "does not exist. Adding it to notFound list"; notFound << vStr; } } else { qCDebug(lcStorage) << "cannot read" << vStr; } } if (!foundList.isEmpty()) { searchResult = true; qCDebug(lcStorage) << "One or more matching nodes found. Signaling to GW to highlight them..."; progressStatus(tr("Found %1 matching nodes.").arg(foundList.size())); emit signalNodesFound(foundList); } else { qCDebug(lcStorage) << "No matching nodes found. Return."; progressStatus(tr("Could not find any nodes matching your choices.")); } return searchResult; } /** * @brief Finds vertices by their label * @param QStringList * @return */ bool Graph::vertexFindByLabel(const QStringList &labelList) { qCDebug(lcStorage) << "Finding vertices by label - searchList:" << labelList; QString vLabel; QList foundList; int vFoundPos = -1; QStringList notFound; bool searchResult = false; for (int i = 0; i < labelList.size(); ++i) { vLabel = labelList.at(i); if ((vFoundPos = vertexIndexIfExists(vLabel)) != -1) { qCDebug(lcStorage) << "vertex with label" << vLabel << "exists. Adding it to found list"; foundList << m_graph[vFoundPos]->number(); } else { qCDebug(lcStorage) << "vertex with label" << vLabel << "does not exist. Adding it to notFound list "; notFound << vLabel; } } if (!foundList.isEmpty()) { searchResult = true; qCDebug(lcStorage) << "One or more matchin nodes found. Signaling to GW to highlight them..."; progressStatus(tr("Found %1 matching nodes.").arg(foundList.size())); emit signalNodesFound(foundList); } else { qCDebug(lcStorage) << "No matching nodes found. Return."; progressStatus(tr("Could not find any nodes matching your choices.")); } return searchResult; } /** * @brief Finds vertices by their index score * @param QStringList * @return */ bool Graph::vertexFindByIndexScore(const int &index, const QStringList &thresholds, const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates) { qCDebug(lcStorage) << "Finding vertices by index" << index << "threshold list" << thresholds << "considerWeights" << considerWeights << "inverseWeights" << inverseWeights << "dropIsolates" << dropIsolates; QList foundList; bool searchResult = false; VList::const_iterator it; QString thresholdStr = ""; bool gtThan = false; bool gtEqual = false; bool lsThan = false; bool lsEqual = false; bool convertedOk = false; qreal threshold = 0; qreal score = 0; switch (index) { case 0: { // do nothing break; } case IndexType::DC: { centralityDegree(considerWeights, dropIsolates); break; } case IndexType::IRCC: { centralityClosenessIR(); break; } case IndexType::IC: { centralityInformation(considerWeights, inverseWeights); break; } case IndexType::EVC: { centralityEigenvector(considerWeights, inverseWeights, dropIsolates); break; } case IndexType::DP: { prestigeDegree(considerWeights, dropIsolates); break; } case IndexType::PRP: { prestigePageRank(dropIsolates); break; } case IndexType::PP: { prestigeProximity(considerWeights, inverseWeights); break; } case IndexType::CLC: { clusteringCoefficient(); break; } case IndexType::KATZ: { if (m_lastKatzAlpha < 0) { progressStatus(tr("Please compute Katz Centrality first " "(Analyze > Centrality > Katz Centrality), then try again.")); return false; } centralityKatz(m_lastKatzAlpha, considerWeights, inverseWeights, dropIsolates); break; } case IndexType::BPC: { if (m_lastBonacichAlpha < 0) { progressStatus(tr("Please compute Bonacich Power Centrality first " "(Analyze > Centrality > Bonacich Power Centrality), then try " "again.")); return false; } centralityBonacich(m_lastBonacichAlpha, m_lastBonacichBeta, considerWeights, inverseWeights, dropIsolates); break; } default: graphDistancesGeodesic(true, considerWeights, inverseWeights, dropIsolates); break; } // Parse threshold user input for (int i = 0; i < thresholds.size(); ++i) { thresholdStr = thresholds.at(i); thresholdStr = thresholdStr.simplified(); gtThan = false; gtEqual = false; lsThan = false; lsEqual = false; convertedOk = false; if (thresholdStr.startsWith(">=") || thresholdStr.startsWith("=>")) { gtEqual = true; thresholdStr.remove(">="); thresholdStr.remove("=>"); qCDebug(lcStorage) << "thresholdStr starts with >="; } else if (thresholdStr.startsWith(">")) { gtThan = true; thresholdStr.remove(">"); qCDebug(lcStorage) << "thresholdStr starts with > "; } else if (thresholdStr.startsWith("<=") || thresholdStr.startsWith("=<")) { lsEqual = true; thresholdStr.remove("<="); thresholdStr.remove("=<"); qCDebug(lcStorage) << "thresholdStr starts with <="; } else if (thresholdStr.startsWith("<")) { lsThan = true; thresholdStr.remove("<"); qCDebug(lcStorage) << "thresholdStr starts with < "; } else { qCDebug(lcStorage) << "thresholdStr does not start with > or <"; continue; } // Parse score threshold threshold = thresholdStr.toDouble(&convertedOk); if (!convertedOk) { qCDebug(lcStorage) << "cannot convert thresholdStr to float"; continue; } else { qCDebug(lcStorage) << "threshold" << threshold; } // Iterate over all vertices and get their scores for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { switch (index) { case 0: { score = 0; break; } case IndexType::DC: { score = (*it)->SDC(); break; } case IndexType::CC: { score = (*it)->SCC(); break; } case IndexType::IRCC: { score = (*it)->SIRCC(); break; } case IndexType::BC: { score = (*it)->SBC(); break; } case IndexType::SC: { score = (*it)->SSC(); break; } case IndexType::EC: { score = (*it)->SEC(); break; } case IndexType::PC: { score = (*it)->SPC(); break; } case IndexType::IC: { score = (*it)->SIC(); break; } case IndexType::EVC: { score = (*it)->SEVC(); break; } case IndexType::DP: { score = (*it)->SDP(); break; } case IndexType::PRP: { score = (*it)->SPRP(); break; } case IndexType::PP: { score = (*it)->SPP(); break; } case IndexType::CLC: { score = (*it)->CLC(); break; } case IndexType::KATZ: { score = (*it)->SKC(); break; } case IndexType::BPC: { score = (*it)->SBPC(); break; } } if (gtThan) { if (score > threshold) { qCDebug(lcStorage) << "matching vertex" << (*it)->number() << "score" << score; foundList << (*it)->number(); } } else if (gtEqual) { if (score >= threshold) { qCDebug(lcStorage) << "matching vertex" << (*it)->number() << "score" << score; foundList << (*it)->number(); } } else if (lsThan) { if (score < threshold) { qCDebug(lcStorage) << "matching vertex" << (*it)->number() << "score" << score; foundList << (*it)->number(); } } else if (lsEqual) { if (score <= threshold) { qCDebug(lcStorage) << "matching vertex" << (*it)->number() << "score" << score; foundList << (*it)->number(); } } } } if (!foundList.isEmpty()) { searchResult = true; qCDebug(lcStorage) << "One or more matching nodes found. Signaling to GW to highlight them..."; progressStatus(tr("Found %1 matching nodes.").arg(foundList.size())); emit signalNodesFound(foundList); } else { qCDebug(lcStorage) << "No matching nodes found. Return."; progressStatus(tr("Could not find any nodes matching your choices.")); } return searchResult; } // // Vertex isolation // /** * @brief Toggles the status of all isolated vertices (those without links) * * For each isolate vertex in the Graph, emits the setVertexVisibility signal * * @param toggle */ void Graph::vertexIsolatedAllToggle(const bool &toggle) { qCDebug(lcStorage) << "Setting all isolated vertices to" << toggle; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isIsolated()) { continue; } else { qCDebug(lcStorage) << "vertex" << (*it)->number() << "is isolated. Toggling it and emitting setVertexVisibility signal to GW..."; (*it)->setEnabled(toggle); setModStatus(ModStatus::VertexCount); emit setVertexVisibility((*it)->number(), toggle); } } } /** * @brief Checks if vertex is isolated * @param v1 * @return */ bool Graph::vertexIsolated(const int &v1) const { if (m_graph[vpos[v1]]->isIsolated()) { qCDebug(lcStorage) << "vertex:" << v1 << "is isolated"; return true; } qCDebug(lcStorage) << "vertex:" << v1 << "not isolated"; return false; } // // Vertex positions // /** * @brief Changes the position of the given vertex * * Called from MW/GW when node moves to update its position * * @param v1 * @param x * @param y */ void Graph::vertexPosSet(const int &v1, const int &x, const int &y) { m_graph[vpos[v1]]->setX(x); m_graph[vpos[v1]]->setY(y); setModStatus(ModStatus::VertexPositions, false); } /** * @brief Returns the position of the given vertex * @param v1 * @return */ QPointF Graph::vertexPos(const int &v1) const { return m_graph[vpos[v1]]->pos(); } GraphVertex *Graph::vertexPtr(const int v) { if (!vpos.contains(v)) return nullptr; const int idx = vpos.value(v); if (idx < 0 || idx >= m_graph.size()) return nullptr; return m_graph.at(idx); } SocNetV-3.7/src/graph/ui/000077500000000000000000000000001524402210300152025ustar00rootroot00000000000000SocNetV-3.7/src/graph/ui/graph_canvas.cpp000066400000000000000000000077221524402210300203520ustar00rootroot00000000000000/** * @file graph_canvas.cpp * @brief Implements canvas geometry and coordinate helper methods for the Graph * class (canvas size, visible coordinates, random coordinates). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" /** * @brief Sets the size of the canvas * * Called when the MW is resized to update canvasWidth/canvasHeight, and node positions * * @param w * @param h */ void Graph::canvasSizeSet(const int &width, const int &height) { qreal fX = (static_cast(width)) / canvasWidth; qreal fY = (static_cast(height)) / canvasHeight; qreal newX, newY; qCDebug(lcGraphUI) << "Canvas was resized: " << width << "x" << height << "Adjusting node positions, if any. Please wait..."; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { newX = (*it)->x() * fX; newY = (*it)->y() * fY; (*it)->setX(newX); (*it)->setY(newY); emit setNodePos((*it)->number(), newX, newY); } canvasWidth = width; canvasHeight = height; // progressStatus(tr("Canvas size: (%1, %2)px") // .arg(QString::number(canvasWidth)) // .arg(QString::number(canvasHeight)) // ); setModStatus(ModStatus::VertexPositions, false); // Signal the view to auto-fit after node rescaling completes. // QueuedConnection ordering guarantees all setNodePos() calls are processed // on the main thread before zoomToFit() runs. emit signalLayoutFinished(); qCDebug(lcGraphUI) << "Finished resizing."; } /** * @brief Sets the canvas size without rescaling node positions. * * Used by Graph::subgraphExtract() to propagate the source graph's canvas * dimensions to the new Graph object so that IO routines normalize * coordinates correctly on export. * * @param width New canvas width in pixels. * @param height New canvas height in pixels. */ void Graph::canvasSizeSetQuiet(const qreal &width, const qreal &height) { canvasWidth = width; canvasHeight = height; } /** * @brief Gets the max radius of the canvas * @return double */ double Graph::canvasMaxRadius() const { return (canvasHeight < canvasWidth) ? canvasHeight / 2.0 - 30 : canvasWidth / 2.0 - 30; } /** * @brief Gets the min dimensions of the canvas * @return qreal */ qreal Graph::canvasMinDimension() const { return (canvasHeight < canvasWidth) ? canvasHeight - 30 : canvasWidth - 30; } /** * @brief Checks if x is visible inside the canvas usable area and if not returns an adjusted x-coordinate * @param x * @return double */ double Graph::canvasVisibleX(const double &x) const { return qMin(canvasWidth - 50.0, qMax(50.0, x)); } /** * @brief Checks if y is visible inside the canvas usable area and if not returns an adjusted y-coordinate * @param y * @return double */ double Graph::canvasVisibleY(const double &y) const { return qMin(canvasHeight - 50.0, qMax(50.0, y)); } /** * @brief Returns a random x-coordinate adjusted to be visible inside the canvas usable area * @return double */ double Graph::canvasRandomX() const { qreal randX = static_cast(rand() % static_cast(canvasWidth)); return qMin(canvasWidth - 30.0, qMax(30.0, randX)); } /** * @brief Returns a random y-coordinate adjusted to be visible inside the canvas usable area * @return double */ double Graph::canvasRandomY() const { qreal randY = static_cast(rand() % static_cast(canvasHeight)); return qMin(canvasHeight - 30.0, qMax(30.0, randY)); } SocNetV-3.7/src/graph/ui/graph_edge_style.cpp000066400000000000000000000230221524402210300212120ustar00rootroot00000000000000/** * @file graph_edge_style.cpp * @brief Implements edge appearance and label/visibility helpers for the Graph * class (edge colors, labels, and display toggles). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" // // Visibility toggles // /** * @brief Changes the visibility of edge weight numbers * @param toggle */ void Graph::edgeWeightNumbersVisibilitySet(const bool &toggle) { initEdgeWeightNumbers = toggle; } // // Edge colors // /** * @brief Saves the default edge color * Used by random network creation methods * @param color */ void Graph::edgeColorInit(const QString &color) { initEdgeColor = color; } // #30: stores the user-configured color for zero-weight edges void Graph::edgeColorZeroInit(const QString &color) { initEdgeColorZero = color; } // #30: controls whether zero-weight edges are drawn at all void Graph::showZeroWeightEdgesSet(const bool &toggle) { initShowZeroWeightEdges = toggle; } /** * @brief Changes the color of all enabled edges. * @param color * @return */ bool Graph::edgeColorAllSet(const QString &color, const int &threshold) { qCDebug(lcGraphUI) << "Graph::edgeColorAllSet() - new color: " << color; int target = 0, source = 0; edgeColorInit(color); QHash enabledOutEdges; QHash::const_iterator it1; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { source = (*it)->number(); if (!(*it)->isEnabled()) continue; enabledOutEdges = (*it)->outEdgesEnabledHash(); it1 = enabledOutEdges.cbegin(); while (it1 != enabledOutEdges.cend()) { target = it1.key(); if (threshold == 0) { if (it1.value() == threshold) { qCDebug(lcGraphUI) << " Graph::edgeColorAllSet() zero weight threshold " << threshold << " - edge " << source << "->" << target << " new color " << color; (*it)->setOutLinkColor(target, color); emit setEdgeColor(source, target, color); } } else if (threshold != 0 && threshold != RAND_MAX) { if (it1.value() <= threshold) { qCDebug(lcGraphUI) << " Graph::edgeColorAllSet() below weight threshold " << threshold << " - edge " << source << "->" << target << " new color " << color; (*it)->setOutLinkColor(target, color); emit setEdgeColor(source, target, color); } } else { qCDebug(lcGraphUI) << " Graph::edgeColorAllSet() : " << source << "->" << target << " new color " << color; (*it)->setOutLinkColor(target, color); emit setEdgeColor(source, target, color); } ++it1; } } // delete enabledOutEdges; setModStatus(ModStatus::EdgeMetadata); return true; } /** * @brief Changes the color of edge v1->v2 * @param v1 * @param v2 * @param color */ void Graph::edgeColorSet(const int &v1, const int &v2, const QString &color) { qCDebug(lcGraphUI) << "Graph::edgeColorSet() - " << v1 << "->" << v2 << " vpos (" << vpos[v1] << "->" << vpos[v2] << ")" << " new color " << color; m_graph[vpos[v1]]->setOutLinkColor(v2, color); emit setEdgeColor(v1, v2, color); if (isSymmetric()) { m_graph[vpos[v2]]->setOutLinkColor(v1, color); emit setEdgeColor(v2, v1, color); } setModStatus(ModStatus::EdgeMetadata); } /** * @brief Returns the color of the directed edge v1->v2 * @param v1 * @param v2 * @return */ QString Graph::edgeColor(const int &v1, const int &v2) { return m_graph[vpos[v1]]->outLinkColor(v2); } // // Edge labels // /** * @brief Changes the label of edge v1->v2 * @param v1 * @param v2 * @param weight */ void Graph::edgeLabelSet(const int &v1, const int &v2, const QString &label) { qCDebug(lcGraphUI) << "Graph::edgeLabelSet() " << v1 << "[" << vpos[v1] << "]->" << v2 << "[" << vpos[v2] << "]" << " label " << label; m_graph[vpos[v1]]->setOutEdgeLabel(v2, label); emit setEdgeLabel(v1, v2, label); setModStatus(ModStatus::EdgeMetadata); } /** * @brief Returns the label of edge v1->v2 * @param v1 * @param v2 * @return */ QString Graph::edgeLabel(const int &v1, const int &v2) const { return m_graph[vpos[v1]]->outEdgeLabel(v2); } /** * @brief Sets all custom attributes on edge v1β†’v2, replacing any previously * stored attributes for that edge. * @param v1 Source vertex number. * @param v2 Target vertex number. * @param attrs Key/value map of custom attributes. */ void Graph::edgeCustomAttributesSet(const int &v1, const int &v2, const QHash &attrs) { m_graph[vpos[v1]]->setOutEdgeCustomAttributes(v2, attrs); setModStatus(ModStatus::EdgeMetadata); } /** * @brief Returns the custom attributes stored on edge v1β†’v2. * Returns an empty hash if no attributes have been set for that edge. * @param v1 Source vertex number. * @param v2 Target vertex number. */ QHash Graph::edgeCustomAttributes(const int &v1, const int &v2) const { return m_graph[vpos[v1]]->outEdgeCustomAttributes(v2); } /** * @brief Imports custom attributes from a parsed table into existing edges. * * Each row is matched to an edge by looking up vertex numbers from * @p srcColumn and @p tgtColumn. All other columns become custom attributes * on the matched edge. Rows that do not match any existing edge are skipped. * * Example β€” CSV input with srcColumn=0, tgtColumn=1. * Native editable columns (Weight, Label, Color) are routed to their setters; * read-only native columns (Relation) are silently skipped: * @code * Source,Target,Weight,Label,Color,relationship,strength * 1,2,0.8,,#666666,invested_in,strong ← Weight/Color updated; relationship/strength β†’ custom attrs * 2,3,0.5,,#666666,mentors,medium * @endcode * * @return Number of edges that received at least one attribute update. */ int Graph::edgeAttributesImport(const QStringList &headers, const QVector &rows, int srcColumn, int tgtColumn) { int matched = 0; for (const QStringList &row : rows) { if (srcColumn >= row.size() || tgtColumn >= row.size()) continue; bool okSrc = false, okTgt = false; const int src = row.at(srcColumn).toInt(&okSrc); const int tgt = row.at(tgtColumn).toInt(&okTgt); if (!okSrc || !okTgt) continue; if (edgeExists(src, tgt) == 0) continue; QHash newCustomAttrs = edgeCustomAttributes(src, tgt); for (int c = 0; c < headers.size(); ++c) { if (c == srcColumn || c == tgtColumn || c >= row.size()) continue; const QString &h = headers.at(c); const QString &v = row.at(c); // Route editable native columns to their proper setters; // silently skip read-only native columns (Source, Target, Relation). if (h.compare(QLatin1String("Weight"), Qt::CaseInsensitive) == 0) { bool ok = false; const qreal w = v.toDouble(&ok); if (ok) edgeWeightSet(src, tgt, w); } else if (h.compare(QLatin1String("Label"), Qt::CaseInsensitive) == 0) edgeLabelSet(src, tgt, v); else if (h.compare(QLatin1String("Color"), Qt::CaseInsensitive) == 0) edgeColorSet(src, tgt, v); else if (h.compare(QLatin1String("Relation"), Qt::CaseInsensitive) == 0) continue; // read-only β€” skip else newCustomAttrs.insert(h, v); } if (!newCustomAttrs.isEmpty()) edgeCustomAttributesSet(src, tgt, newCustomAttrs); ++matched; } qCDebug(lcGraphUI) << "edgeAttributesImport: matched" << matched << "of" << rows.size() << "rows"; return matched; } /** * @brief Returns a list of all unique custom attribute keys present across * all enabled edges in the current graph. */ QStringList Graph::graphHasEdgeCustomAttributes() const { QStringList keys; for (auto it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) continue; for (auto eit = (*it)->m_outEdges.cbegin(); eit != (*it)->m_outEdges.cend(); ++eit) { const int v2 = eit.key(); const QHash attrs = (*it)->outEdgeCustomAttributes(v2); for (auto ait = attrs.cbegin(); ait != attrs.cend(); ++ait) { if (!keys.contains(ait.key())) keys.append(ait.key()); } } } return keys; } /** * @brief Toggles the visibility of edge labels. * @param toggle */ void Graph::edgeLabelsVisibilitySet(const bool &toggle) { initEdgeLabels = toggle; } SocNetV-3.7/src/graph/ui/graph_selection.cpp000066400000000000000000000117521524402210300210620ustar00rootroot00000000000000/** * @file graph_selection.cpp * @brief Implements selection and clicked-state helper methods for the Graph * class (selected vertices/edges, click handling, selection change hooks). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" /** * @brief Resets the clicked edge and node * * Usually, called when the user clicks on an empty space. * * @param p */ void Graph::graphClickedEmptySpace(const QPointF &p) { qCDebug(lcGraphUI) << "Click on empty space at" << p << " - resetting clicked edge and node..."; // Reset clicked vertices this->vertexClickedSet(0, p); // Reset clicked edges this->edgeClickedSet(0, 0); } /** * @brief Sets the user-selected vertices and edges * * Usually called from GW, it emits selection counts to MW * * @param selectedVertices * @param selectedEdges */ void Graph::setSelectionChanged(const QList selectedVertices, const QList selectedEdges) { m_verticesSelected = selectedVertices; m_selectedEdges = selectedEdges; qCDebug(lcGraphUI) << "Selection changed. Vertices" << m_verticesSelected << "Edges" << m_selectedEdges << "Emitting to MW..."; emit signalSelectionChanged(m_verticesSelected.size(), m_selectedEdges.size()); } /** * @brief Returns a QList of user-selected vertices * @return */ QList Graph::getSelectedVertices() const { return m_verticesSelected; } /** * @brief Returns count of user-selected vertices * @return */ int Graph::getSelectedVerticesCount() const { return m_verticesSelected.size(); } /** * @brief Returns min of user-selected vertices * @return */ int Graph::getSelectedVerticesMin() const { int min = RAND_MAX; foreach (int i, m_verticesSelected) { if (i < min) min = i; } return min; } /** * @brief Returns max of user-selected vertices * @return */ int Graph::getSelectedVerticesMax() const { int max = 0; foreach (int i, m_verticesSelected) { if (i > max) max = i; } return max; } /** * @brief Returns a QList of user-selected edges in pair * @return */ QList Graph::getSelectedEdges() const { return m_selectedEdges; } /** * @brief Returns the count of user-selected edges * @return */ int Graph::getSelectedEdgesCount() const { return m_selectedEdges.size(); } /** * @brief Sets the clicked vertex. * * Signals to MW to show node info on the status bar. * * @param v1 * @param p */ void Graph::vertexClickedSet(const int &v1, const QPointF &p) { qCDebug(lcGraphUI) << "Setting clicked vertex: " << v1 << "click at " << p; m_vertexClicked = v1; if (v1 == 0) { emit signalNodeClickedInfo(0, p); } else { edgeClickedSet(0, 0); emit signalNodeClickedInfo(v1, vertexPos(v1), vertexLabel(v1), vertexDegreeIn(v1), vertexDegreeOut(v1)); } } /** * @brief Returns the number of the clicked vertex * @return int */ int Graph::vertexClicked() const { return m_vertexClicked; } /** * @brief Sets the clicked edge * * Parameters are the source and target node of the edge. * It emits signal to MW, which displays a relevant message on the status bar. * * @param v1 * @param v2 */ void Graph::edgeClickedSet(const int &v1, const int &v2, const bool &openMenu) { m_clickedEdge.source = v1; m_clickedEdge.target = v2; if (m_clickedEdge.source == 0 && m_clickedEdge.target == 0) { emit signalEdgeClicked(); return; } qreal weight = m_graph[vpos[m_clickedEdge.source]]->hasEdgeTo(m_clickedEdge.target); qCDebug(lcGraphUI) << "Setting clicked edge: " << v1 << "->" << v2 << "weight:" << weight; int type = EdgeType::Directed; // Check if the reverse tie exists. If yes, this is a reciprocated edge qreal oppositeWeight = edgeExists(m_clickedEdge.target, m_clickedEdge.source, false); if (oppositeWeight) { qCDebug(lcGraphUI) << "Reverse tie" << v2 << "->" << v1 << "exists. Weight:" << oppositeWeight; if (!isDirected()) { type = EdgeType::Undirected; } else { type = EdgeType::Reciprocated; } } m_clickedEdge.type = type; m_clickedEdge.weight = weight; m_clickedEdge.rWeight = oppositeWeight; emit signalEdgeClicked(m_clickedEdge, openMenu); } /** * @brief Returns clicked edge * @return */ MyEdge Graph::edgeClicked() { return m_clickedEdge; } SocNetV-3.7/src/graph/ui/graph_ui_facade.cpp000066400000000000000000000062221524402210300207710ustar00rootroot00000000000000/** * @file graph_ui_facade.cpp * @brief Implements FaΓ§ade wrapper methods called by the UI * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include #include #include QThread *Graph::getThread() const { return thread(); } void Graph::moveToThreadFacade(QThread *t) { moveToThread(t); } /** * @brief Runs @p fn on the application's main/GUI thread, regardless of the calling thread. * * Exists because `Graph`'s own thread affinity is graphThread (see moveToThreadFacade()), but * some UI faΓ§ade code (currently: the prominence-distribution chart builders in * graph_ui_prominence_distribution.cpp) constructs real Qt GUI objects β€” QtCharts series/axes, * QChartView::grab() for PNG export β€” which is only ever safe on the main/GUI thread, per Qt's * single-threaded-GUI rule. Those objects would otherwise inherit whichever thread happens to be * calling in, which is graphThread whenever this is reached from algorithm-slice code (layouts, * report generation, etc.) that now genuinely executes there (see #254). * * Uses QCoreApplication::instance() rather than the qApp macro (which force-casts to * QApplication*) so this stays correct in the headless CLI, where only a QCoreApplication is * constructed - never a QApplication. Qt::AutoConnection (the default, used explicitly here for * clarity) resolves to a direct, synchronous call when already on the target thread - which is * always true for the CLI, since it never creates a second thread - so this has no behavioural * or performance effect there. In GUI mode, called from graphThread, it resolves to queued * delivery on the next GUI event loop iteration. * * @param fn The work to run on the main/GUI thread. */ void Graph::runOnGuiThread(std::function fn) { QMetaObject::invokeMethod(QCoreApplication::instance(), fn, Qt::AutoConnection); } // INTERNAL-FACADE HELPERS /** * @brief Emits a status message to be shown in the UI status bar. * @param msg The message to be shown. */ void Graph::progressStatus(const QString &msg) { emit statusMessage(msg); } /** * @brief Resets the cancellation status */ void Graph::resetProgressCanceled() { qCDebug(lcGraphUI) << "Graph::resetProgressCanceled() - resetting flag"; m_progressCanceled = false; } /** * @brief Returns true if the user has requested cancellation via the progress dialog. */ bool Graph::progressCanceled() const { return m_progressCanceled; } /** * @brief Slot called by MainWindow when the user clicks Cancel in the progress dialog. */ void Graph::slotCancelComputation() { qCDebug(lcGraphUI) << "Graph::slotCancelComputation() - setting flag from thread:" << QThread::currentThreadId(); m_progressCanceled = true; } SocNetV-3.7/src/graph/ui/graph_ui_prominence_distribution.cpp000066400000000000000000000250151524402210300245250ustar00rootroot00000000000000/** * @file graph_prominence_distribution.cpp * @brief Implements visualization routines (bars, spline, area charts) * for prominence index distribution for the Graph class. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include #include #include #include #include #include #include #include #include #include #include #include #include void Graph::uiProminenceDistributionSpline(const QVector> &points, qreal min, qreal max, qreal minF, qreal maxF, const QString &seriesName, const QString &distImageFileName) { // Guarantees GUI-thread execution regardless of the calling thread - this function // constructs real QtCharts objects and (for PNG export) grabs a QChartView, both of which // are only safe on the main/GUI thread. See Graph::runOnGuiThread() for why this is needed. runOnGuiThread([this, points, min, max, minF, maxF, seriesName, distImageFileName]() { qCDebug(lcGraphUI) << "Computing prominence distribution as spline chart..."; auto *series = new QLineSeries(); series->setName(seriesName); auto *axisX = new QValueAxis(); auto *axisY = new QValueAxis(); // Used only for large chart export auto *series1 = new QLineSeries(); series1->setName(seriesName); auto *axisX1 = new QValueAxis(); auto *axisY1 = new QValueAxis(); for (const auto &pt : points) { series->append(pt.first, pt.second); series1->append(pt.first, pt.second); } axisX->setMin(min); axisX->setMax(max); axisY->setMin(minF); axisY->setMax(maxF + 1.0); QPen sPen(QColor("#209fdf")); sPen.setWidthF(0.9); QBrush sBrush(QColor("#ff0000")); series->setBrush(sBrush); series->setPen(sPen); if (!distImageFileName.isEmpty()) { qCDebug(lcGraphUI) << "saving prominence distribution image to" << distImageFileName; axisX1->setMin(min); axisX1->setMax(max); axisY1->setMin(minF); axisY1->setMax(maxF + 1.0); auto *chart = new QChart(); auto *chartView = new QChartView(chart); chart->addSeries(series1); chart->setTitle(series1->name() + " distribution"); chart->setTitleFont(QFont("Times", 12)); chart->legend()->hide(); chart->addAxis(axisX1, Qt::AlignBottom); series1->attachAxis(axisX1); chart->addAxis(axisY1, Qt::AlignLeft); series1->attachAxis(axisY1); chart->axes(Qt::Vertical).first()->setMin(0); chart->axes(Qt::Horizontal).first()->setMin(0); chart->axes(Qt::Horizontal).first()->setLabelsAngle(-90); chart->resize(2560, 1440); chartView->resize(2561, 1441); QPixmap p = chartView->grab(); p.save(distImageFileName, "PNG"); chartView->hide(); delete chartView; // matches your old β€œdon’t delete axes too early” rationale } qCDebug(lcGraphUI) << "emitting signal to MW update the prominence distribution spline chart"; emit signalPromininenceDistributionChartUpdate(series, axisX, min, max, axisY, minF, maxF); }); } void Graph::uiProminenceDistributionArea(const QVector> &points, const qreal min, const qreal max, const qreal minF, const qreal maxF, const QString &name, const QString &distImageFileName) { // Guarantees GUI-thread execution regardless of the calling thread - see // Graph::runOnGuiThread() and the same note in uiProminenceDistributionSpline() above. runOnGuiThread([this, points, min, max, minF, maxF, name, distImageFileName]() { QAreaSeries *series = new QAreaSeries(); series->setName(name); QLineSeries *upperSeries = new QLineSeries(); QValueAxis *axisX = new QValueAxis(); QValueAxis *axisY = new QValueAxis(); // Used only for the large chart exported to PNG for the HTML report QAreaSeries *series1 = new QAreaSeries(); series1->setName(name); QValueAxis *axisX1 = new QValueAxis(); QValueAxis *axisY1 = new QValueAxis(); // Fill series points (same semantics: upperSeries holds the curve) for (const auto &pt : points) { upperSeries->append(pt.first, pt.second); } axisX->setMin(min); axisX->setMax(max); axisY->setMin(minF); axisY->setMax(maxF + 1.0); series->setUpperSeries(upperSeries); QPen sPen(QColor("#666")); sPen.setWidthF(0.2); QBrush sBrush(QColor("#209fdf")); series->setBrush(sBrush); series->setPen(sPen); if (!distImageFileName.isEmpty()) { qCDebug(lcGraphUI) << "saving distribution image to" << distImageFileName; axisX1->setMin(min); axisX1->setMax(max); axisY1->setMin(minF); axisY1->setMax(maxF + 1.0); // Keep export behavior identical: export uses its own chart/axes, // but reuses the computed upperSeries data semantics. // IMPORTANT: attach the same upperSeries instance to series1 // to preserve output; if you prefer deep-copy, we can clone points. series1->setUpperSeries(upperSeries); QChart *chart = new QChart(); QChartView *chartView = new QChartView(chart); chart->addSeries(series1); chart->setTitle(series->name() + " distribution"); chart->setTitleFont(QFont("Times", 12)); chart->legend()->hide(); chart->addAxis(axisX1, Qt::AlignBottom); series1->attachAxis(axisX1); chart->addAxis(axisY1, Qt::AlignLeft); series1->attachAxis(axisY1); chart->axes(Qt::Vertical).first()->setMin(0); chart->axes(Qt::Horizontal).first()->setMin(0); chart->axes(Qt::Horizontal).first()->setLabelsAngle(-90); chart->resize(2560, 1440); chartView->resize(2561, 1441); QPixmap p = chartView->grab(); p.save(distImageFileName, "PNG"); chartView->hide(); delete chartView; } qCDebug(lcGraphUI) << "emitting signal to MW update the prominence distribution area chart"; emit signalPromininenceDistributionChartUpdate(series, axisX, min, max, axisY, minF, maxF); }); } void Graph::uiProminenceDistributionBars(const QStringList &categories, const QVector &frequencies, const qreal min, const qreal max, const qreal minF, const qreal maxF, const QString &name, const QString &distImageFileName) { // Guarantees GUI-thread execution regardless of the calling thread - see // Graph::runOnGuiThread() and the same note in uiProminenceDistributionSpline() above. runOnGuiThread([this, categories, frequencies, min, max, minF, maxF, name, distImageFileName]() { qCDebug(lcGraphUI) << "Computing prominence distribution as bar chart (UI layer)..."; auto *series = new QBarSeries(); series->setName(name); auto *barSet = new QBarSet(""); auto *axisY = new QValueAxis(); auto *axisX = new QBarCategoryAxis(); // Used only for large chart export (keep identical behavior) auto *series1 = new QBarSeries(); series1->setName(name); auto *barSet1 = new QBarSet(""); auto *axisY1 = new QValueAxis(); auto *axisX1 = new QBarCategoryAxis(); // Fill categories + bars // Old code appended categories to axisX and values to barSet in lockstep. axisX->append(categories); for (const auto &f : frequencies) { barSet->append(f); } // axis ranges (same semantics as old) axisX->setMin(QString::number(min, 'f', 6)); axisX->setMax(QString::number(max, 'f', 6)); axisY->setMin(minF); axisY->setMax(maxF + 1.0); series->append(barSet); QPen sPen(QColor("#666")); sPen.setWidthF(0.2); QBrush sBrush(QColor("#209fdf")); barSet->setBrush(sBrush); barSet->setPen(sPen); if (!distImageFileName.isEmpty()) { qCDebug(lcGraphUI) << "saving distribution image to" << distImageFileName; // Export copies (old code only built these when filename non-empty) axisX1->append(categories); for (const auto &f : frequencies) { barSet1->append(f); } series1->append(barSet1); axisX1->setMin(QString::number(min, 'f', 6)); axisX1->setMax(QString::number(max, 'f', 6)); axisY1->setMin(minF); axisY1->setMax(maxF + 1.0); auto *chart = new QChart(); auto *chartView = new QChartView(chart); chart->addSeries(series1); chart->setTitle(series->name() + " distribution"); chart->setTitleFont(QFont("Times", 12)); chart->legend()->hide(); chart->addAxis(axisX1, Qt::AlignBottom); series1->attachAxis(axisX1); chart->addAxis(axisY1, Qt::AlignLeft); series1->attachAxis(axisY1); chart->axes(Qt::Vertical).first()->setMin(0); chart->axes(Qt::Horizontal).first()->setMin(0); chart->axes(Qt::Horizontal).first()->setLabelsAngle(-90); chart->resize(2560, 1440); chartView->resize(2561, 1441); QPixmap p = chartView->grab(); p.save(distImageFileName, "PNG"); chartView->hide(); delete chartView; } qCDebug(lcGraphUI) << "emitting signal to MW update the prominence distribution bar chart"; emit signalPromininenceDistributionChartUpdate(series, axisX, min, max, axisY, minF, maxF); }); }SocNetV-3.7/src/graph/ui/graph_vertex_style.cpp000066400000000000000000000465431524402210300216400ustar00rootroot00000000000000/** * @file graph_vertex_style.cpp * @brief Implements vertex appearance and labeling methods for the Graph class * (size, shape/icons, colors, labels, number styling, custom attributes). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include // // Vertex size // /** * @brief Sets the initial vertex size * * @param size */ void Graph::vertexSizeInit(const int size) { initVertexSize = size; } /** * @brief Changes the size of a vertex v or all vertices if v=0 * * Called from MW (i.e. user changing node properties) * * @param v * @param size */ void Graph::vertexSizeSet(const int &v, const int &size) { if (v) { qCDebug(lcGraphUI) << "Changing size of vertex" << v << "new size" << size; m_graph[vpos[v]]->setSize(size); emit setNodeSize(v, size); } else { qCDebug(lcGraphUI) << "Changing size of all vertices, new size" << size; vertexSizeInit(size); VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { continue; } else { (*it)->setSize(size); emit setNodeSize((*it)->number(), size); } } } setModStatus(ModStatus::VertexMetadata); } /** * @brief Returns the size of vertex v * @param v * @return int */ int Graph::vertexSize(const int &v) const { return m_graph[vpos[v]]->size(); } // // Vertex shape + icons // /** * @brief Sets the default vertex shape and iconPath * * @param shape * @param iconPath */ void Graph::vertexShapeSetDefault(const QString shape, const QString &iconPath) { initVertexShape = shape; initVertexIconPath = iconPath; } /** * @brief Changes the shape and iconPath of vertex v1, or all vertices if v1=-1 * @param v1 * @param shape * @param iconPath */ void Graph::vertexShapeSet(const int &v1, const QString &shape, const QString &iconPath) { if (v1 == -1) { qCDebug(lcGraphUI) << "Changing shape for all vertices, new shape:" << shape << "iconPath:" << iconPath; vertexShapeSetDefault(shape, iconPath); VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { continue; } else { (*it)->setShape(shape, iconPath); emit setNodeShape((*it)->number(), shape, iconPath); } } } else { qCDebug(lcGraphUI) << "Changing shape for vertex:" << v1 << "new shape:" << shape << "iconPath:" << iconPath; m_graph[vpos[v1]]->setShape(shape, iconPath); emit setNodeShape(v1, shape, iconPath); } setModStatus(ModStatus::VertexMetadata); } /** * @brief Returns the shape of this vertex * @param v1 * @return */ QString Graph::vertexShape(const int &v1) { return m_graph[vpos[v1]]->shape(); } /** * @brief Returns the IconPath of vertex v1 * @param v1 * @return */ QString Graph::vertexShapeIconPath(const int &v1) { return m_graph[vpos[v1]]->shapeIconPath(); } /** * @brief Returns true if at least one vertex has a 'custom' shape (therefore a custom icon). * @return bool */ bool Graph::graphHasVertexCustomIcons() const { VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) continue; if ((*it)->shape() == "custom") { return true; } } return false; } // // Vertex Custom attributes // /** * @brief Returns true if at least one vertex has a 'custom' attribute * @return bool */ QStringList Graph::graphHasVertexCustomAttributes() const { VList::const_iterator it; QStringList m_customAttributesNames; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { continue; } QHashIterator i((*it)->customAttributes()); while (i.hasNext()) { i.next(); if (!m_customAttributesNames.contains(i.key())) { m_customAttributesNames.append(i.key()); } } } return m_customAttributesNames; } /** * @brief Retrieves the vertex at the position specified by the index `v1` * from the `vpos` map and calls its `customAttributes` method. * * @param v1 The index of the vertex whose custom attributes are to be accessed. */ QHash Graph::vertexCustomAttributes(const int &v1) const { return m_graph[vpos[v1]]->customAttributes(); } // // Vertex color // /** * @brief Changes the color of vertex v1 * @param v1 * @param color */ void Graph::vertexColorSet(const int &v1, const QString &color) { if (v1) { qCDebug(lcGraphUI) << "Setting vertex" << v1 << "new color" << color; m_graph[vpos[v1]]->setColor(color); emit setNodeColor(m_graph[vpos[v1]]->number(), color); } else { qCDebug(lcGraphUI) << "Setting new color for all vertices:" << color; vertexColorInit(color); VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { continue; } else { qCDebug(lcGraphUI) << "for all, setting vertex" << (*it)->number() << " new color" << color; (*it)->setColor(color); emit setNodeColor((*it)->number(), color); } } } setModStatus(ModStatus::VertexMetadata); } /** * @brief Graph::vertexColor * @param v1 * @return */ QColor Graph::vertexColor(const int &v1) const { return QColor(m_graph[vpos[v1]]->color()); } /** * @brief Graph::vertexColorInit * default vertex color initialization * @param color */ void Graph::vertexColorInit(const QString &color) { initVertexColor = color; } // // Vertex number styling (font/color/distance) // /** * @brief Changes the initial color of the vertex numbers * @param color */ void Graph::vertexNumberColorInit(const QString &color) { initVertexNumberColor = color; } /** * @brief Graph::vertexColorSet * Changes the color of vertex v1 * @param v1 * @param color */ void Graph::vertexNumberColorSet(const int &v1, const QString &color) { qCDebug(lcGraphUI) << "Setting number color for vertex:" << v1 << "new number color:" << color; if (v1) { m_graph[vpos[v1]]->setNumberColor(color); emit setNodeNumberColor(m_graph[vpos[v1]]->number(), color); } else { qCDebug(lcGraphUI) << "Changing color for all node numbers"; vertexNumberColorInit(color); VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { continue; } else { (*it)->setNumberColor(color); emit setNodeNumberColor((*it)->number(), color); } } } setModStatus(ModStatus::VertexMetadata); } /** * @brief Changes the initial size of vertex numbers * @param size */ void Graph::vertexNumberSizeInit(const int &size) { initVertexNumberSize = size; } /** * @brief Changes the size of vertex v number * @param v * @param size */ void Graph::vertexNumberSizeSet(const int &v, const int &size) { if (v) { qCDebug(lcGraphUI) << "Changing number size for vertex" << v << "new number size" << size; m_graph[vpos[v]]->setNumberSize(size); emit setNodeNumberSize(m_graph[vpos[v]]->number(), size); } else { qCDebug(lcGraphUI) << "Setting new number size for all vertices to:" << size; vertexNumberSizeInit(size); VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { continue; } else { qCDebug(lcGraphUI) << "for all, setting vertex" << (*it)->number() << " new number size " << size; (*it)->setNumberSize(size); emit setNodeNumberSize((*it)->number(), size); } } } setModStatus(ModStatus::MinorOptions); } /** * @brief Changes the initial distance of vertex numbers * @param distance */ void Graph::vertexNumberDistanceInit(const int &distance) { initVertexNumberDistance = distance; } /** * @brief Changes the distance.of vertex v number from the vertex * @param v * @param size */ void Graph::vertexNumberDistanceSet(const int &v, const int &newDistance) { if (v) { qCDebug(lcGraphUI) << "Changing number distance for vertex" << v << "new number distance" << newDistance; m_graph[vpos[v]]->setNumberDistance(newDistance); emit setNodeNumberDistance(v, newDistance); } else { qCDebug(lcGraphUI) << "Changing number distance for all vertices, " "new number distance" << newDistance; vertexNumberDistanceInit(newDistance); VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { continue; } else { (*it)->setNumberDistance(newDistance); emit setNodeNumberDistance((*it)->number(), newDistance); } } } setModStatus(ModStatus::MinorOptions); } // // Vertex label styling (font/color/distance) // /** * @brief Changes the label of a vertex v1 * @param v1 * @param label */ void Graph::vertexLabelSet(const int &v1, const QString &label) { qCDebug(lcGraphUI) << "Graph::vertexLabelSet() - vertex " << v1 << "vpos " << vpos[v1] << "new label" << label; m_graph[vpos[v1]]->setLabel(label); emit setNodeLabel(m_graph[vpos[v1]]->number(), label); setModStatus(ModStatus::VertexMetadata); } /** * @brief Returns the label of a vertex v1 * @param v1 * @return */ QString Graph::vertexLabel(const int &v) const { return m_graph[vpos[v]]->label(); } /** * @brief Graph::vertexLabelSizeInit * Changes the default size of vertex labels * @param newSize */ void Graph::vertexLabelSizeInit(int newSize) { initVertexLabelSize = newSize; } /** * @brief Changes the label size of vertex v1 or all vertices if v1=0 * @param v1 * @param size */ void Graph::vertexLabelSizeSet(const int &v1, const int &labelSize) { if (v1) { qCDebug(lcGraphUI) << "Changing the label size of vertex" << v1 << "new label size:" << labelSize; m_graph[vpos[v1]]->setLabelSize(labelSize); emit setNodeLabelSize(v1, labelSize); } else { qCDebug(lcGraphUI) << "Changing the label size of all vertices, new label size" << labelSize; vertexLabelSizeInit(labelSize); VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { continue; } else { qCDebug(lcGraphUI) << "Changing label size of all vertices, set vertex" << (*it)->number() << "new label size" << labelSize; (*it)->setLabelSize(labelSize); emit setNodeLabelSize((*it)->number(), labelSize); } } } setModStatus(ModStatus::MinorOptions); } /** * @brief Changes the label color of vertex v1 or all vertices if v1 = 0 * @param v1 * @param color */ void Graph::vertexLabelColorSet(const int &v1, const QString &color) { if (v1) { qCDebug(lcGraphUI) << "Changing the label color of vertex" << v1 << "new label color" << color; m_graph[vpos[v1]]->setLabelColor(color); emit setNodeLabelColor(v1, color); } else { qCDebug(lcGraphUI) << "Changing the label color of all vertices, " "new label color" << color; vertexLabelColorInit(color); VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { continue; } else { qCDebug(lcGraphUI) << "Changing the label color of all, set vertex" << v1 << "new label color" << color; (*it)->setLabelColor(color); emit setNodeLabelColor((*it)->number(), color); } } } setModStatus(ModStatus::MinorOptions); } /** * @brief Graph::vertexLabelColorInit * Changes the default vertex label color * @param color */ void Graph::vertexLabelColorInit(QString color) { initVertexLabelColor = color; } /** * @brief Changes the distance.of vertex v label from the vertex * @param v * @param size */ void Graph::vertexLabelDistanceSet(const int &v, const int &newDistance) { m_graph[vpos[v]]->setLabelDistance(newDistance); setModStatus(ModStatus::MinorOptions); emit setNodeLabelDistance(v, newDistance); } /** * @brief Changes the distance of all vertex labels from their vertices * @param size */ void Graph::vertexLabelDistanceAllSet(const int &newDistance) { qCDebug(lcGraphUI) << "Changing the label distance of all vertices to:" << newDistance; vertexLabelDistanceInit(newDistance); VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { continue; } else { qCDebug(lcGraphUI) << "vertex" << (*it)->number() << " new label distance:" << newDistance; (*it)->setLabelDistance(newDistance); emit setNodeLabelDistance((*it)->number(), newDistance); } } setModStatus(ModStatus::MinorOptions); } /** * @brief Changes the default distance of vertex labels * @param distance */ void Graph::vertexLabelDistanceInit(const int &distance) { initVertexLabelDistance = distance; } /** * @brief Sets custom attributes for a specified vertex. * * This function assigns a set of custom attributes to a vertex identified by its index. * It also updates the modification status to indicate that vertex metadata has been changed. * * @param v1 The index of the vertex for which custom attributes are being set. * @param customAttributes A QHash containing the custom attributes to be set for the vertex. * The keys and values of the QHash are both QStrings. */ void Graph::vertexCustomAttributesSet(const int &v1, const QHash &customAttributes) { m_graph[vpos[v1]]->setCustomAttributes(customAttributes); setModStatus(ModStatus::VertexMetadata); } /** * @brief Sets a single custom attribute key/value on vertex @p v1, * leaving all other attributes untouched. * @param v1 Vertex number. * @param key Attribute key. * @param value Attribute value. */ void Graph::vertexCustomAttributeSet(const int &v1, const QString &key, const QString &value) { QHash attrs = m_graph[vpos[v1]]->customAttributes(); attrs.insert(key, value); m_graph[vpos[v1]]->setCustomAttributes(attrs); setModStatus(ModStatus::VertexMetadata); } /** * @brief Removes a single custom attribute key from vertex @p v1. * Does nothing if the key does not exist. * @param v1 Vertex number. * @param key Attribute key to remove. */ void Graph::vertexCustomAttributeRemove(const int &v1, const QString &key) { QHash attrs = m_graph[vpos[v1]]->customAttributes(); attrs.remove(key); m_graph[vpos[v1]]->setCustomAttributes(attrs); setModStatus(ModStatus::VertexMetadata); } /** * @brief Imports custom attributes from a parsed table into existing vertices. * * Each row in @p rows is matched to a vertex either by node number * (matchByLabel == false) or by label (matchByLabel == true) using the value * in column @p idColumn. All other columns become custom attributes on the * matched vertex. Rows that do not match any vertex are silently skipped. * * Example β€” CSV input with idColumn=0, matchByLabel=false. * Native columns are routed to their proper setters (Label, Size, Color, Shape); * only Visible is silently skipped (it is a filter state, not a persistent attribute): * @code * #,Label,Size,Color,type,year_founded * 1,Alice,12,#ff0000,investor,2010 ← Label/Size/Color updated; type/year_founded β†’ custom attrs * 2,Bob,10,#0000ff,founder,2018 * @endcode * * @return Number of vertices that received at least one attribute update. */ int Graph::vertexAttributesImport(const QStringList &headers, const QVector &rows, int idColumn, bool matchByLabel) { int matched = 0; for (const QStringList &row : rows) { if (idColumn >= row.size()) continue; int vn = -1; if (matchByLabel) { const int idx = vertexIndexIfExists(row.at(idColumn)); if (idx != -1) vn = m_graph[idx]->number(); } else { bool ok = false; const int num = row.at(idColumn).toInt(&ok); if (ok && vertexExists(num)) vn = num; } if (vn < 0) continue; for (int c = 0; c < headers.size(); ++c) { if (c == idColumn || c >= row.size()) continue; const QString &h = headers.at(c); const QString &v = row.at(c); // Route editable native columns to their proper setters; // silently skip read-only/state columns (# is the id key, Visible is filter state). if (h.compare(QLatin1String("Label"), Qt::CaseInsensitive) == 0) vertexLabelSet(vn, v); else if (h.compare(QLatin1String("Size"), Qt::CaseInsensitive) == 0) { bool ok = false; const int sz = v.toInt(&ok); if (ok) vertexSizeSet(vn, sz); } else if (h.compare(QLatin1String("Color"), Qt::CaseInsensitive) == 0) vertexColorSet(vn, v); else if (h.compare(QLatin1String("Shape"), Qt::CaseInsensitive) == 0) vertexShapeSet(vn, v); else if (h.compare(QLatin1String("Visible"), Qt::CaseInsensitive) == 0) continue; // filter state β€” skip else vertexCustomAttributeSet(vn, h, v); } ++matched; } qCDebug(lcGraphUI) << "vertexAttributesImport: matched" << matched << "of" << rows.size() << "rows"; return matched; } SocNetV-3.7/src/graph/util/000077500000000000000000000000001524402210300155425ustar00rootroot00000000000000SocNetV-3.7/src/graph/util/graph_type_strings.cpp000066400000000000000000000174001524402210300221630ustar00rootroot00000000000000/** * @file graph_type_strings.cpp * @brief Implements Graph helper methods for mapping internal enum/int types to user-facing strings (and back), plus small string utilities. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2025 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graph.h" #include /** * @brief Helper method, return the human readable name of matrix type. * @param matrix */ QString Graph::graphMatrixTypeToString(const int &matrixType) const { QString matrixStr; switch (matrixType) { case MATRIX_ADJACENCY: matrixStr = "Adjacency Matrix"; break; case MATRIX_DISTANCES: matrixStr = "Distances Matrix"; break; case MATRIX_DEGREE: matrixStr = "Degree Matrix"; break; case MATRIX_LAPLACIAN: matrixStr = "Laplacian Matrix"; break; case MATRIX_ADJACENCY_INVERSE: matrixStr = "Adjacency Inverse"; break; case MATRIX_GEODESICS: matrixStr = "Geodesics Matrix"; break; case MATRIX_REACHABILITY: matrixStr = "Reachability Matrix"; break; case MATRIX_ADJACENCY_TRANSPOSE: matrixStr = "Adjacency Transpose"; break; case MATRIX_COCITATION: matrixStr = "Cocitation Matrix"; break; case MATRIX_DISTANCES_EUCLIDEAN: matrixStr = "Euclidean distance matrix"; break; case MATRIX_DISTANCES_MANHATTAN: matrixStr = "Manhattan distance matrix"; break; case MATRIX_DISTANCES_JACCARD: matrixStr = "Jaccard distance matrix"; break; case MATRIX_DISTANCES_HAMMING: matrixStr = "Hamming distance matrix"; break; default: matrixStr = "-"; break; } return matrixStr; } /** * @brief Helper method, return the matrix type of human readable matrix name . * @param matrix * @return */ int Graph::graphMatrixStrToType(const QString &matrix) const { if (matrix.contains("Hamming", Qt::CaseInsensitive)) { return MATRIX_DISTANCES_HAMMING; } else if (matrix.contains("Jaccard", Qt::CaseInsensitive)) { return MATRIX_DISTANCES_JACCARD; } else if (matrix.contains("Manhattan", Qt::CaseInsensitive)) { return MATRIX_DISTANCES_MANHATTAN; } else if (matrix.contains("Euclidean", Qt::CaseInsensitive)) { return MATRIX_DISTANCES_EUCLIDEAN; } else if (matrix.contains("Cocitation", Qt::CaseInsensitive)) { return MATRIX_COCITATION; } else if (matrix.contains("Adjacency Transpose", Qt::CaseInsensitive)) { return MATRIX_ADJACENCY_TRANSPOSE; } else if (matrix.contains("Reachability", Qt::CaseInsensitive)) { return MATRIX_REACHABILITY; } else if (matrix.contains("Geodesics", Qt::CaseInsensitive)) { return MATRIX_GEODESICS; } else if (matrix.contains("Adjacency Inverse", Qt::CaseInsensitive)) { return MATRIX_ADJACENCY_INVERSE; } else if (matrix.contains("Laplacian", Qt::CaseInsensitive)) { return MATRIX_LAPLACIAN; } else if (matrix.contains("Degree", Qt::CaseInsensitive)) { return MATRIX_DEGREE; } else if (matrix.contains("Adjacency", Qt::CaseInsensitive)) { return MATRIX_ADJACENCY; } else if (matrix.contains("Distances", Qt::CaseInsensitive)) { return MATRIX_DISTANCES; } else { return -1; } } /** * @brief Helper method, return the human readable name of metric type. * @param metric */ QString Graph::graphMetricTypeToString(const int &metricType) const { QString metricStr; switch (metricType) { case METRIC_SIMPLE_MATCHING: metricStr = "Simple / Exact matching"; break; case METRIC_JACCARD_INDEX: metricStr = "Jaccard Index"; break; case METRIC_HAMMING_DISTANCE: metricStr = "Hamming distance"; break; case METRIC_COSINE_SIMILARITY: metricStr = "Cosine similarity"; break; case METRIC_EUCLIDEAN_DISTANCE: metricStr = "Euclidean distance"; break; case METRIC_MANHATTAN_DISTANCE: metricStr = "Manhattan distance"; break; case METRIC_PEARSON_COEFFICIENT: metricStr = "Pearson Correlation Coefficient"; break; case METRIC_CHEBYSHEV_MAXIMUM: metricStr = "Chebyshev distance"; break; default: metricStr = "-"; break; } return metricStr; } /** * @brief Helper method, return the identifier of a metric. * @param metricStr */ int Graph::graphMetricStrToType(const QString &metricStr) const { int metric = METRIC_SIMPLE_MATCHING; if (metricStr.contains("Simple", Qt::CaseInsensitive)) metric = METRIC_SIMPLE_MATCHING; else if (metricStr.contains("Jaccard", Qt::CaseInsensitive)) metric = METRIC_JACCARD_INDEX; else if (metricStr.contains("None", Qt::CaseInsensitive)) metric = METRIC_NONE; else if (metricStr.contains("Hamming", Qt::CaseInsensitive)) metric = METRIC_HAMMING_DISTANCE; else if (metricStr.contains("Cosine", Qt::CaseInsensitive)) metric = METRIC_COSINE_SIMILARITY; else if (metricStr.contains("Euclidean", Qt::CaseInsensitive)) metric = METRIC_EUCLIDEAN_DISTANCE; else if (metricStr.contains("Manhattan", Qt::CaseInsensitive)) metric = METRIC_MANHATTAN_DISTANCE; else if (metricStr.contains("Pearson ", Qt::CaseInsensitive)) metric = METRIC_PEARSON_COEFFICIENT; else if (metricStr.contains("Chebyshev", Qt::CaseInsensitive)) metric = METRIC_CHEBYSHEV_MAXIMUM; return metric; } /** * @brief Helper method, return the human readable name of clustering method type. * @return */ QString Graph::graphClusteringMethodTypeToString(const int &methodType) const { QString methodStr; switch (methodType) { case Clustering::Single_Linkage: methodStr = "Single-linkage (minimum)"; break; case Clustering::Complete_Linkage: methodStr = "Complete-linkage (maximum)"; break; case Clustering::Average_Linkage: methodStr = "Average-linkage (UPGMA)"; break; default: break; } return methodStr; } /** * @brief Helper method, return clustering method type from the human readable name of it. * @param method * @return */ int Graph::graphClusteringMethodStrToType(const QString &method) const { int methodType = Clustering::Average_Linkage; if (method.contains("Single", Qt::CaseInsensitive)) { methodType = Clustering::Single_Linkage; } else if (method.contains("Complete", Qt::CaseInsensitive)) { methodType = Clustering::Complete_Linkage; } else if (method.contains("Average", Qt::CaseInsensitive)) { methodType = Clustering::Average_Linkage; } return methodType; } /** * @brief Helper method, returns a nice qstring where all html special chars are encoded * @param str * @return */ QString Graph::htmlEscaped(QString str) const { str = str.simplified(); if (str.contains('&')) { str = str.replace('&', "&"); } if (str.contains('<')) { str = str.replace('<', "<"); } if (str.contains('>')) { str = str.replace('>', ">"); } if (str.contains('\"')) { str = str.replace('\"', """); } if (str.contains('\'')) { str = str.replace('\'', "'"); } return str; } SocNetV-3.7/src/graphicsedge.cpp000077500000000000000000000621541524402210300166300ustar00rootroot00000000000000/** * @file graphicsedge.cpp * @brief Implements the GraphicsEdge class, which visualizes edges in the network graph. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graphicsedge.h" #include #include #include #include #include //used for qDebug messages #include #include "global.h" #include "graphicswidget.h" #include "graphicsnode.h" #include "graphicsedgeweight.h" #include "graphicsedgelabel.h" // #include SOCNETV_USE_NAMESPACE GraphicsEdge::GraphicsEdge(GraphicsWidget *gw, GraphicsNode *from, GraphicsNode *to, const qreal &weight, const QString &label, const QString &color, const Qt::PenStyle &style, const int &type, const bool &drawArrows, const bool &bezier, const bool &weightNumbers, const bool &highlighting, const int &arrowSize) : graphicsWidget(gw) { graphicsWidget->scene()->addItem(this); //add edge to scene to be displayed from->addOutEdge( this ); // adds this new edge to sourceNode to->addInEdge( this ); // adds this new edge to targetNode source=from; // saves the source node target=to; // saves the target node m_style = style; m_state = EDGE_STATE_REGULAR ; m_color=QColor(color); // saves the color of the edge m_drawArrows=drawArrows; // controls if edge will have arrows or not m_edgeDirType=type; m_minOffsetFromNode = 6; // controls the minimum offset from source/target node center m_offsetFromSourceNode=source->size()+m_minOffsetFromNode; // offsets edge from the centre of source node m_offsetFromTargetNode=target->size()+m_minOffsetFromNode; // offsets edge from the centre of target node m_arrowSize=arrowSize; angle = 0.0; m_srcAngle = 0.0; m_weight = weight ; // saves the weight/value of this edge if ( fabs(m_weight) > 1 ) { m_width = 1+log ( 1+ log(fabs(m_weight) )) ; } else { m_width = fabs(m_weight) ; } m_Bezier = bezier; // controls if it will appear as line or curve m_label = label; m_drawLabel = !m_label.isEmpty(); m_drawWeightNumber = weightNumbers; // controls if weight number will be shown // << source->nodeNumber() // << "->" // << target->nodeNumber() // <<" = " << m_weight // <<" label " << m_label // <<" edgeType " << m_edgeDirType; if (m_drawWeightNumber) { addWeightNumber(); } if (m_drawLabel) addLabel(); m_hoverHighlighting = highlighting; setAcceptHoverEvents(true); setFlags(QGraphicsItem::ItemIsSelectable); //Edges have lower z than nodes. Nodes always appear above edges. setZValue(ZValueEdge); setBoundingRegionGranularity(0); // Leave the default cache option (NoCache) // setCacheMode(QGraphicsItem::NoCache); rebuildPens(); adjust(); } /** * @brief Toggles displaying edge arrow * @param drawArrows */ void GraphicsEdge::showArrows(const bool &drawArrows){ m_drawArrows=drawArrows; update(); } void GraphicsEdge::toggleBezier(const bool &toggle) { m_Bezier = toggle; adjust(); } /** * @brief Fills the four cached arrow-corner QPointFs. * * Uses @c angle (curve tangent direction at @c targetPoint, in the sourceβ†’target * sense) for the destination arrowhead wings, and @c m_srcAngle (tangent * direction at @c sourcePoint) for the source arrowhead on undirected / * reciprocated edges. For straight edges @c m_srcAngle == @c angle; for * bezier curves they differ because the tangent varies along the arc. * * Called by @c adjust() and by @c setArrowSize() so @c paint() performs no * trigonometry of its own. */ void GraphicsEdge::computeArrowPoints() { if (!m_drawArrows || source == target || line_length <= 10) return; m_destArrowP1 = targetPoint + QPointF(sin(angle - M_PI_3) * m_arrowSize, cos(angle - M_PI_3) * m_arrowSize); m_destArrowP2 = targetPoint + QPointF(sin(angle - M_PI + M_PI_3) * m_arrowSize, cos(angle - M_PI + M_PI_3) * m_arrowSize); if (m_edgeDirType == EdgeType::Undirected || m_edgeDirType == EdgeType::Reciprocated) { m_srcArrowP1 = sourcePoint + QPointF(sin(m_srcAngle + M_PI_3) * m_arrowSize, cos(m_srcAngle + M_PI_3) * m_arrowSize); m_srcArrowP2 = sourcePoint + QPointF(sin(m_srcAngle + M_PI - M_PI_3) * m_arrowSize, cos(m_srcAngle + M_PI - M_PI_3) * m_arrowSize); } } /** * @brief Sets the arrow size for this edge. * Recalculates only the four cached arrow-corner points (no path rebuild) * then schedules a repaint. */ void GraphicsEdge::setArrowSize(const int &size) { prepareGeometryChange(); // bounding rect includes m_arrowSize padding m_arrowSize = size; computeArrowPoints(); update(); } int GraphicsEdge::arrowSize() const { return static_cast(m_arrowSize); } /** * @brief Removes any references to this edge in source and target nodes. * * Skipped during a bulk GraphicsWidget::clear(): source and target nodes are being * destroyed too, so unlinking this edge from their (std::list-backed, O(n) to erase from) * edge lists is wasted work. See #260. */ void GraphicsEdge::removeRefs(){ if (graphicsWidget->isClearing()) return; source->removeOutEdge(this); target->removeInEdge(this); } /** * @brief Sets the edge color * @param str */ void GraphicsEdge::setColor( const QString &str) { m_color=QColor(str); prepareGeometryChange(); rebuildPens(); } /** * @brief Returns the edge QColor. * @return */ QColor GraphicsEdge::color() const{ return m_color; } /** * @brief Returns the edge color in pajek-accepted format * * Called from Graph * * @return */ QString GraphicsEdge::colorToPajek() { QString m_colorStr = m_color.name(); if (m_colorStr.startsWith("#")) { return ("RGB"+m_colorStr.right( m_colorStr.size()-1 )).toUpper() ; } return m_colorStr; } /** * @brief Changes the edge weight - Updates both the width and the weightNumber * * Called from MW when user wants to change an edge's weight. * @param w */ void GraphicsEdge::setWeight(const qreal &w) { prepareGeometryChange(); m_weight = w; if ( fabs(m_weight) > 1 ) { m_width = 1+log ( 1+ log(fabs(m_weight) )) ; } else { m_width = fabs(m_weight) ; } rebuildPens(); if (m_drawWeightNumber) weightNumber->setPlainText (QString::number(w)); } /** * @brief Returns the weight/value of this edge * @return */ qreal GraphicsEdge::weight() const { return m_weight; } /** * @brief Adds a graphics edge weight to this edge */ void GraphicsEdge::addWeightNumber (){ // create edge weight item double x = -20 + ( source->x() + target->x() ) / 2.0; double y = -20 + ( source->y() + target->y() ) / 2.0; weightNumber = new GraphicsEdgeWeight (this, 7, QString::number(m_weight) ); weightNumber->setPos(x,y); weightNumber->setDefaultTextColor (m_color); m_drawWeightNumber = true; } /** * @brief Toggles visibility of weight numbers * @param toggle */ void GraphicsEdge::setWeightNumberVisibility (const bool &toggle) { if (m_drawWeightNumber) { if (toggle) weightNumber->show(); else weightNumber->hide(); } else{ if (toggle) addWeightNumber(); } } /** * @brief Changes the edge label. * * Called from MW when user wants to change an edge's label * * @param label */ void GraphicsEdge::setLabel(const QString &label) { prepareGeometryChange(); m_label = label; if (m_drawLabel) edgeLabel->setPlainText (m_label); } /** * @brief Returns the edge label text * @return QString */ QString GraphicsEdge::label() const { return m_label; } /** * @brief Adds a graphics edge label to this edge */ void GraphicsEdge::addLabel (){ // create edge label item double x = 5+ ( source->x() + target->x() ) / 2.0; double y = 5+ ( source->y() + target->y() ) / 2.0; edgeLabel = new GraphicsEdgeLabel (this, 7, m_label ); edgeLabel->setPos(x,y); edgeLabel->setDefaultTextColor (m_color); m_drawLabel = true; } /** * @brief Toggles the graphics edge label visibility * @param toggle */ void GraphicsEdge::setLabelVisibility (const bool &toggle) { if (m_drawLabel) { if (toggle) edgeLabel->show(); else edgeLabel->hide(); } else{ if (toggle) addLabel(); } } /** * @brief Returns the source node of this graphics edge * @return */ GraphicsNode *GraphicsEdge::sourceNode() const { return source; } /** * @brief Sets the source node of this graphics edge * @param node */ void GraphicsEdge::setSourceNode(GraphicsNode *node) { source = node; adjust(); } /** * @brief Called from graphicsNode to update edge offset from source node (i.e. when node size changes) * @param offset */ void GraphicsEdge::setSourceNodeSize(const int &size){ m_offsetFromSourceNode=size + m_minOffsetFromNode; adjust(); } /** * Returns the source node number * @return int */ int GraphicsEdge::sourceNodeNumber () { return source->nodeNumber(); } /** * @brief Returns the target node. * @return */ GraphicsNode *GraphicsEdge::targetNode() const { return target; } /** * @brief Sets the target node.s * @param node */ void GraphicsEdge::setTargetNode(GraphicsNode *node){ target = node; adjust(); } /** * @brief Called from graphicsNode to update edge offset from target node (i.e. when node size changes) * @param offset */ void GraphicsEdge::setTargetNodeSize(const int & size){ m_offsetFromTargetNode=size + m_minOffsetFromNode; adjust(); } /** * Returns the target node number * @return int */ int GraphicsEdge::targetNodeNumber() { return target->nodeNumber(); } /** * @brief Updates Minimum Offset From Node and calls adjust to update the edge * @param offset */ void GraphicsEdge::setMinimumOffsetFromNode(const int &offset) { m_minOffsetFromNode = offset; m_offsetFromTargetNode = target->size() + m_minOffsetFromNode; adjust(); } /** * @brief Returns the horizontal difference between target and source nodes. * @return */ qreal GraphicsEdge::dx() const { return target->x() - source->x(); } /** * @brief Returns the vertical difference between target and source nodes. * @return */ qreal GraphicsEdge::dy() const { return target->y() - source->y(); } /** * @brief Returns the euclidean length of the edge * @return */ qreal GraphicsEdge::length() const { return sqrt ( dx() * dx() + dy() * dy() ) ; } /** * @brief Recomputes all geometry for this edge. * * Called whenever a connected node moves or any geometric property changes * (node size, offset, bezier mode, etc.). Executes four steps in order: * * 1. Snapshots @c line_dx / @c line_dy / @c line_length from current node centres. * 2. Sets @c sourcePoint / @c targetPoint β€” offset from node centres along the * chord direction (straight mode) or along the curve tangent at each endpoint * (bezier mode), so arrowheads land on the node boundary in the correct direction. * 3. Rebuilds @c m_path: * - straight edge: @c lineTo() * - quadratic bezier arc: @c quadTo() with curvature capped at * min(40 px, line_length Γ— 0.3) to avoid extreme * bowing on short edges * - self-loop: @c cubicTo() with fixed Β±30 px control offsets * 4. Caches @c angle (tangent at @c targetPoint) and @c m_srcAngle (tangent at * @c sourcePoint), then calls @c computeArrowPoints() to fill the four cached * arrow-corner QPointFs. * * Arrow polygons are intentionally @b not added to @c m_path so that changing * @c m_arrowSize only requires recomputing four QPointFs and a repaint β€” * not a full path rebuild. */ void GraphicsEdge::adjust(){ if (!source || !target) { return; } line_length = length(); line_dx = dx(); line_dy = dy(); // Curvature for bezier mode: capped so short edges don't bow excessively. // Computed once here and reused for both endpoint offsetting and path ctrl. const qreal curvature = (m_Bezier && line_length > 0) ? qMin(40.0, line_length * 0.3) : 0.0; prepareGeometryChange(); if (source != target && line_length > 0) { if (!m_Bezier) { // Straight line: offset both endpoints along the chord direction. edgeOffset = QPointF((line_dx * m_offsetFromTargetNode) / line_length, (line_dy * m_offsetFromTargetNode) / line_length); sourcePoint = source->pos() + edgeOffset; targetPoint = target->pos() - edgeOffset; } else { // Bezier: offset endpoints along the curve's tangent at each end // so the arrowhead sits on the node boundary in the arrival direction. // Use an approximate ctrl from node centers to derive tangent vectors. QPointF srcPos = source->pos(); QPointF tgtPos = target->pos(); QPointF approxCtrl = (srcPos + tgtPos) / 2.0 + QPointF(-line_dy, line_dx) * (curvature / line_length); QPointF sTan = approxCtrl - srcPos; qreal sTLen = qSqrt(sTan.x()*sTan.x() + sTan.y()*sTan.y()); sourcePoint = (sTLen > 0) ? srcPos + sTan * (m_offsetFromSourceNode / sTLen) : srcPos; QPointF tTan = tgtPos - approxCtrl; qreal tTLen = qSqrt(tTan.x()*tTan.x() + tTan.y()*tTan.y()); targetPoint = (tTLen > 0) ? tgtPos - tTan * (m_offsetFromTargetNode / tTLen) : tgtPos; } } else { edgeOffset = QPointF(0, 0); sourcePoint = source->pos(); targetPoint = target->pos(); } if (m_drawWeightNumber) { weightNumber->setPos( -20 + (source->x()+target->x())/2.0, -20 + (source->y()+target->y())/2.0 ); } if (m_drawLabel) { edgeLabel->setPos( 5 + (source->x()+target->x())/2.0, 5 + (source->y()+target->y())/2.0 ); } QPainterPath path(sourcePoint); if (source != target) { if (!m_Bezier) { path.lineTo(targetPoint); if (line_length > 0) { angle = ::acos( line_dx / line_length ); if (line_dy >= 0) angle = M_PI_X_2 - angle; } m_srcAngle = angle; // chord is uniform β€” same angle at both ends } else { // Control point: midpoint of the offset endpoints shifted // curvature pixels perpendicular to the chord direction. QPointF mid = (sourcePoint + targetPoint) / 2.0; QPointF ctrl = mid + QPointF(-line_dy, line_dx) * (curvature / line_length); path.quadTo(ctrl, targetPoint); // Dest arrow: tangent at targetPoint = direction ctrl β†’ target. qreal tdx = targetPoint.x() - ctrl.x(); qreal tdy = targetPoint.y() - ctrl.y(); qreal tlen = qSqrt(tdx * tdx + tdy * tdy); if (tlen > 0) { angle = ::acos( tdx / tlen ); if (tdy >= 0) angle = M_PI_X_2 - angle; } // Src arrow: tangent at sourcePoint = direction source β†’ ctrl. qreal sdx = ctrl.x() - sourcePoint.x(); qreal sdy = ctrl.y() - sourcePoint.y(); qreal slen = qSqrt(sdx * sdx + sdy * sdy); if (slen > 0) { m_srcAngle = ::acos( sdx / slen ); if (sdy >= 0) m_srcAngle = M_PI_X_2 - m_srcAngle; } } } else { // self-loop QPointF c1 = QPointF( targetPoint.x() -30, targetPoint.y() -30 ); QPointF c2 = QPointF( targetPoint.x() +30, targetPoint.y() -30 ); path.cubicTo( c1, c2, targetPoint); } m_path = path; computeArrowPoints(); } /** * @brief Returns the shape of this edge as a QPainterPath in local coordinates. * The shape is used for many things, including collision detection, hit tests, * and for the QGraphicsScene::items() functions. * The default implementation calls boundingRect() to return a simple rectangular shape, * but we reimplement it to return a more accurate shape for non-rectangular items. * @return QPainterPath */ QPainterPath GraphicsEdge::shape () const { // QPainterPath m_path_shape = m_path; // m_path_shape.addPath(m_path.translated(1,1)); // m_path_shape.addPath(m_path.translated(-1,-1)); // return m_path_shape; return m_path; } /** * @brief Defines the outer bounds of the edge as a rectangle; * All painting will be restricted to inside the edge's bounding rect. * Qt uses this bounding rect to determine whether the edge requires redrawing. * @return */ QRectF GraphicsEdge::boundingRect() const { if (!source || !target) return QRectF(); // Pad by arrowSize so arrows drawn in paint() are never clipped. const qreal pad = m_arrowSize + m_width / 2.0 + 1.0; return m_path.controlPointRect().adjusted(-pad, -pad, pad, pad); } /** * @brief Changes the direction type of edge A->B */ void GraphicsEdge::setDirectionType(const int &dirType){ // << source->nodeNumber() // << "->" // << target->nodeNumber() // << "new direction type" // << dirType; prepareGeometryChange(); m_edgeDirType = dirType; m_drawArrows = true; if (m_edgeDirType==EdgeType::Undirected) { m_drawArrows = false; } adjust(); } /** * @brief returns the direction type of this edge * @return */ int GraphicsEdge::directionType() { return m_edgeDirType ; } /** * @brief Sets the PenStyle of this edge * @param style */ void GraphicsEdge::setStyle( const Qt::PenStyle &style ) { m_style = style; rebuildPens(); } /** * @brief Rebuilds the three cached QPens and the cached QBrush. * * Called from the constructor and from any setter that changes a pen-relevant * field (color, weight/width, style). This keeps paint() free of per-repaint * object construction. */ void GraphicsEdge::rebuildPens() { // Negative-weight edges are always drawn dashed regardless of m_style. const Qt::PenStyle regStyle = (m_weight < 0) ? Qt::DashLine : m_style; m_penRegular = QPen(m_color, m_width, regStyle, Qt::RoundCap, Qt::RoundJoin); m_penHighlight = QPen(QColor("red"), m_width, m_style, Qt::RoundCap, Qt::RoundJoin); m_penHover = QPen(QColor("red"), m_width + 1, m_style, Qt::RoundCap, Qt::RoundJoin); m_brush = QBrush(m_color); } /** * @brief Returns the PenStyle of this edge * @return */ Qt::PenStyle GraphicsEdge::style() const{ return m_style; } /** * @brief Returns the QPen for this edge -- the pen changes when the edge state changes/ * @return */ QPen GraphicsEdge::pen() const { switch (m_state) { case EDGE_STATE_REGULAR: if (m_weight < 0 ){ return QPen(m_color, m_width, Qt::DashLine, Qt::RoundCap, Qt::RoundJoin); } return QPen(m_color, m_width, m_style, Qt::RoundCap, Qt::RoundJoin); break; case EDGE_STATE_HIGHLIGHT: // selected return QPen( QColor("red"), m_width, m_style, Qt::RoundCap, Qt::RoundJoin); case EDGE_STATE_HOVER: // hover return QPen(QColor("red"), m_width+1, m_style, Qt::RoundCap, Qt::RoundJoin); default: return QPen(m_color, m_width, m_style, Qt::RoundCap, Qt::RoundJoin); } } /** * @brief Sets the edge state * @param state */ void GraphicsEdge::setState(const int &state) { //NOTE: DO NOT USE HERE: prepareGeometryChange() m_state=state; } /** * @brief Paints the edge onto the canvas. * * Draws the pre-built line/self-loop @c m_path, then draws the arrow * polygons from the cached corner points (@c m_destArrowP1/P2 and, for * undirected/reciprocated edges, @c m_srcArrowP1/P2). No trigonometry * is performed here β€” all geometry is pre-computed by @c adjust() and * @c computeArrowPoints(), so this method is as cheap as possible for * the common case of a static network. * * @param painter The QPainter provided by the scene. * @param option Style options (selection, hover state). */ void GraphicsEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *){ if (!source || !target) return; //painter->setClipRect(); //if the edge is being dragged around, darken it! if (option->state & QStyle::State_Selected) { //setZValue(ZValueEdgeHighlighted); setState(EDGE_STATE_HOVER); } else if (option->state & QStyle::State_MouseOver) { if (m_hoverHighlighting) { setZValue(ZValueEdgeHighlighted); setState(EDGE_STATE_HOVER); } } else if (m_state==EDGE_STATE_HIGHLIGHT){ if (m_hoverHighlighting) { setZValue(ZValueEdgeHighlighted); setState(EDGE_STATE_HIGHLIGHT); } } else { setZValue(ZValueEdge); setState(EDGE_STATE_REGULAR); } // Select cached pen for current state β€” no QPen construction per repaint. switch (m_state) { case EDGE_STATE_HIGHLIGHT: painter->setPen(m_penHighlight); break; case EDGE_STATE_HOVER: painter->setPen(m_penHover); break; default: painter->setPen(m_penRegular); break; } painter->setBrush(Qt::NoBrush); painter->drawPath(m_path); if (m_drawArrows && source != target && line_length > 10) { painter->setBrush(m_brush); painter->drawPolygon(QPolygonF() << targetPoint << m_destArrowP1 << m_destArrowP2); if (m_edgeDirType == EdgeType::Undirected || m_edgeDirType == EdgeType::Reciprocated) painter->drawPolygon(QPolygonF() << sourcePoint << m_srcArrowP1 << m_srcArrowP2); } } /** * @brief Called when the edge changes, i.e. moves, becomes disabled or changes its visibility * @param change * @param value * @return */ QVariant GraphicsEdge::itemChange(GraphicsItemChange change, const QVariant &value){ switch (change) { case ItemPositionHasChanged: { break; } case ItemEnabledHasChanged: { break; } case ItemSelectedHasChanged: { if (value.toBool()) { setZValue(ZValueEdgeHighlighted); setHighlighted(true); //source->setSelected(true); //target->setSelected(true); } else{ setZValue(ZValueEdge); setHighlighted(false); //source->setSelected(false); //target->setSelected(false); } break; } case ItemVisibleHasChanged: { break; } default: { break; } }; return QGraphicsItem::itemChange(change, value); } /** * @brief Returns the width of the edge as a function of edge weight * @return */ qreal GraphicsEdge::width() const{ return m_width; } /** * @brief Toggles the highlighted state of the the edge, if highlighting is allowed. * * Called from GraphicsNode when the user hovers over the node. * * @param flag */ void GraphicsEdge::setHighlighted(const bool &flag) { if (flag && m_hoverHighlighting) { prepareGeometryChange(); setState(EDGE_STATE_HIGHLIGHT); } else { prepareGeometryChange(); setState(EDGE_STATE_REGULAR); } } /** * @brief Toggles edge highlighting on or off * * If enabled, the edge can be highlighted. * * @param toggle */ void GraphicsEdge::setHighlighting(const bool &toggle) { m_hoverHighlighting = toggle; } ///** // * @brief handles the events of a click on an edge // * @param event // */ //void GraphicsEdge::mousePressEvent(QGraphicsSceneMouseEvent *e) { // //setClicked(); // QGraphicsItem::mousePressEvent(e); //} GraphicsEdge::~GraphicsEdge(){ // << sourceNodeNumber()<< "->" << targetNodeNumber() // << "will remove refs first..."; removeRefs(); if (m_drawWeightNumber) graphicsWidget->removeItem(weightNumber); if (m_drawLabel) graphicsWidget->removeItem(edgeLabel); this->hide(); graphicsWidget->removeItem(this); } SocNetV-3.7/src/graphicsedge.h000077500000000000000000000111311524402210300162620ustar00rootroot00000000000000/** * @file graphicsedge.h * @brief Declares the GraphicsEdge class for visualizing edges in the network graph. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef GRAPHICSEDGE_H #define GRAPHICSEDGE_H #include #include #include #include #include //declares pair construct class GraphicsWidget; class QGraphicsSceneMouseEvent; class GraphicsNode; class GraphicsEdgeWeight; class GraphicsEdgeLabel; QT_USE_NAMESPACE using namespace std; static const int TypeEdge= QGraphicsItem::UserType+2; static const int ZValueEdge = 50; static const int ZValueEdgeHighlighted = 99; static const int EDGE_STATE_REGULAR = 0; static const int EDGE_STATE_HIGHLIGHT = 1; static const int EDGE_STATE_HOVER = 2; class GraphicsEdge : public QObject, public QGraphicsItem { Q_OBJECT Q_INTERFACES (QGraphicsItem) public: GraphicsEdge(GraphicsWidget *, GraphicsNode*, GraphicsNode*, const qreal &weight, const QString &label, const QString &color, const Qt::PenStyle &style, const int&type, const bool & drawArrows, const bool &bezier, const bool &weightNumbers=false, const bool &highlighting=true, const int &arrowSize=6); ~GraphicsEdge(); enum { Type = UserType + 2 }; int type() const { return Type; } GraphicsNode *sourceNode() const; void setSourceNode(GraphicsNode *node); GraphicsNode *targetNode() const; void setTargetNode(GraphicsNode *node); int sourceNodeNumber(); int targetNodeNumber(); qreal dx() const; qreal dy() const; qreal length() const; void setSourceNodeSize(const int & size); void setTargetNodeSize(const int & size); void setMinimumOffsetFromNode(const int & offset); void removeRefs(); void setWeight( const qreal &w) ; qreal weight() const; void addWeightNumber (); //void deleteWeightNumber(); void setWeightNumberVisibility (const bool &toggle); void setLabel( const QString &label) ; QString label() const; void addLabel(); //void deleteLabel(); void setLabelVisibility (const bool &toggle); void showArrows(const bool &); void toggleBezier(const bool &toggle); void setArrowSize(const int &size); int arrowSize() const; void setDirectionType(const int &dirType=0); int directionType(); qreal width() const; QPen pen() const; void setState(const int &state); void setStyle( const Qt::PenStyle &style); Qt::PenStyle style() const; void setColor( const QString &str) ; //QString color() const ; QColor color() const; QString colorToPajek(); void setHighlighted (const bool &flag); void setHighlighting (const bool &toggle); QPainterPath shape() const; public slots: void adjust (); protected: QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); QVariant itemChange(GraphicsItemChange change, const QVariant &value); // void mousePressEvent(QGraphicsSceneMouseEvent *event); private: GraphicsWidget *graphicsWidget; GraphicsNode *source, *target; GraphicsEdgeWeight* weightNumber; GraphicsEdgeLabel* edgeLabel; QPainterPath m_path; QPointF sourcePoint, targetPoint; QPointF edgeOffset; // Cached arrow corner points β€” computed in adjust() and recomputed in // setArrowSize() so paint() needs no trig of its own. QPointF m_destArrowP1, m_destArrowP2; QPointF m_srcArrowP1, m_srcArrowP2; qreal m_arrowSize; qreal m_minOffsetFromNode; qreal m_offsetFromTargetNode, m_offsetFromSourceNode; Qt::PenStyle m_style; int m_state; QString m_colorNegative, m_label; QColor m_color; qreal m_weight, m_width; int m_edgeDirType; qreal angle, m_srcAngle, line_length, line_dx, line_dy; bool m_Bezier, m_drawArrows, m_drawWeightNumber; bool m_drawLabel, m_hoverHighlighting; bool m_isClicked; void computeArrowPoints(); void rebuildPens(); // Cached pens β€” rebuilt in rebuildPens(), selected in paint() by m_state. QPen m_penRegular; QPen m_penHighlight; QPen m_penHover; QBrush m_brush; }; #endif SocNetV-3.7/src/graphicsedgelabel.cpp000077500000000000000000000022541524402210300176230ustar00rootroot00000000000000/** * @file graphicsedgelabel.cpp * @brief Implements the GraphicsEdgeLabel class for rendering edge labels in the network graph. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graphicsedgelabel.h" #include "graphicsedge.h" #include "graphicswidget.h" #include #include GraphicsEdgeLabel::GraphicsEdgeLabel( GraphicsEdge *link , int size, QString labelText) : QGraphicsTextItem( 0) { qCDebug(lcGW)<< "GraphicsEdgeLabel:: creating new edgelabel and attaching it to link"; setPlainText( labelText ); setParentItem(link); //auto disables child items like this, when link is disabled. this->setFont( QFont ("Courier", size, QFont::Light, true) ); setZValue(ZValueEdgeLabel); } GraphicsEdgeLabel::~GraphicsEdgeLabel() { } SocNetV-3.7/src/graphicsedgelabel.h000077500000000000000000000020671524402210300172720ustar00rootroot00000000000000/** * @file graphicsedgelabel.h * @brief Declares the GraphicsEdgeLabel class for managing and rendering edge labels in the network graph. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef GRAPHICSEDGELABEL_H #define GRAPHICSEDGELABEL_H #include class GraphicsEdge; static const int TypeEdgeLabel = QGraphicsItem::UserType+6; static const int ZValueEdgeLabel = 80; class GraphicsEdgeLabel: public QGraphicsTextItem { public: GraphicsEdgeLabel(GraphicsEdge * , int, QString); void removeRefs(); enum { Type = UserType + 6 }; int type() const { return Type; } ~GraphicsEdgeLabel(); private: }; #endif SocNetV-3.7/src/graphicsedgeweight.cpp000077500000000000000000000023041524402210300200270ustar00rootroot00000000000000/** * @file graphicsedgeweight.cpp * @brief Implements the GraphicsEdgeWeight class for managing and rendering edge weights in the network graph. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graphicsedgeweight.h" #include "graphicsedge.h" #include "graphicswidget.h" #include #include GraphicsEdgeWeight::GraphicsEdgeWeight( GraphicsEdge *link , int size, QString labelText) : QGraphicsTextItem( 0) { qCDebug(lcGW)<< "GraphicsEdgeWeight:: creating new edgeweight and attaching it to link"; setPlainText( labelText ); setParentItem(link); //auto disables child items like this, when link is disabled. this->setFont( QFont ("Courier", size, QFont::Light, true) ); setZValue(ZValueEdgeWeight); } GraphicsEdgeWeight::~GraphicsEdgeWeight() { } SocNetV-3.7/src/graphicsedgeweight.h000077500000000000000000000021011524402210300174670ustar00rootroot00000000000000/** * @file graphicsedgeweight.h * @brief Declares the GraphicsEdgeWeight class for managing and rendering edge weights in the network graph. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef GRAPHICSEDGEWEIGHT_H #define GRAPHICSEDGEWEIGHT_H #include class GraphicsEdge; static const int TypeEdgeWeight = QGraphicsItem::UserType+5; static const int ZValueEdgeWeight = 80; class GraphicsEdgeWeight: public QGraphicsTextItem { public: GraphicsEdgeWeight(GraphicsEdge * , int, QString); void removeRefs(); enum { Type = UserType + 5 }; int type() const { return Type; } ~GraphicsEdgeWeight(); private: }; #endif SocNetV-3.7/src/graphicsguide.cpp000077500000000000000000000050051524402210300170110ustar00rootroot00000000000000/** * @file graphicsguide.cpp * @brief Implements the GraphicsGuide class for drawing guides in the network graph visualization. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graphicsguide.h" #include "graphicswidget.h" GraphicsGuide::GraphicsGuide ( GraphicsWidget *gw, const double &x0, const double &y0, const double &radius ) : graphicsWidget ( gw ){ graphicsWidget->scene()->addItem ( this ); m_radius=radius; setZValue(ZValueGuide); circle=true; setPos(x0, y0); } GraphicsGuide::GraphicsGuide ( GraphicsWidget *gw, const double &y0, const int &width) : graphicsWidget ( gw ){ graphicsWidget->scene()->addItem ( this ); setPos(0, y0); m_width= width; setZValue(ZValueGuide); circle=false; } double GraphicsGuide::radius() { return m_radius; } bool GraphicsGuide::isCircle() { return (circle); } void GraphicsGuide::setCircle(const QPointF ¢er, const double &radius ) { setPos(center); m_radius=radius; circle = true; update(); } void GraphicsGuide::setHorizontalLine(const QPointF &origin, const int &width){ setPos(origin); m_width= width; circle=false; update(); } int GraphicsGuide::width() { return m_width; } /** Returns the bounding rectangle of the background circle*/ QRectF GraphicsGuide::boundingRect() const { if (circle) { return QRectF ( - m_radius-1, - m_radius-1, + 2 * m_radius + 1, + 2* m_radius +1 ); } else { return QRectF ( 1, -1, m_width, + 1 ); } } void GraphicsGuide::paint ( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * ){ Q_UNUSED(option); painter->setPen ( QPen ( QColor ( "red" ), 1, Qt::DotLine ) ); if (circle) { painter->drawEllipse ( QPointF(0,0), m_radius, m_radius ); } else { painter->drawLine ( 0 , 0, m_width , 0); } } void GraphicsGuide::die (){ this->prepareGeometryChange(); this->hide(); this->update(); graphicsWidget->scene()->removeItem(this); this->update(); } SocNetV-3.7/src/graphicsguide.h000077500000000000000000000031461524402210300164620ustar00rootroot00000000000000/** * @file graphicsguide.h * @brief Declares the GraphicsGuide class for drawing guides in the network graph visualization. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef GRAPHICSGUIDE_H #define GRAPHICSGUIDE_H #include #include class GraphicsWidget; static const int TypeGuide = QGraphicsItem::UserType+7; static const int ZValueGuide = 10; class GraphicsGuide : public QObject, public QGraphicsItem { Q_OBJECT Q_INTERFACES (QGraphicsItem) public: GraphicsGuide(GraphicsWidget *, const double &x0, const double &y0, const double &radius ); GraphicsGuide(GraphicsWidget *, const double &y0, const int &width); bool isCircle(); void setCircle(const QPointF ¢er, const double &radius) ; void setHorizontalLine(const QPointF &origin, const int &width) ; double radius(); int width(); enum { Type = UserType + 7 }; int type() const { return Type; } void die(); protected: QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); private: GraphicsWidget *graphicsWidget; double m_radius; int m_width; bool circle; }; #endif SocNetV-3.7/src/graphicsnode.cpp000077500000000000000000000567521524402210300166600ustar00rootroot00000000000000/** * @file graphicsnode.cpp * @brief Implements the GraphicsNode class for rendering nodes in the network graph visualization. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graphicsnode.h" #include #include #include #include #include #include #include "graphicswidget.h" #include "graphicsedge.h" #include "graphicsnodelabel.h" #include "graphicsnodenumber.h" /** * @brief Constructs a new node object (which is a graphics item) * * @param gw * @param num * @param size * @param color * @param shape * @param iconPath * @param showNumbers * @param numbersInside * @param numberColor * @param numberSize * @param numDistance * @param showLabels * @param label * @param labelColor * @param labelSize * @param labelDistance * @param edgeHighlighting * @param p */ GraphicsNode::GraphicsNode ( GraphicsWidget* gw, const int &num, const int &size, const QString &color, const QString &shape, const QString &iconPath, const bool &showNumbers, const bool &numbersInside, const QString &numberColor, const int &numberSize, const int &numDistance, const bool &showLabels, const QString &label, const QString &labelColor, const int &labelSize, const int &labelDistance, const bool &edgeHighlighting, QPointF p ) : graphicsWidget (gw) { graphicsWidget->scene()->addItem(this); // Without this nodes don't appear on the screen... setFlags(ItemSendsGeometryChanges | ItemIsSelectable | ItemIsMovable ); // Leave the default cache option (NoCache) // setCacheMode(QGraphicsItem::NoCache); setAcceptHoverEvents(true); m_num=num; m_size=size; m_shape=shape; m_iconPath = iconPath; m_col_orig=m_col=QColor(color); m_col_outline = QColor(0,0,0,50); m_colHover = m_col.darker(120); m_penOutline = QPen(m_col_outline, 0); m_hasNumber=showNumbers; m_hasNumberInside = numbersInside; m_numSize = numberSize; m_numColor = numberColor; m_numberDistance=numDistance; m_hasLabel=showLabels; m_labelText = label; m_labelSize = labelSize; m_labelColor = labelColor; m_labelDistance=labelDistance; if (m_hasLabel) { addLabel(); } if (!m_hasNumberInside && m_hasNumber) { addNumber(); } m_edgeHighLighting = edgeHighlighting; setZValue(ZValueNode); setShape(m_shape,m_iconPath); setPos(p); qCDebug(lcGW)<< "Constructed new node" << nodeNumber() << "at pos:" << x()<<"x"<setTargetNodeSize(size); } for (GraphicsEdge *edge: outEdgeList) { edge->setSourceNodeSize(size); } setShape(m_shape); } /** * @brief Returns the esoteric size of the node. * * @return */ int GraphicsNode::size() const{ return m_size; } /** * @brief Sets the shape of the node and prepares the corresponding QPainterPath * m_path which will be drawn by our painter (see paint()). * * The only exception is when the shape is 'custom'. In that case, the painter * will paint a pixmap with the custom node icon (loaded from iconPath). * However, even in that case we are still creating a QPainterPath, because this * is needed by QGraphicsNode::shape() function which is responsible for collision * detection and needs to return a path with an accurate outline of the item's shape. * Called every time the user needs to change the shape of an node. * * @param shape * @param iconPath */ void GraphicsNode::setShape(const QString shape, const QString &iconPath) { prepareGeometryChange(); m_shape=shape; QPainterPath path; if ( m_shape == "circle") { path.addEllipse (-m_size, -m_size, 2*m_size, 2*m_size); } else if ( m_shape == "ellipse") { path.addEllipse(-m_size, -m_size, 2*m_size, 1.7* m_size); } else if ( m_shape == "box" || m_shape == "rectangle" || m_shape == "square" ) { //rectangle: for GraphML/GML compliance path.addRect (-m_size , -m_size , 1.8*m_size , 1.8*m_size ); } else if (m_shape == "roundrectangle" ) { //roundrectangle: GraphML only path.addRoundedRect (-m_size , -m_size , 1.8*m_size , 1.8*m_size, 60.0, 60.0, Qt::RelativeSize ); } else if ( m_shape == "triangle") { path.moveTo(-m_size,0.95* m_size) ; path.lineTo(m_size,0.95*m_size); path.lineTo( 0,-1*m_size); path.lineTo(-m_size,0.95*m_size) ; path.closeSubpath(); } else if ( m_shape == "star") { path.setFillRule(Qt::WindingFill); path.moveTo(-0.8*m_size,0.6* m_size) ; path.lineTo(+0.8*m_size,0.6*m_size); path.lineTo( 0,-1*m_size); path.lineTo(-0.8*m_size,0.6*m_size) ; path.closeSubpath(); path.moveTo(0, 1* m_size) ; path.lineTo(+0.8*m_size,-0.6*m_size); path.lineTo(-0.8*m_size,-0.6*m_size) ; path.lineTo(0, 1* m_size); path.closeSubpath(); } else if ( m_shape == "diamond"){ path.moveTo(-m_size, 0); path.lineTo( 0,-1*m_size); path.lineTo( m_size, 0); path.lineTo( 0, 1*m_size); path.lineTo(-m_size, 0) ; path.closeSubpath(); } else if ( m_shape == "custom" ) { path.addRect (-m_size , -m_size , 2*m_size , 2*m_size ); if (!iconPath.isEmpty()) { m_iconPath = iconPath; } } else if (m_shape == "bugs" || m_shape == "heart" || m_shape == "dice" || m_shape == "person" || m_shape == "person-b" ) { path.addRect (-m_size , -m_size , 2*m_size , 2*m_size ); // we update iconPath only if it's not empty // this is to allow internal GraphicsNode methods to call us // without always passing the current iconPath again and again. if (!iconPath.isEmpty()) { m_iconPath = iconPath; } else { if ( m_shape == "person" ) { m_iconPath = ":/images/person.svg"; } if ( m_shape == "bugs" ) { m_iconPath = ":/images/bugs.png"; } else if ( m_shape == "heart") { m_iconPath = ":/images/heart.svg"; } else if ( m_shape == "dice" ) { m_iconPath = ":/images/random.png"; } } } else { // If shape is not supported, we draw a circle. path.addEllipse (-m_size, -m_size, 2*m_size, 2*m_size); } m_path = path; // Cache the pixmap for icon-based shapes so paint() does no file I/O. if (m_shape == "custom" || m_shape == "person" || m_shape == "person-b" || m_shape == "bugs" || m_shape == "heart" || m_shape == "dice") { QPixmap raw(m_iconPath); // For user-supplied custom images, scale to fit the bounding box while preserving // aspect ratio so non-square images are not distorted. Built-in icons are already // square, so this is a no-op for them in practice. m_pixmap = raw.scaled(2*m_size, 2*m_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { m_pixmap = QPixmap(); } update(); } /** * @brief Returns the shape of the node as a path in local coordinates. * * The shape is used for many things, including collision detection, hit tests, * and for the QGraphicsScene::items() functions. * We could ommit reimplementing this and have the default QGraphicsItem::shape() * return a simple rectangular shape through boundingRect() but we opt to return * an accurate outline of the item's shape. * * @return */ QPainterPath GraphicsNode::shape() const { return (m_path); } /** * @brief Returns the bounding rectangle of the node. * * The bounding rectangle is the area where all painting will take place. * * @return */ QRectF GraphicsNode::boundingRect() const { return m_path.controlPointRect(); //qreal adjust = 5; // return QRectF(-m_size -adjust , -m_size-adjust , 2*m_size+adjust , 2*m_size +adjust); } /** * @brief Does the actual painting using the QPainterPath created by the setShape() * * Called by GraphicsView and GraphicsNode methods in every update() * * @param painter * @param option */ void GraphicsNode::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) { // painter->setClipRect( option->exposedRect ); if (option->state & QStyle::State_MouseOver) { painter->setBrush(m_colHover); setZValue(ZValueNodeHighlighted); } else { painter->setBrush(m_col); setZValue(ZValueNode); } if (m_shape == "custom") { // Draw centered within the bounding box; m_pixmap was pre-scaled with KeepAspectRatio // so its dimensions may be smaller than 2*m_size on one axis. painter->drawPixmap(-m_pixmap.width()/2, -m_pixmap.height()/2, m_pixmap); } else if ( m_shape == "person" || m_shape == "person-b" || m_shape == "bugs" || m_shape == "heart" || m_shape == "dice" ) { // QImage image(m_iconPath); // painter->drawImage(QRectF(-m_size, -m_size, 2*m_size, 2*m_size) , image); painter->drawPixmap(-m_size, -m_size, 2*m_size, 2*m_size, m_pixmap); } else { painter->setPen(m_penOutline); painter->drawPath (m_path); } //@TODO FIX NUMBER SIZE WHEN TOGGLING IN/OUT OF NODE SHAPE if (m_hasNumberInside && m_hasNumber) { // m_path->setFillRule(Qt::WindingFill); painter->setPen(QPen(QColor(m_numColor), 0)); if (m_num > 999) { painter->setFont(QFont("Sans Serif", (m_numSize)? m_numSize-1: 0.4*m_size, QFont::Normal)); painter->drawText(-0.8*m_size,m_size/3, QString::number(m_num) ); } else if (m_num > 99) { painter->setFont(QFont("Sans Serif", (m_numSize)? m_numSize-1: 0.5*m_size, QFont::Normal)); painter->drawText(-0.6 * m_size,m_size/3, QString::number(m_num) ); } else if (m_num > 9 ) { painter->setFont(QFont("Sans Serif", (m_numSize)? m_numSize: 0.66*m_size, QFont::Normal)); painter->drawText(-0.5*m_size,m_size/3,QString::number(m_num) ); } else { painter->setFont(QFont("Sans Serif", (m_numSize)? m_numSize: 0.66*m_size, QFont::Normal)); painter->drawText(-0.33*m_size,m_size/3,QString::number(m_num) ); } } } /** * @brief Called when the node changes, i.e. moves, becomes disabled or changes its visibility * Propagates the changes to connected elements, i.e. edges, numbers, etc. * Checks if the node is inside the scene. * @param change * @param value * @return */ QVariant GraphicsNode::itemChange(GraphicsItemChange change, const QVariant &value) { switch (change) { case ItemPositionHasChanged: { //setCacheMode( QGraphicsItem::ItemCoordinateCache ); for (GraphicsEdge *edge: inEdgeList) //Move each inEdge of this node edge->adjust(); for (GraphicsEdge *edge: outEdgeList) //Move each outEdge of this node edge->adjust(); //Move its graphic number if ( m_hasNumber ) { if (!m_hasNumberInside) { //move it outside m_number->setZValue(ZValueNodeNumber); m_number->setPos( m_size+m_numberDistance, 0); } } if (m_hasLabel) { m_label->setPos( -m_size, m_labelDistance+m_size); } break; } case ItemEnabledHasChanged:{ break; } case ItemSelectedHasChanged:{ if (value.toBool()) { setZValue(ZValueNodeHighlighted); int origSize = m_size; setSize(m_size * 2 - 1); // setSize() just overwrote m_size_orig with the enlarged size (since // isSelected() is already true here) - restore the real original so // deselect doesn't restore into the enlarged size and compound on // every select/deselect cycle. m_size_orig = origSize; m_col_orig = m_col; setColor(m_col.darker(120)); if (m_edgeHighLighting) { for (GraphicsEdge *edge: inEdgeList) edge->setHighlighted(true); for (GraphicsEdge *edge: outEdgeList) edge->setHighlighted(true); } } else{ setZValue(ZValueNode); setSize(m_size_orig); setColor(m_col_orig); if (m_edgeHighLighting) { for (GraphicsEdge *edge: inEdgeList) edge->setHighlighted(false); for (GraphicsEdge *edge: outEdgeList) edge->setHighlighted(false); } } break; } case ItemVisibleHasChanged: { break; } default: { break; } }; return QGraphicsItem::itemChange(change, value); } //void GraphicsNode::mousePressEvent(QGraphicsSceneMouseEvent *event) { // QGraphicsItem::mousePressEvent(event); //} //void GraphicsNode::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { // update(); // QGraphicsItem::mouseReleaseEvent(event); //} ///** // * @brief GraphicsNode::hoverEnterEvent // * on hover on node, it highlights all connected edges // * @param event // */ //void GraphicsNode::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { // QGraphicsItem::hoverEnterEvent(event); //} ///** // * @brief GraphicsNode::hoverLeaveEvent // * Stops the connected edges highlighting // * @param event // */ //void GraphicsNode::hoverLeaveEvent(QGraphicsSceneHoverEvent *event){ // QGraphicsItem::hoverLeaveEvent(event); //} void GraphicsNode::setEdgeHighLighting(const bool &toggle) { m_edgeHighLighting = toggle; } /** * @brief Adds an inbound edge to this node * * Called from a new connected in-link to acknowloedge itself to this node. * * @param edge */ void GraphicsNode::addInEdge( GraphicsEdge *edge ) { inEdgeList.push_back( edge); } /** * @brief Removes the inbound edge from this node * @param link */ void GraphicsNode::removeInEdge( GraphicsEdge *edge ){ // << "Removing inEdge"<< nodeNumber() << "<-" << edge->sourceNodeNumber() // << "inEdgeList size:" << inEdgeList.size(); inEdgeList.remove( edge ); } /** * @brief Adds a outbound edge to this node * * Called from the edge itself. * * @param edge */ void GraphicsNode::addOutEdge( GraphicsEdge *edge ) { outEdgeList.push_back(edge); } /** * @brief Removes an outbound edge from this node * * @param edge */ void GraphicsNode::removeOutEdge(GraphicsEdge *edge){ // << "Removing outEdge"<< nodeNumber() << "->" << edge->targetNodeNumber() // << "outEdgeList size:" << outEdgeList.size(); outEdgeList.remove(edge); } /** * @brief Creates a graphics label to this node. */ void GraphicsNode::addLabel () { m_label = new GraphicsNodeLabel (this, m_labelText, m_labelSize); m_label->setDefaultTextColor (m_labelColor); m_label->setPos( m_size, m_labelDistance+m_size); m_hasLabel = true; } /** * @brief Returns the graphics label of the node * @return */ GraphicsNodeLabel* GraphicsNode::label(){ if (!m_hasLabel) { addLabel(); } return m_label; } /** * @brief Deletes the graphics label of this node. */ void GraphicsNode::deleteLabel(){ if (m_hasLabel) { m_hasLabel=false; m_label->hide(); graphicsWidget->removeItem(m_label); } } /** * @brief Sets the label of this node * * @param label */ void GraphicsNode::setLabelText (const QString &label) { prepareGeometryChange(); m_labelText = label; if (m_hasLabel) m_label->setPlainText(label); else addLabel(); m_hasLabel=true; } /** * @brief Returns the label of the node * @return QString */ QString GraphicsNode::labelText ( ) { return m_labelText; } /** * @brief Sets the color of the node label. * @param color */ void GraphicsNode::setLabelColor ( const QString &color) { prepareGeometryChange(); m_labelColor= color; if (m_hasLabel) m_label->setDefaultTextColor(color); else addLabel(); m_hasLabel=true; } /** * @brief Toggles the visibiity of the node label * @param toggle */ void GraphicsNode::setLabelVisibility(const bool &toggle) { if (toggle){ if (m_hasLabel) { m_label->show(); } else { addLabel(); } } else { if (m_hasLabel) { m_label->hide(); } } m_hasLabel=toggle; } /** * @brief Sets the size of the node label. * @param size */ void GraphicsNode::setLabelSize(const int &size) { m_labelSize = size; if (!m_hasLabel) { addLabel(); } m_label->setSize(m_labelSize); } /** * @brief Sets the distance of the label from the node. * @param distance */ void GraphicsNode::setLabelDistance(const int &distance) { m_labelDistance = distance; if (!m_hasLabel) { addLabel(); } m_label->setPos( -m_size, m_size+m_labelDistance);; } /** * @brief Adds a graphics number to the node */ void GraphicsNode::addNumber () { m_hasNumber=true; m_hasNumberInside = false; m_number= new GraphicsNodeNumber ( this, QString::number(m_num), m_numSize); m_number->setDefaultTextColor (m_numColor); m_number->setPos(m_size+m_numberDistance, 0); } /** * @brief Returns the graphics node number * @return */ GraphicsNodeNumber* GraphicsNode::number(){ return m_number; } /** * @brief Deletes the graphics node number */ void GraphicsNode::deleteNumber( ){ if (m_hasNumber && !m_hasNumberInside) { m_number->hide(); graphicsWidget->removeItem(m_number); m_hasNumber=false; } } /** * @brief Toggles the visibility of graphics node number * @param toggle */ void GraphicsNode::setNumberVisibility(const bool &toggle) { if (toggle) { //show if (!m_hasNumber) { m_hasNumber=toggle; if ( !m_hasNumberInside ) addNumber(); else { setShape(m_shape); } } } else { // hide deleteNumber(); m_hasNumber=toggle; setShape(m_shape); } } /** * @brief Toggles displaying node number inside the node * @param toggle */ void GraphicsNode::setNumberInside (const bool &toggle){ if (toggle) { // set number inside deleteNumber(); } else { addNumber(); } m_hasNumber = true; m_hasNumberInside = toggle; setShape(m_shape); } /** * @brief GraphicsNode::setNumberSize * @param size */ void GraphicsNode::setNumberSize(const int &size) { m_numSize = size; if (m_hasNumber && !m_hasNumberInside) { m_number->setSize(m_numSize); } else if (m_hasNumber && m_hasNumberInside) { setShape(m_shape); } else { // create a nodeNumber ? } } /** * @brief GraphicsNode::setNumberColor * @param color */ void GraphicsNode::setNumberColor(const QString &color) { m_numColor = color; if (m_hasNumber){ if (m_hasNumberInside) { setShape(m_shape); } else { m_number->setDefaultTextColor (m_numColor); } } } /** * @brief GraphicsNode::setNumberDistance * @param distance */ void GraphicsNode::setNumberDistance(const int &distance) { m_numberDistance = distance; if (m_hasNumber && !m_hasNumberInside) { m_number->setPos( m_size+m_numberDistance, 0); } else if (m_hasNumber && m_hasNumberInside) { // do nothing } else { // create a nodeNumber ? } } GraphicsNode::~GraphicsNode(){ // Skipped during a bulk GraphicsWidget::clear(): every edge is itself a top-level // scene item, so scene()->clear() destroys it directly regardless of what happens // here. Manually deleting it early would leave a dangling pointer in the *other* // endpoint's edge list (GraphicsEdge::removeRefs() is a no-op while isClearing(), // for the same reason), causing that node's destructor to double-delete it later. // See #260. if (!graphicsWidget->isClearing()) { // Temp copy in edges list temp_inEdgeList = inEdgeList; // Loop over the temp list and call delete for each edge // that will call the edge destructor for (GraphicsEdge *edge: temp_inEdgeList) { delete edge; } // Temp copy out edges list temp_outEdgeList = outEdgeList; for (GraphicsEdge *edge: temp_outEdgeList) { delete edge; } } if ( m_hasNumber ) deleteNumber(); if ( m_hasLabel ) deleteLabel(); inEdgeList.clear(); outEdgeList.clear(); this->hide(); graphicsWidget->removeItem(this); } SocNetV-3.7/src/graphicsnode.h000077500000000000000000000114201524402210300163040ustar00rootroot00000000000000/** * @file graphicsnode.h * @brief Declares the GraphicsNode class for rendering nodes in the network graph visualization. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef GRAPHICSNODE_H #define GRAPHICSNODE_H #include #include #include #include #include class GraphicsWidget; class QGraphicsSceneMouseEvent; class GraphicsEdge; class GraphicsNodeLabel; class GraphicsNodeNumber; using namespace std; static const int TypeNode = QGraphicsItem::UserType+1; static const int ZValueNode = 100; static const int ZValueNodeHighlighted = 110; /** * This is actually a container-class. * Contains the graphical objects called Nodes, * which are displayed as triangles, boxes, circles, etc, on the GraphicsWidget. * Each node "knows" the others with which she is connected. */ // class GraphicsNode : public QObject, public QGraphicsItem { Q_OBJECT Q_INTERFACES (QGraphicsItem) public: GraphicsNode ( GraphicsWidget* gw, const int &num, const int &size, const QString &color, const QString &shape, const QString &iconPath, const bool &showNumbers, const bool &numbersInside, const QString &numberColor, const int &numberSize, const int &numDistance, const bool &showLabels, const QString &label, const QString &labelColor, const int &labelSize, const int &labelDistance, const bool &edgeHighlighting, QPointF p ); ~GraphicsNode(); enum { Type = UserType + 1 }; int type() const { return Type; } QRectF boundingRect() const; QPainterPath shape() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); int nodeNumber() {return m_num;} void addInEdge( GraphicsEdge *edge ) ; void removeInEdge(GraphicsEdge*); void addOutEdge( GraphicsEdge *edge ) ; void removeOutEdge(GraphicsEdge*); void setSize(const int &); int size() const; void setShape (const QString, const QString &iconPath=QString()); QString nodeShape() {return m_shape;} void setColor(const QString &colorName); void setColor(QColor color); QString color (); void addLabel(); GraphicsNodeLabel* label(); void deleteLabel(); void setLabelVisibility(const bool &toggle); void setLabelSize(const int &size); void setLabelText ( const QString &label) ; void setLabelColor (const QString &color) ; QString labelText(); void setLabelDistance(const int &distance); void addNumber () ; GraphicsNodeNumber* number(); void deleteNumber(); void setNumberVisibility(const bool &toggle); void setNumberInside(const bool &toggle); void setNumberSize(const int &size); void setNumberDistance(const int &distance); void setNumberColor(const QString &color); void setEdgeHighLighting(const bool &toggle) ; protected: QVariant itemChange(GraphicsItemChange change, const QVariant &value); // void mousePressEvent(QGraphicsSceneMouseEvent *event); // void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); // void hoverEnterEvent(QGraphicsSceneHoverEvent *event); // void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); signals: void nodeClicked(GraphicsNode*); private: GraphicsWidget *graphicsWidget; QPainterPath m_path; QPointF newPos; QPolygonF *m_poly_t; int m_num; int m_size, m_size_orig; int m_state; int m_numSize; int m_labelSize; int m_numberDistance; int m_labelDistance; QString m_shape, m_iconPath; QString m_numColor; QColor m_col, m_col_orig; QColor m_col_outline; QColor m_colHover; // m_col.darker(120); rebuilt in setColor() QPen m_penOutline; // QPen(m_col_outline, 0); built once in constructor QPixmap m_pixmap; // loaded in setShape() for icon-based shapes QString m_labelText, m_labelColor; bool m_hasNumber, m_hasLabel, m_hasNumberInside, m_edgeHighLighting; /**Lists of elements attached to this node */ list inEdgeList, outEdgeList; GraphicsNodeLabel *m_label; GraphicsNodeNumber *m_number; }; #endif SocNetV-3.7/src/graphicsnodelabel.cpp000077500000000000000000000026751524402210300176530ustar00rootroot00000000000000/** * @file graphicsnodelabel.cpp * @brief Implements the GraphicsNodeLabel class for rendering labels associated with nodes in the network graph visualization. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graphicsnodelabel.h" #include "graphicsnode.h" #include "graphicswidget.h" #include #include GraphicsNodeLabel::GraphicsNodeLabel(GraphicsNode *jim , const QString &text, const int &size) : QGraphicsTextItem(jim) { source=jim; setParentItem(jim); //auto disables child items like this, when node is disabled. setPlainText( text ); setFont( QFont ("Times", size, QFont::Light, true) ); setZValue(ZValueNodeLabel); setAcceptHoverEvents(false); qCDebug(lcGW) << "GraphicsNodeLabel() - initialized"; } void GraphicsNodeLabel::setSize(const int &size) { prepareGeometryChange(); setFont( QFont ("Times", size, QFont::Black, false) ); //update(); } void GraphicsNodeLabel::removeRefs(){ source->deleteLabel(); } GraphicsNodeLabel::~GraphicsNodeLabel(){ } SocNetV-3.7/src/graphicsnodelabel.h000077500000000000000000000022731524402210300173120ustar00rootroot00000000000000/** * @file graphicsnodelabel.h * @brief Declares the GraphicsNodeLabel class for rendering labels associated with nodes in the network graph visualization. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef GRAPHICSNODELABEL_H #define GRAPHICSNODELABEL_H #include class GraphicsNode; static const int TypeLabel = QGraphicsItem::UserType+4; static const int ZValueNodeLabel = 80; class GraphicsNodeLabel : public QGraphicsTextItem{ public: GraphicsNodeLabel(GraphicsNode * , const QString &text, const int &size ); void removeRefs(); enum { Type = UserType + 4 }; int type() const { return Type; } void setSize(const int &size); ~GraphicsNodeLabel(); GraphicsNode* node() { return source; } private: GraphicsNode *source; }; #endif SocNetV-3.7/src/graphicsnodenumber.cpp000077500000000000000000000027021524402210300200530ustar00rootroot00000000000000/** * @file graphicsnodenumber.cpp * @brief Implements the GraphicsNodeNumber class for rendering node numbers in the network graph visualization. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graphicsnodenumber.h" #include "graphicsnode.h" #include "graphicswidget.h" #include #include GraphicsNodeNumber::GraphicsNodeNumber( GraphicsNode *jim , const QString &labelText, const int &size) :QGraphicsTextItem(jim) { source=jim; setParentItem(jim); //auto disables child items like this, when node is disabled. setPlainText( labelText ); setFont( QFont ("Times", size, QFont::Black, false) ); setZValue(ZValueNodeNumber); setAcceptHoverEvents(false); qCDebug(lcGW) << "GraphicsNodeNumber() - initialized"; } void GraphicsNodeNumber::setSize(const int size) { prepareGeometryChange(); setFont( QFont ("Times", size, QFont::Black, false) ); //update(); } void GraphicsNodeNumber::removeRefs(){ source->deleteNumber(); } GraphicsNodeNumber::~GraphicsNodeNumber(){ } SocNetV-3.7/src/graphicsnodenumber.h000077500000000000000000000022651524402210300175240ustar00rootroot00000000000000/** * @file graphicsnodenumber.h * @brief Declares the GraphicsNodeNumber class for rendering node numbers in the network graph visualization. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef GRAPHICSNODENUMBER_H #define GRAPHICSNODENUMBER_H #include class GraphicsNode; static const int TypeNumber=QGraphicsItem::UserType+3; static const int ZValueNodeNumber = 90; class GraphicsNodeNumber : public QGraphicsTextItem { public: GraphicsNodeNumber(GraphicsNode * , const QString &labelText, const int &size); enum { Type = UserType + 3 }; void removeRefs(); int type() const { return Type; } GraphicsNode* node() { return source; } void setSize(const int size); ~GraphicsNodeNumber(); private: GraphicsNode *source; }; #endif SocNetV-3.7/src/graphicswidget.cpp000077500000000000000000001645731524402210300172170ustar00rootroot00000000000000/** * @file graphicswidget.cpp * @brief Implements the GraphicsWidget class for handling the main visualization widget in the network graph interface. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graphicswidget.h" #include #include #include #include #include #include #include #ifndef QT_NO_OPENGL #include #include #else #include #endif #include "mainwindow.h" #include "graphicsnode.h" #include "graphicsedge.h" #include "graphicsnodenumber.h" #include "graphicsnodelabel.h" #include "graphicsguide.h" #include "graphicsedgeweight.h" #include "graphicsedgelabel.h" // Disabled at runtime by default (no recompile needed to re-enable); used for // the mouse/wheel/zoom event handlers, which fire on every pixel of drag/scroll. Q_LOGGING_CATEGORY(lcGW, "socnetv.graphicswidget") /** * @brief Constructs a GraphicsWidget object * @param sc * @param m_parent */ GraphicsWidget::GraphicsWidget(QGraphicsScene *sc, MainWindow* m_parent) : QGraphicsView ( sc,m_parent) { qCDebug(lcGW) << "Constructing GraphicsWidget..."; qRegisterMetaType("SelectedEdge"); qRegisterMetaType >(); qRegisterMetaType >(); // Set defaults hasDoubleClickedNode=false; m_isTransformationActive = false; m_isZooming = false; m_isClearing = false; m_viewCenterValid = false; m_nodeLabel=""; // Note: Should be changed by a call to setMaxZoomIndex() m_zoomIndex=250; m_currentScaleFactor = 1; m_currentRotationAngle = 0; clickedEdge=0; // No upfront reserve() - QHash grows amortized O(1) regardless, and a fixed // reserve here pre-allocates the same bucket capacity for every widget instance, // whether it ends up holding 5 nodes or 500,000. m_edgeHighlighting = true; m_edgesBezier = false; m_edgeMinOffsetFromNode=6; m_arrowSize = 6; m_nodeNumberVisibility = true; m_nodeLabelVisibility = true; /* "QGraphicsScene applies an indexing algorithm to the scene, to speed up * item discovery functions like items() and itemAt(). * Indexing is most efficient for static scenes (i.e., where items don't move around). * For dynamic scenes, or scenes with many animated items, the index bookkeeping * can outweight the fast lookup speeds." * The user can change this from Settings. */ // Scene index method is set at startup via slotOptionsCanvasIndexMethod(appSettings["canvasIndexMethod"]). // Default appSetting is NoIndex; user can override via Settings > Canvas. // Debounce resize events: fire canvasSizeSet once, 150 ms after the last // resize event, instead of on every pixel of a window-drag resize. m_resizeTimer.setSingleShot(true); connect(&m_resizeTimer, &QTimer::timeout, this, [this]() { emit resized(width(), height()); }); // Connect scene change signal to the slot that handles selected items connect ( scene() , &QGraphicsScene::selectionChanged, this, &GraphicsWidget::handleSelectionChanged); // Set our scene dimensions. This closes #131 scene()->setSceneRect(0,0, 3000, 3000); qCDebug(lcGW) << "Scene dimensions now:" << scene()->width() << "x" << scene()->height(); } /** * @brief Toggles openGL * * @param enabled */ void GraphicsWidget::setOptionsOpenGL(const bool &enabled) { #ifndef QT_NO_OPENGL if (enabled) { QOpenGLWidget *gl = new QOpenGLWidget(); QSurfaceFormat format; format.setSamples(4); gl->setFormat(format); setViewport(gl); qCDebug(lcGW) << "Enabled openGL in GraphicsWidget."; } else { setViewport(new QWidget); qCDebug(lcGW) << "Disabled openGL in GraphicsWidget."; } #else qWarning() << "No OpenGL support! Cannot enable OpenGL in GraphicsWidget."; #endif } /** * @brief Toggles QPainter render hints for primitive edges and text antialiasing * * @param toggle */ void GraphicsWidget::setOptionsAntialiasing(const bool &toggle) { setRenderHint(QPainter::Antialiasing, toggle ); setRenderHint(QPainter::TextAntialiasing, toggle ); } /** * @brief Toggles QGraphicsView's antialiasing auto-adjustment of exposed areas. * * Default: false * Items that render antialiased lines on the boundaries of their QGraphicsItem::boundingRect() * can end up rendering parts of the line outside. * To prevent rendering artifacts, QGraphicsView expands all exposed regions by 2 pixels in all directions. * If you enable this flag, QGraphicsView will no longer perform these adjustments, minimizing the areas that require redrawing, which improves performance. A common side effect is that items that do draw with antialiasing can leave painting traces behind on the scene as they are moved. * * @param toggle */ void GraphicsWidget::setOptionsNoAntialiasingAutoAdjust(const bool &toggle) { setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing, toggle); } /** * @brief Packs (relation, v1, v2) into a single quint64 key for edgesHash. * * Used to be a QString built as "relation:v1>v2" (e.g. "0:3>7"), which cost several heap * allocations per call (QString::number() x3 plus concatenation) and character-wise hashing * on every lookup. This is purely a UI-layer lookup key for GraphicsWidget's own edgesHash * (mapping a graph edge to its GraphicsEdge item) β€” it has nothing to do with Graph's own * data model or any algorithm kernel. * * The key is decimal-digit-packed, not bit-packed, so it stays readable in a debugger the * same way the old string was: reading left to right you get relation, then v1, then v2 - * e.g. relation=0, v1=3, v2=7 packs to 300000007. (Leading zeros aren't preserved by a plain * integer print, so a value like relation=0 won't visually show its 3-digit field width * unless printed zero-padded to 19 digits β€” this key is only ever used as an opaque hash * bucket key, never parsed back into fields, so that's not a correctness concern.) * * This is a positional encoding, not a mixing hash: as long as every field's value stays * within its allocated width (relation < 1000, v1 < 100'000'000, v2 < 100'000'000), the * mapping from (relation, v1, v2) to key is a true bijection - collisions between distinct * triples are structurally impossible, not just statistically unlikely. The three asserts * below exist because that guarantee only holds while every field stays under its digit * budget; an overflow would silently bleed into the next field and could produce a genuine * collision with a different triple. * * @param v1 Source node number (must be < 100,000,000) * @param v2 Target node number (must be < 100,000,000) * @param relation Relation index, or -1 to use the current relation (must be < 1000) * @return Packed key, unique per (relation, v1, v2) triple within the bounds above */ quint64 GraphicsWidget::edgeKey(const int &v1, const int &v2, const int &relation) { constexpr quint64 kRelationMult = 10'000'000'000'000'000ULL; // relation: 3-digit field constexpr quint64 kV1Mult = 100'000'000ULL; // v1: 8-digit field constexpr quint64 kFieldWidth = 100'000'000ULL; // v1 and v2 field width const int effectiveRelation = (relation != -1) ? relation : m_curRelation; Q_ASSERT_X(effectiveRelation >= 0 && quint64(effectiveRelation) < 1000, "GraphicsWidget::edgeKey", "relation exceeds the 3-digit field width"); Q_ASSERT_X(v1 >= 0 && quint64(v1) < kFieldWidth, "GraphicsWidget::edgeKey", "v1 exceeds the 8-digit field width"); Q_ASSERT_X(v2 >= 0 && quint64(v2) < kFieldWidth, "GraphicsWidget::edgeKey", "v2 exceeds the 8-digit field width"); m_edgeKey = quint64(effectiveRelation) * kRelationMult + quint64(v1) * kV1Mult + quint64(v2); return m_edgeKey; } /** * @brief Clears the scene and all hashes, lists, var etc */ void GraphicsWidget::clear() { qCDebug(lcGW) << "Clearing graphics widget data..."; nodeHash.clear(); edgesHash.clear(); m_selectedNodes.clear(); m_selectedEdges.clear(); qCDebug(lcGW) << "Clearing GW scene..."; // scene()->clear() destroys every item unconditionally, so while it runs there is // no point in GraphicsNode/GraphicsEdge destructors also unlinking themselves from // their neighbours' edge lists - that's O(degree) wasted work per edge, repeated // across the whole network. See #260. m_isClearing = true; scene()->clear(); m_isClearing = false; // scene()->clear() already deleted any guide items; just drop the now-dangling // pointers rather than calling clearGuides(), which would delete them again. m_guides.clear(); m_curRelation=0; clickedEdge=0; firstDoubleClickedNode=0; secondDoubleClickedNode=0; hasDoubleClickedNode=false; m_viewCenterValid = false; // next zoom re-centres on content after new network loads qCDebug(lcGW) << "Finished clearing graphics widget"; } /** * @brief Changes the current relation * * @param relation */ void GraphicsWidget::relationSet(int relation) { qCDebug(lcGW) << "Setting relation to" << relation; m_curRelation = relation; } /** * @brief Draws a new node in the scene * * Called when we load files, or when the user presses "Add Node" button or * the user double clicks on the canvas. * * @param nodeNum * @param nodeSize * @param nodeColor * @param numberColor * @param numberSize * @param nodeLabel * @param labelColor * @param labelSize * @param p * @param nodeShape * @param showLabels * @param numberInsideNode * @param showNumbers */ void GraphicsWidget::drawNode( const QPointF &p, const int &nodeNum, const int &nodeSize, const QString &nodeShape, const QString &nodeIconPath, const QString &nodeColor, const QString &numberColor, const int &numberSize, const int &numberDistance, const QString &nodeLabel, const QString &labelColor, const int &labelSize, const int &labelDistance ) { // Draw node GraphicsNode *jim= new GraphicsNode ( this, nodeNum, nodeSize, nodeColor, nodeShape, nodeIconPath, m_nodeNumberVisibility, m_nodeNumbersInside, numberColor, numberSize, numberDistance, m_nodeLabelVisibility, nodeLabel, labelColor, labelSize, labelDistance, m_edgeHighlighting, p ); // Add new node to a container to ease finding, edge creation etc nodeHash.insert(nodeNum, jim); } /** * @brief Draws a new edge in the scene, from node with sourceNum to node with targetNum. * * Used when we do not have references to nodes but only node numbers: * a) when we load a network file * b) when the user clicks on the AddLink button on the MW. * * @param sourceNum * @param targetNum * @param weight * @param label * @param color * @param type * @param drawArrows * @param bezier * @param weightNumbers */ void GraphicsWidget::drawEdge(const int &sourceNum, const int &targetNum, const qreal &weight, const QString &label, const QString &color, const int &type, const bool &drawArrows, const bool &bezier, const bool &weightNumbers) { m_edgeKey = edgeKey(sourceNum, targetNum); if (type != EdgeType::Reciprocated) { GraphicsEdge *edge = new GraphicsEdge( this, nodeHash.value(sourceNum), nodeHash.value(targetNum), weight, label, color, Qt::SolidLine, type, drawArrows, (sourceNum == targetNum) ? true : (bezier || m_edgesBezier), weightNumbers, m_edgeHighlighting, m_arrowSize); edgesHash.insert(m_edgeKey, edge); } else { // Reciprocated: do not create a new GraphicsEdge. // Find the existing opposite arc and upgrade its direction type. // The caller passes (newSource=v2, newTarget=v1) so that this lookup // edgeKey(targetNum, sourceNum) resolves to edgeKey(v1,v2), // matching the key under which the original forward arc was stored. m_edgeKey = edgeKey(targetNum, sourceNum); // Guard against a missing key. In normal flow this should not happen, // but defensive check prevents a null dereference if there is any // inconsistency between the data model and the visual state. GraphicsEdge *e = edgesHash.value(m_edgeKey, nullptr); if (!e) { return; } e->setDirectionType(type); } } /** * @brief Handles double-clicks (or middle-clicks) on the given node, creating a new edge if needed. * * If it is the first double/middle-click on a node, it saves the first node as source. * On a second double/middle-click on a node, it saves the second node as target and signals MW to notify activeGraph, * which will create a new edge from source to target and will signal back to GW to draw the edge on the canvas. * * @param node */ void GraphicsWidget::handleDoubleClickOnNode(GraphicsNode *node){ if (hasDoubleClickedNode){ secondDoubleClickedNode=node; emit userMiddleClicked(firstDoubleClickedNode->nodeNumber(), secondDoubleClickedNode->nodeNumber() ); emit setCursor(Qt::ArrowCursor); hasDoubleClickedNode=false; } else{ firstDoubleClickedNode=node; hasDoubleClickedNode=true; emit setCursor( Qt::PointingHandCursor ); } } /** * @brief Clears clickedEdge and emits a signal to Graph. * * The signal is used to * - display node info on the status bar * - notify context menus for the clicked node. * * Called when the user clicks or double-clicks on a node. * * @param node */ void GraphicsWidget::setNodeClicked(GraphicsNode *node){ if ( node ) { if (clickedEdge) { setEdgeClicked(0); } emit userClickedNode( node->nodeNumber(), QPointF(node->x(), node->y()) ); } } /** * @brief Sets the clicked edge. * * Emits signal to Graph to: * - display edge info on the status bar * - notify context menus for the clicked edge. * * @param edge * @param openMenu */ void GraphicsWidget::setEdgeClicked(GraphicsEdge *edge, const bool &openMenu){ if (edge) { clickedEdge=edge; emit userClickedEdge(edge->sourceNode()->nodeNumber(), edge->targetNode()->nodeNumber(), openMenu); } else { clickedEdge=0; emit userClickedEdge(0,0,openMenu); } } /** * @brief Moves the node with the given number to the new coordinates * * Called while creating random networks. * * @param nodeNum * @param x * @param y */ void GraphicsWidget::moveNode(const int &nodeNum, const qreal &x, const qreal &y){ nodeHash.value(nodeNum)->setPos(x,y); } /** * @brief Deletes the node with the given number from the scene, if exists * * Called from Graph * * @param nodeNum */ void GraphicsWidget::removeNode(const int &nodeNum){ if (GraphicsNode *node = nodeHash.value(nodeNum, nullptr)) { qCDebug(lcGW) << "Removing node with number:" << nodeNum << "- nodeHash size before:" << nodeHash.size(); delete node; } } /** * @brief Removes the edge from node sourceNum to node targetNum from the scene. * * Called when erasing edges using vertex numbers. * Also called when transforming directed edges to undirected. * * @param sourceNum * @param targetNum * @param removeReverse */ void GraphicsWidget::removeEdge(const int &sourceNum, const int &targetNum, const bool &removeReverse){ m_edgeKey = edgeKey(sourceNum, targetNum); if (GraphicsEdge *edge = edgesHash.value(m_edgeKey, nullptr)) { int directionType = edge->directionType(); delete edge; // Check if it was reciprocated if (directionType == EdgeType::Reciprocated) { // The deleted edge was reciprocated, draw the reverse edge. if (!removeReverse) { drawEdge(targetNum, sourceNum, 1,""); } } } else { // Check opposite edge. If it exists and is reciprocated, either downgrade it to a single // directed arc or delete it entirely, matching the removeReverse-found branch above. See #251. m_edgeKey = edgeKey(targetNum, sourceNum); if (GraphicsEdge *reverseEdge = edgesHash.value(m_edgeKey, nullptr)) { if (reverseEdge->directionType() == EdgeType::Reciprocated) { if (removeReverse) { delete reverseEdge; } else { reverseEdge->setDirectionType(EdgeType::Directed); } return; } } } } /** * @brief Removes a node item from the scene. * * Called from GraphicsNode::~GraphicsNode() to remove itself from nodeHash, scene and be deleted * * @param node */ void GraphicsWidget::removeItem( GraphicsNode *node){ int i=node->nodeNumber(); if (firstDoubleClickedNode == node) { hasDoubleClickedNode = false; emit setCursor(Qt::ArrowCursor); } nodeHash.remove(i); scene()->removeItem(node); node->deleteLater (); } /** * @brief Removes an edge item from the scene. * * Called from GraphicsEdge::~GraphicsEdge() to remove itself from edgesHash, scene and be deleted * * @param edge * */ void GraphicsWidget::removeItem( GraphicsEdge * edge){ m_edgeKey = edgeKey(edge->sourceNodeNumber(), edge->targetNodeNumber() ) ; setEdgeClicked(0); edgesHash.remove(m_edgeKey); scene()->removeItem(edge); edge->deleteLater(); } /** * @brief Removes an edge weight number item from the scene. * * @param edgeWeight */ void GraphicsWidget::removeItem( GraphicsEdgeWeight *edgeWeight){ scene()->removeItem(edgeWeight); edgeWeight->deleteLater(); } /** * @brief Removes an edge label item from the scene. * * @param edgeLabel */ void GraphicsWidget::removeItem( GraphicsEdgeLabel *edgeLabel){ scene()->removeItem(edgeLabel); edgeLabel->deleteLater(); } /** * @brief Removes a node label item from the scene. * * @param nodeLabel */ void GraphicsWidget::removeItem( GraphicsNodeLabel *nodeLabel){ scene()->removeItem(nodeLabel); nodeLabel->deleteLater(); } /** * @brief Removes a node number item from the scene. * @param nodeNumber */ void GraphicsWidget::removeItem( GraphicsNodeNumber *nodeNumber){ scene()->removeItem(nodeNumber); nodeNumber->deleteLater(); } /** * @brief Sets the color of an node. * * Called when the user changes the color of a node (right-clicking). * * @param nodeNumber * @param color * @return */ bool GraphicsWidget::setNodeColor(const int &nodeNum, const QString &color){ nodeHash.value(nodeNum)->setColor(color); return true; } /** * @brief Sets the shape of an node. * * @param nodeNumber * @param shape * @param iconPath * @return */ bool GraphicsWidget::setNodeShape(const int &nodeNum, const QString &shape, const QString &iconPath){ nodeHash.value(nodeNum)->setShape(shape,iconPath); return true; \ } /** * @brief Toggles the visibility of all node labels. * * @param toggle */ void GraphicsWidget::setNodeLabelsVisibility (const bool &toggle){ viewport()->setUpdatesEnabled(false); foreach ( GraphicsNode *m_node, nodeHash) { m_node->setLabelVisibility(toggle); } viewport()->setUpdatesEnabled(true); m_nodeLabelVisibility = toggle; } /** * @brief Sets the label of an node. * * @param nodeNumber * @param label * @return */ bool GraphicsWidget::setNodeLabel(const int &nodeNumber, const QString &label){ nodeHash.value(nodeNumber)->setLabelText (label); return true; } /** * @brief Toggles displaying node numbers in or out of nodes * * @param numIn */ void GraphicsWidget::setNumbersInsideNodes(const bool &toggle){ viewport()->setUpdatesEnabled(false); foreach ( GraphicsNode *m_node, nodeHash) { m_node->setNumberInside(toggle); } viewport()->setUpdatesEnabled(true); m_nodeNumbersInside=toggle; } /** * @brief Sets initial node size from MW. * * It is called from MW on startup and when user changes it. * * @param size */ void GraphicsWidget::setInitNodeSize(int size){ m_nodeSize=size; } /** * @brief Sets the initial zoom setting. Use setMaxZoomIndex() instead. * @param zoomIndex */ void GraphicsWidget::setInitZoomIndex(const int &zoomIndex) { m_zoomIndex = m_zoomIndexInit = zoomIndex; } /** * @brief Sets the max zoom allowed (and the initial zoom as half of it). * Called from MW on startup * @param zoomIndex */ void GraphicsWidget::setMaxZoomIndex(const int &maxZoomIndex) { m_zoomIndexMax = maxZoomIndex; m_zoomIndex = m_zoomIndexInit = m_zoomIndexMax / 2.0; } /** * @brief Changes the visibility of a Node * @param nodeNum * @param toggle */ void GraphicsWidget::setNodeVisibility(const int &nodeNum, const bool &toggle){ if (GraphicsNode *node = nodeHash.value(nodeNum, nullptr)) { node->setVisible(toggle); node->setEnabled(toggle); } } /** * @brief Changes the size of a node * * @param nodeNum * @param size * @return bool */ bool GraphicsWidget::setNodeSize(const int &nodeNum, const int &size ){ if (GraphicsNode *node = nodeHash.value(nodeNum, nullptr)) { node->setSize(size > 0 ? size : m_nodeSize); return true; } return false; } /** * @brief Toggles the visibility of node numbers * * @param toggle */ void GraphicsWidget::setNodeNumberVisibility(const bool &toggle){ viewport()->setUpdatesEnabled(false); foreach ( GraphicsNode *m_node, nodeHash) { m_node->setNumberVisibility(toggle); } viewport()->setUpdatesEnabled(true); m_nodeNumberVisibility = toggle; } /** * @brief Changes the color of a node number * * @param nodeNumber * @param color */ void GraphicsWidget::setNodeNumberColor(const int &nodeNum, const QString &color) { if (!color.isNull()) { if (GraphicsNode *node = nodeHash.value(nodeNum, nullptr)) node->setNumberColor(color); } } /** * @brief Changes the size of the number of a node * * @param nodeNum * @param size */ bool GraphicsWidget::setNodeNumberSize(const int &nodeNum, const int &size){ if (size > 0) { if (GraphicsNode *node = nodeHash.value(nodeNum, nullptr)) { node->setNumberSize(size); return true; } } return false; } /** * @brief Changes the distance of the number of a node. * @param nodeNum * @param distance */ bool GraphicsWidget::setNodeNumberDistance(const int &nodeNum, const int &distance ){ if (distance >= 0) { if (GraphicsNode *node = nodeHash.value(nodeNum, nullptr)) { node->setNumberDistance(distance); return true; } } return false; } /** * @brief Changes the label color of a node to 'color'. * @param nodeNum * @param color */ bool GraphicsWidget::setNodeLabelColor(const int &nodeNum, const QString &color){ if (GraphicsNode *node = nodeHash.value(nodeNum, nullptr)) { node->setLabelColor(color); return true; } return false; } /** * @brief Changes the label size of a node to 'size'. * @param nodeNum * @param size */ bool GraphicsWidget::setNodeLabelSize(const int &nodeNum, const int &size){ if (size > 0) { if (GraphicsNode *node = nodeHash.value(nodeNum, nullptr)) { node->setLabelSize(size); return true; } } return false; } /** * @brief Changes the distance of the label of the given node * * @param nodeNum * @param distance */ bool GraphicsWidget::setNodeLabelDistance( const int &nodeNum, const int &distance ){ if (distance >= 0) { if (GraphicsNode *node = nodeHash.value(nodeNum, nullptr)) { node->setLabelDistance(distance); return true; } } return false; } /** * @brief Selects all nodes in the given list * * @param list */ void GraphicsWidget::setSelectedNodes(const QList &list){ foreach ( int nodeNum, list) { if (GraphicsNode *node = nodeHash.value(nodeNum, nullptr)) node->setSelected(true); } } /** * @brief Sets the label of an edge. * * Called when the user changes the label of an edge (right-clicking). * * @param source * @param target * @param label */ void GraphicsWidget::setEdgeLabel(const int &source, const int &target, const QString &label){ m_edgeKey = edgeKey( source, target ); if (GraphicsEdge *edge = edgesHash.value(m_edgeKey, nullptr)) edge->setLabel(label); } /** * @brief Sets the color of an edge. * * Called when the user changes the color of an edge (right-clicking). * Also called from Graph when all edge colors need to be changed. * * @param source * @param target * @param color */ void GraphicsWidget::setEdgeColor(const int &source, const int &target, const QString &color){ m_edgeKey = edgeKey( source, target ); if (GraphicsEdge *edge = edgesHash.value(m_edgeKey, nullptr)) edge->setColor(color); } /** * @brief Changes the direction type of an existing edge * * @param source * @param target * @param dirType * @return */ bool GraphicsWidget::setEdgeDirectionType(const int &source, const int &target, const int &dirType){ m_edgeKey = edgeKey( source, target ); if (GraphicsEdge *edge = edgesHash.value(m_edgeKey, nullptr)) { edge->setDirectionType(dirType); return true; } return false; } /** * @brief Sets the weight of an edge. * * Called when the user changes the weight of an edge (right-clicking). * * @param source * @param target * @param weight * @return */ bool GraphicsWidget::setEdgeWeight(const int &source, const int &target, const qreal &weight){ m_edgeKey = edgeKey( source, target ); if (GraphicsEdge *edge = edgesHash.value(m_edgeKey, nullptr)) { edge->setWeight(weight); return true; } else { //check opposite edge. If it exists, then transform it to directed m_edgeKey = edgeKey(target, source); if (GraphicsEdge *edge = edgesHash.value(m_edgeKey, nullptr)) { edge->setWeight(weight); return true; } } return false; } /** * @brief Toggles the visibility of all edge arrows * * @param toggle */ void GraphicsWidget::setEdgeArrowsVisibility(const bool &toggle){ viewport()->setUpdatesEnabled(false); foreach ( GraphicsEdge *m_edge, edgesHash) { m_edge->showArrows(toggle); } viewport()->setUpdatesEnabled(true); } /** * @brief Sets the arrow size for all edges and stores it as the default for * new edges. * * Each edge only recomputes its four cached arrow-corner QPointFs and * schedules a repaint β€” no full path rebuild is triggered. * * @param size Arrow size in pixels (typically 2–20). */ void GraphicsWidget::setEdgeArrowSize(const int &size) { m_arrowSize = size; viewport()->setUpdatesEnabled(false); foreach (GraphicsEdge *edge, edgesHash) edge->setArrowSize(size); viewport()->setUpdatesEnabled(true); } /** * @brief Changes the offset of an edge (or all edges) from source and target nodes. * * @param source * @param target * @param offset */ void GraphicsWidget::setEdgeOffsetFromNode(const int &source, const int &target, const int &offset){ if (source && target) { m_edgeKey = edgeKey(source, target); if (GraphicsEdge *edge = edgesHash.value(m_edgeKey, nullptr)) { edge->setMinimumOffsetFromNode(offset); return; } } // if source == target == 0, then we change all edges'offset. else { foreach (GraphicsEdge *edge, edgesHash) edge->setMinimumOffsetFromNode(offset); } } /** * @brief Toggles all edge weight numbers visibility * @param toggle */ void GraphicsWidget::setEdgeWeightNumbersVisibility (const bool &toggle){ viewport()->setUpdatesEnabled(false); foreach ( GraphicsEdge *m_edge, edgesHash) { m_edge->setWeightNumberVisibility(toggle); } viewport()->setUpdatesEnabled(true); } /** * @brief Toggles all edge labels visibility * @param toggle */ void GraphicsWidget::setEdgeLabelsVisibility (const bool &toggle){ viewport()->setUpdatesEnabled(false); foreach ( GraphicsEdge *m_edge, edgesHash) { m_edge->setLabelVisibility(toggle); } viewport()->setUpdatesEnabled(true); } /** * @brief Enables or disables edge highlighting. * * If enabled, an edge will be highlighted on mouse hover and edges * connected to a node will be highlighted when the user selects that node. * * @param toggle */ void GraphicsWidget::setEdgeHighlighting(const bool &toggle){ viewport()->setUpdatesEnabled(false); foreach ( GraphicsNode *m_node, nodeHash) { m_node->setEdgeHighLighting(toggle); } foreach ( GraphicsEdge *m_edge, edgesHash) { m_edge->setHighlighting(toggle); } viewport()->setUpdatesEnabled(true); m_edgeHighlighting = toggle; } /** * @brief Selects the edges that form a shortest path, exactly as if the user * had Shift-clicked each edge in turn. * * Uses QSignalBlocker so that scene()->selectionChanged fires only once * (via the explicit handleSelectionChanged() call at the end), not once per * edge. MainWindow then receives a single userSelectedItems() emission with * the complete path edge set β€” same as any other multi-selection. * * For each hop (path[i] β†’ path[i+1]) the forward key is tried first; if the * edge is not found (directed graph, path goes the "other way") the reverse * key is tried. * * @param path Ordered list of vertex numbers returned by * Graph::graphGeodesicShortestPath(). */ void GraphicsWidget::selectPath(const QList &path) { if (path.size() < 2) return; { QSignalBlocker blocker(scene()); scene()->clearSelection(); for (int i = 0; i < path.size() - 1; ++i) { GraphicsEdge *e = edgesHash.value(edgeKey(path[i], path[i+1]), nullptr); if (!e) e = edgesHash.value(edgeKey(path[i+1], path[i]), nullptr); if (e) e->setSelected(true); } } // blocker destructor re-enables scene signals handleSelectionChanged(); // one emission to MainWindow with the full path set } /** * @brief Toggles bezier-curve rendering for all edges currently in the scene * * Also stores the toggle in m_edgesBezier so it applies to edges drawn afterwards. * Viewport updates are disabled for the duration of the loop to avoid a repaint per edge. * * @param toggle */ void GraphicsWidget::setEdgesBezier(const bool &toggle) { m_edgesBezier = toggle; viewport()->setUpdatesEnabled(false); foreach (GraphicsEdge *edge, edgesHash) edge->toggleBezier(toggle); viewport()->setUpdatesEnabled(true); } /** * @brief Toggles the visibility of the given edge * * @param relation * @param sourceNum * @param targetNum * @param visible * @param preserveReverseEdge */ void GraphicsWidget::setEdgeVisibility(const int &relation, const int &sourceNum, const int &targetNum, const bool &visible, const bool &preserveReverseEdge, const int &edgeWeight, const int &reverseEdgeWeight) { quint64 reverseEdgeKey; m_edgeKey = edgeKey(sourceNum, targetNum, relation); // During network teardown/reset, Graph may still emit edge-visibility updates // after the corresponding GraphicsNodes have already been removed from nodeHash. // In that case, do not attempt to recreate/draw edges. const bool haveSourceNode = nodeHash.contains(sourceNum); const bool haveTargetNode = nodeHash.contains(targetNum); if (GraphicsEdge *edge = edgesHash.value(m_edgeKey, nullptr)) { // Edge does exist if (!visible && preserveReverseEdge) { // This edge must be disabled and the reverse must be preserved/created delete edge; } else { edge->setVisible(visible); edge->setEnabled(visible); } } else { // Edge does not exist yet if (visible) { // The reverse may exist. Check reverseEdgeKey = edgeKey(targetNum, sourceNum, relation); if (GraphicsEdge *reverseEdge = edgesHash.value(reverseEdgeKey, nullptr)) { reverseEdge->setVisible(true); reverseEdge->setEnabled(true); reverseEdge->setDirectionType(EdgeType::Reciprocated); return; } else { // Do not create an edge if either endpoint node is already gone if (!haveSourceNode || !haveTargetNode) { return; } drawEdge(sourceNum, targetNum, edgeWeight, "", "black", EdgeType::Directed); } } } if (preserveReverseEdge) { // Reverse edge must be preserved (see #140) // Check if the reverse exists and if not create it reverseEdgeKey = edgeKey(targetNum, sourceNum, relation); if (GraphicsEdge *reverseEdge = edgesHash.value(reverseEdgeKey, nullptr)) { reverseEdge->setVisible(true); reverseEdge->setEnabled(true); reverseEdge->setDirectionType(EdgeType::Directed); return; } else { // Do not create the reverse edge if either endpoint node is already gone if (!haveSourceNode || !haveTargetNode) { return; } drawEdge(targetNum, sourceNum, reverseEdgeWeight, "", "black", EdgeType::Directed); } } } /** * @brief Applies a batch of edge visibility changes in one call (WS3 M2). * * Reuses setEdgeVisibility() per entry unchanged - the only difference from N separate * signal/slot dispatches is that this whole batch crosses from graphThread to the GUI * thread as a single queued call, not one per edge. See Graph::signalSetEdgesVisibilityBatch. * * @param changes */ void GraphicsWidget::setEdgesVisibilityBatch(const QList &changes) { for (const EdgeVisibilityChange &c : changes) { setEdgeVisibility(c.relation, c.source, c.target, c.visible, c.preserveReverseEdge, c.edgeWeight, c.reverseEdgeWeight); } } /** * @brief Toggles the visibility of all items of the given type * * @param type * @param visible */ void GraphicsWidget::setAllItemsVisibility(int type, bool visible){ if (type == TypeEdge) { foreach (GraphicsEdge *edge, edgesHash) { if (visible) edge->show(); else edge->hide(); } } else if (type == TypeNode) { foreach (GraphicsNode *node, nodeHash) { if (visible) node->show(); else node->hide(); } } else { foreach (QGraphicsItem *item, scene()->items()) { if (item->type() == type) { if (visible) item->show(); else item->hide(); } } } } /** * @brief Adds a circular guideline * @param x0 * @param y0 * @param radius */ void GraphicsWidget::addGuideCircle( const double&x0, const double&y0, const double&radius){ GraphicsGuide *circ=new GraphicsGuide (this, x0, y0, radius); circ->show(); m_guides.append(circ); } /** * @brief Adds a horizonal guideline * @param y0 */ void GraphicsWidget::addGuideHLine(const double &y0){ GraphicsGuide *line=new GraphicsGuide (this, y0, width()); line->show(); m_guides.append(line); } /** * @brief Removes and deletes all guide items previously added via * addGuideCircle()/addGuideHLine(), without scanning the scene. * * GraphicsGuide::die() only clears geometry/visibility bookkeeping β€” it does * not delete or schedule deletion β€” so a direct synchronous delete is safe. */ void GraphicsWidget::clearGuides(){ for (GraphicsGuide *guide : m_guides) { guide->die(); delete guide; } m_guides.clear(); } /** * @brief Forces the scene to select all items. Also signals that no node is clicked. */ void GraphicsWidget::selectAll(){ qCDebug(lcGW) << "Selecting all scene items... Scene dimensions: " << scene()->width() << "x" << scene()->height(); QPainterPath path; path.addRect(scene()->sceneRect()); scene()->setSelectionArea(path); emit userClickedNode(0, QPointF(0,0)); qCDebug(lcGW) << "Selected scene items now: " << selectedItems().size(); } /** * @brief Clears any item selection from the scene. Also signals that no node is clicked. */ void GraphicsWidget::selectNone(){ qCDebug(lcGW) << "Emptying user selection... scene dimensions: " << scene()->width()<<"x"<height(); emit userClickedNode(0,QPointF(0,0)); scene()->clearSelection(); } /** * @brief Handles the event of selection change in the scene. * * Queries scene()->selectedItems() once, populates m_selectedNodes and * m_selectedEdges from a single pass, then emits both lists to Graph. */ void GraphicsWidget::handleSelectionChanged() { m_selectedNodes.clear(); m_selectedEdges.clear(); const QList items = scene()->selectedItems(); for (QGraphicsItem *item : items) { if (GraphicsNode *node = qgraphicsitem_cast(item)) m_selectedNodes.append(node->nodeNumber()); else if (GraphicsEdge *edge = qgraphicsitem_cast(item)) m_selectedEdges << qMakePair(edge->sourceNodeNumber(), edge->targetNodeNumber()); } emit userSelectedItems(m_selectedNodes, m_selectedEdges); } /** * @brief Returns a list of all selected QGraphicsItem(s) * * @return QList */ QList GraphicsWidget::selectedItems(){ return scene()->selectedItems(); } /** * @brief Returns a List of selected node numbers * * @return QList */ QList GraphicsWidget::selectedNodes() { m_selectedNodes.clear(); foreach (QGraphicsItem *item, scene()->selectedItems()) { if (GraphicsNode *node = qgraphicsitem_cast(item) ) { m_selectedNodes.append(node->nodeNumber()); } } return m_selectedNodes; } /** * @brief Returns a QList of selected directed edges structs in the form of v1,v2 * * @return QList */ QList GraphicsWidget::selectedEdges() { m_selectedEdges.clear(); foreach (QGraphicsItem *item, scene()->selectedItems()) { if (GraphicsEdge *edge= qgraphicsitem_cast(item) ) { SelectedEdge selEdge = qMakePair( edge->sourceNodeNumber(), edge->targetNodeNumber()); m_selectedEdges << selEdge; } } return m_selectedEdges; } /** * @brief Handles user double-clicks. * * If the double-click was on empty space, it initiates the new node creation process. * Otherwise, it the user double-clicks on a node, starts the new edge creation process. * * @param QMouseEvent */ void GraphicsWidget::mouseDoubleClickEvent ( QMouseEvent * e ) { if (this->dragMode() == QGraphicsView::RubberBandDrag ) { if ( QGraphicsItem *item= itemAt(e->pos() ) ) { if (GraphicsNode *node = qgraphicsitem_cast(item)) { qCDebug(lcGW) << "Double-click on a node. Starting new edge..."; handleDoubleClickOnNode(node); QGraphicsView::mouseDoubleClickEvent(e); return; } else if ( (*item).type() == TypeLabel){ QGraphicsView::mouseDoubleClickEvent(e); return; } qCDebug(lcGW) << "Double-click on something (not node)"; } QPointF p = mapToScene(e->pos()); qCDebug(lcGW) << "Double-click on empty space. Signalling to create a new vertex/node at:" << e->pos() << "~"<< p; emit userDoubleClickNewNode(p); } QGraphicsView::mouseDoubleClickEvent(e); } /** * @brief Handles the mouse press event * @param e */ void GraphicsWidget::mousePressEvent( QMouseEvent * e ) { if (this->dragMode() == QGraphicsView::RubberBandDrag ) { QPointF p = mapToScene(e->pos()); if ( QGraphicsItem *item = itemAt(e->pos() ) ) { // // User clicked on some item // if (GraphicsNode *node = qgraphicsitem_cast(item)) { // // User clicked on a node // qCDebug(lcGW) << "Clicked on a node at:" << e->pos() << "~"<< p << "Setting it as clicked node..."; setNodeClicked(node); if ( e->button()==Qt::RightButton ) { qCDebug(lcGW) << "This was a right-click on node. Signaling to open node menu"; if ( !node->isSelected() ) node->setSelected(true); emit openNodeMenu(); } else if ( e->button()==Qt::MiddleButton) { qCDebug(lcGW) << "This was a middle-click on node. Calling to start or conclude a new edge..."; handleDoubleClickOnNode(node); } else if ( e->button()==Qt::LeftButton && (e->modifiers() & Qt::ShiftModifier) ) { // Shift+left-click: toggle this node in/out of the current // selection without clearing other selected nodes. // Qt's default item handler only treats Ctrl as additive; // we intercept Shift here and return early to skip that path. qCDebug(lcGW) << "Shift+left-click on node. Toggling selection."; node->setSelected(!node->isSelected()); return; } QGraphicsView::mousePressEvent(e); return; } if (GraphicsEdge *edge = qgraphicsitem_cast(item)) { // // User clicked on an edge // qCDebug(lcGW) << "Clicked on an edge at:" << e->pos() << "~"<< p; if ( e->button()==Qt::LeftButton ) { qCDebug(lcGW) << "This was a left click on the edge. Setting clicked edge..."; setEdgeClicked(edge); } else if ( e->button()==Qt::RightButton ) { qCDebug(lcGW) << "This was a right click on the edge. Signaling to open context menu..."; setEdgeClicked(edge, true); } else { setEdgeClicked(edge); } QGraphicsView::mousePressEvent(e); return; } } else { // // User clicked on empty space // if ( e->button() == Qt::RightButton ) { // // User clicked on empty space, with right button // so we must open the context menu // qCDebug(lcGW) << "Right click on empty space at:" << e->pos() << "~"<< p << "Signalling to open context menu"; emit openContextMenu(p); } else { // // user clicked on empty space, with left button. // qCDebug(lcGW) << "Left click on empty space at:" << e->pos() << "~"<< p << "Setting clickedEdge=0 and emitting signal..."; clickedEdge=0; emit userClickOnEmptySpace(p); } } } QGraphicsView::mousePressEvent(e); } /** * @brief Handles the mouse release events. * * Called when the user releases a mouse button, after a click. * First sees what was in the position where the user clicked * If a node was underneath, it signals for every node * in scene selectedItems * * @param e */ void GraphicsWidget::mouseReleaseEvent( QMouseEvent * e ) { if (this->dragMode() == QGraphicsView::RubberBandDrag ) { QPointF p = mapToScene(e->pos()); if ( QGraphicsItem *item= itemAt(e->pos() ) ) { if (GraphicsNode *node = qgraphicsitem_cast(item)) { qCDebug(lcGW) << "Mouse released at:" << e->pos() << "~"<< p << "on a node. Signalling to move all selected nodes"; Q_UNUSED(node); // m_selectedNodes is kept in sync by handleSelectionChanged() and does // not change mid-drag, so reuse it instead of re-querying the scene. for (int nodeNum : std::as_const(m_selectedNodes)) { if (GraphicsNode *nodeSelected = nodeHash.value(nodeNum, nullptr)) { emit userNodeMoved(nodeSelected->nodeNumber(), nodeSelected->x(), nodeSelected->y()); } } QGraphicsView::mouseReleaseEvent(e); } if (GraphicsEdge *edge= qgraphicsitem_cast(item)) { Q_UNUSED(edge); qCDebug(lcGW) << "Mouse released at:" << e->pos() << "~"<< p << "on an edge."; QGraphicsView::mouseReleaseEvent(e); return; } } else{ qCDebug(lcGW) << "Mouse released at:" << e->pos() << "~"<< p << "on empty space."; } } QGraphicsView::mouseReleaseEvent(e); } /** * @brief Handles the mouse wheel event. If CTRL is pressed, zooms in or out. * * @param e */ void GraphicsWidget::wheelEvent(QWheelEvent *e) { bool ctrlKey = (e->modifiers() == Qt::ControlModifier); QPoint numDegrees = e->angleDelta() / 8; qCDebug(lcGW) << "Mouse wheel changeded by numDegrees = " << numDegrees; if (ctrlKey) { if ( numDegrees.x() > 0 || numDegrees.y() > 0) zoomIn(1); else if ( numDegrees.x() < 0 || numDegrees.y() < 0) zoomOut(1); } } /** * @brief Decreases the numerical zoom index of the scene by the given step * * Signals to MW to update the UI and do the rest. * * @param level */ void GraphicsWidget::zoomOut (const int step){ qCDebug(lcGW) << "Zooming out from "<< m_zoomIndex << "-" << step ; m_zoomIndex-=step; if (m_zoomIndex <= 0) { m_zoomIndex = 0; } changeMatrixScale(m_zoomIndex); emit zoomChanged(m_zoomIndex); } /** * @brief Increases the numerical zoom index of the scene by the given step * * Signals to MW to update the UI and do the rest. * * @param level */ void GraphicsWidget::zoomIn(const int step){ qCDebug(lcGW) << "Zooming in from "<< m_zoomIndex << "+" << step ; m_zoomIndex+=step; if (m_zoomIndex > m_zoomIndexMax) { m_zoomIndex=m_zoomIndexMax; } if (m_zoomIndex < 0) { m_zoomIndex = 0; } changeMatrixScale(m_zoomIndex); emit zoomChanged(m_zoomIndex); } /** * @brief Does the actual zoom in or out while preserving current rotation * * Scales the view transformation by the given value (0..m_zoomIndexMax) * * @param value */ void GraphicsWidget::changeMatrixScale(int value) { qCDebug(lcGW) << "Scaling the view transformation matrix by value" << value << " - GW dimensions: " << width() << "x" << height(); // Raise a flag that a non-trivial transformation is applied on the view m_isTransformationActive = true; // For any m_zoomIndexMax value, the max scaleFactor will always be: 2 ^ 5 = 32 m_currentScaleFactor = pow(qreal(2), (value - (m_zoomIndexInit )) / qreal(m_zoomIndexMax/10.0) ); qCDebug(lcGW) << "Scaling view transformation by new scale factor:" << m_currentScaleFactor << "rotation unchanged:" << m_currentRotationAngle; // Anchor zoom to wherever the user last panned to (m_viewCenter), falling back to // content centre on first zoom after load or after reset(). m_isZooming blocks // scrollContentsBy() from updating m_viewCenter during the centerOn() call below. const QRectF contentBounds = scene()->itemsBoundingRect(); if (!m_viewCenterValid) { m_viewCenter = contentBounds.isEmpty() ? scene()->sceneRect().center() : contentBounds.center(); m_viewCenterValid = true; } m_isZooming = true; resetTransform(); scale(m_currentScaleFactor, m_currentScaleFactor); rotate(m_currentRotationAngle); centerOn(m_viewCenter); m_isZooming = false; qCDebug(lcGW) << "Finished scaling the view." << " - GW dimensions: " << width() << "x" << height() << " Scene dimensions:" << scene()->width() << "x" << scene()->height(); } /** * @brief Tracks the user's pan position so zoom can anchor to it. * * Called by Qt whenever the scroll position changes β€” user scroll bars, mouse * wheel scroll, or programmatic centerOn(). The m_isZooming flag blocks * updates during our own centerOn() call in changeMatrixScale(), so only * genuine user pans update m_viewCenter. */ void GraphicsWidget::scrollContentsBy(int dx, int dy) { QGraphicsView::scrollContentsBy(dx, dy); if (!m_isZooming) m_viewCenter = mapToScene(viewport()->rect().center()); } /** * @brief Decreases the numerical rotation Rotates the view to the left, by a fixed angle */ void GraphicsWidget::rotateLeft(){ m_currentRotationAngle-=5; // Apply the transform directly rather than relying on the rotationChanged->slider-> // changeMatrixRotation chain: that chain is now display-only (QSignalBlocker'd in // MainWindow), so nothing else would ever apply the rotation. changeMatrixRotation(m_currentRotationAngle); emit rotationChanged(m_currentRotationAngle); } /** * @brief Rotates the view to the right, by a fixed angle */ void GraphicsWidget::rotateRight() { m_currentRotationAngle+=5; changeMatrixRotation(m_currentRotationAngle); emit rotationChanged(m_currentRotationAngle); } /** * @brief Rotates the view transformation by angle degrees clockwise, while preserving the current scale * * @param angle */ void GraphicsWidget::changeMatrixRotation(int angle){ m_isTransformationActive = true; m_currentRotationAngle = angle; qCDebug(lcGW) << "Rotating clockwise by angle" << angle << " m_currentRotationAngle " << m_currentRotationAngle << " m_currentScaleFactor " << m_currentScaleFactor; resetTransform(); scale(m_currentScaleFactor, m_currentScaleFactor); rotate(angle); } /** * @brief Scales and centres the view so that the full network is visible. * * Computes the scale factor needed to fit the scene's bounding rect inside the * viewport (with a 20 px margin on each side), maps it to the nearest discrete * zoom index, updates @c m_viewCenter to the content centre, and applies the * transform via @c changeMatrixScale(). @c zoomChanged() is also emitted to * keep the zoom slider in sync, but the transform is applied unconditionally β€” * not via the slider chain β€” so the view is always re-centred even when the * computed zoom index equals the slider's current value. * * Called automatically via a QueuedConnection on @c Graph::signalLayoutFinished, * which is emitted after every layout algorithm completes. The queued delivery * guarantees that all @c setNodePos signals (which move scene items to their * new positions) have been processed before this slot runs β€” so * @c itemsBoundingRect() reflects the finished layout. * * After this call, subsequent zoom or pan operations anchor normally to the * new view centre (the #248 behaviour is preserved). */ void GraphicsWidget::zoomToFit() { const QRectF bounds = scene()->itemsBoundingRect(); if (bounds.isEmpty()) return; const QRectF vp = viewport()->rect(); // Small margin so content doesn't touch the viewport edge on tiny networks. const qreal margin = 5.0; const qreal sx = (vp.width() - margin * 2.0) / bounds.width(); const qreal sy = (vp.height() - margin * 2.0) / bounds.height(); const qreal fitScale = qMin(sx, sy); qCDebug(lcGW) << "zoomToFit() bounds:" << bounds << "viewport rect:" << vp << "sceneRect:" << scene()->sceneRect() << "sx:" << sx << "sy:" << sy << "fitScale:" << fitScale; if (fitScale <= 0.0) return; // Content that's only somewhat larger than the viewport is left at the initial // 100% level rather than computing the "exact" fit-with-margin scale β€” matching // reset()'s tight, margin-free look instead of visibly shrinking for an overflow // that reset() tolerates just fine. Real-world networks routinely land in the // 0.85-0.95 range here (node/label overhang beyond the raw coordinate range); // only content that would need at least a quarter of its size cut actually // triggers a real zoom-out. int computedZoomIndex; if (fitScale >= 0.75) { computedZoomIndex = m_zoomIndexInit; } else { computedZoomIndex = qRound(m_zoomIndexInit + qLn(fitScale) / qLn(2.0) * (m_zoomIndexMax / 10.0)); } m_zoomIndex = qBound(0, computedZoomIndex, m_zoomIndexInit); qCDebug(lcGW) << "zoomToFit() computedZoomIndex (pre-clamp):" << computedZoomIndex << "m_zoomIndexInit:" << m_zoomIndexInit << "m_zoomIndexMax:" << m_zoomIndexMax << "final m_zoomIndex:" << m_zoomIndex; m_viewCenter = bounds.center(); m_viewCenterValid = true; // Apply the transform directly β€” don't rely on the zoomChangedβ†’sliderβ†’ // changeMatrixScale chain, which is a no-op when the index hasn't changed. changeMatrixScale(m_zoomIndex); // Update the slider separately; if valueChanged fires it re-calls // changeMatrixScale with the same values, which is idempotent. emit zoomChanged(m_zoomIndex); } /** * @brief Resets to default rotation, zoom and scale */ void GraphicsWidget::reset() { m_currentRotationAngle=0; m_currentScaleFactor = 1; m_zoomIndex=m_zoomIndexInit; m_viewCenterValid = false; // next zoom re-centres on content // Apply the identity transform directly β€” don't rely on the // zoomChangedβ†’sliderβ†’changeMatrixScale chain, which is a no-op when the // slider is already at m_zoomIndexInit. changeMatrixScale calls // resetTransform()+scale()+rotate() using the zeroed rotation above. changeMatrixScale(m_zoomIndexInit); emit zoomChanged(m_zoomIndex); emit rotationChanged(m_currentRotationAngle); } /** * @brief Handles the canvas resize event * * Repositions guides then emits resized() signal to MW and eventually Graph * which does the node repositioning maintaining proportions * * @param e */ void GraphicsWidget::resizeEvent( QResizeEvent *e ) { qCDebug(lcGW) << "GW resized:" << e->oldSize().width() << "x" << e->oldSize().height() << "-->" << e->size().width() << "x" << e->size().height() << "widget dimensions:" << width() << "x"<rect() // is current when zoomToFit() reads it later (via the timer). QGraphicsView::resizeEvent(e); // Suppress the spurious resize event that Qt fires when our own scale/rotate // calls cause scrollbars to appear or disappear (changing the viewport rect). if (m_isTransformationActive) { m_isTransformationActive = false; return; } // Guard against 0-size events (window minimise on Windows). if (e->size().isEmpty()) return; // Guides are positioned relative to the old canvas size; discard them now. // They will be recreated automatically when the next layout runs. clearGuides(); // Debounce: emit resized() once, 150 ms after the last resize event fires. // The signal triggers canvasSizeSet() (proportional node rescaling) on the // graph thread, which then emits signalLayoutFinished β†’ zoomToFit(). m_resizeTimer.start(150); qCDebug(lcGW) << "Scene dimensions now:" << scene()->width() << "x" << scene()->height(); } /** * @brief Destructor. Calls the method to clear the data. */ GraphicsWidget::~GraphicsWidget(){ qCDebug(lcGW) << "Terminating. Calling clear()"; clear(); } SocNetV-3.7/src/graphicswidget.h000077500000000000000000000257711524402210300166600ustar00rootroot00000000000000/** * @file graphicswidget.h * @brief Declares the GraphicsWidget class for handling the main visualization widget in the network graph interface. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef GRAPHICSWIDGET_H #define GRAPHICSWIDGET_H #include #include #include #include #include "global.h" SOCNETV_USE_NAMESPACE // WS14: shared across graphicswidget.cpp and the small canvas item files (graphicsnode.cpp, // graphicsedgelabel.cpp, graphicsedgeweight.cpp, graphicsnodelabel.cpp, graphicsnodenumber.cpp) -- // promoted from graphicswidget.cpp-local since those files are all part of the same WS10 canvas // subsystem and each has too few qDebug() calls to justify a category of its own. Q_DECLARE_LOGGING_CATEGORY(lcGW) class MainWindow; class GraphicsNode; class GraphicsEdge; class GraphicsNodeNumber; class GraphicsNodeLabel; class GraphicsGuide; class GraphicsEdgeWeight; class GraphicsEdgeLabel; typedef QHash H_KeyToEdge; typedef QHash H_NumToNode; class GraphicsWidget : public QGraphicsView { Q_OBJECT public: GraphicsWidget(QGraphicsScene *m_scene, MainWindow *m_parent); ~GraphicsWidget(); void clear(); /** * @brief True while a bulk clear() is in progress; false otherwise. * * GraphicsNode/GraphicsEdge destructors check this to skip per-item unlinking work * (edge-list removal from neighbours, individual scene/hash removal) that's pointless * during a bulk teardown - scene()->clear() destroys every item anyway, so proactively * unlinking each one from its neighbours' edge lists is pure wasted O(degree) work per * edge, repeated across the whole network. See #260. */ bool isClearing() const { return m_isClearing; } quint64 edgeKey(const int &v1, const int &v2, const int &relation=-1); void setInitNodeSize(int); void setInitZoomIndex (const int &); void setMaxZoomIndex (const int &); void setSelectedNodes(const QList &list); QList selectedItems(); QList selectedNodes(); QList selectedEdges(); void selectAll(); void selectNone(); void removeItem(GraphicsEdge*); void removeItem(GraphicsEdgeWeight *edgeWeight); void removeItem(GraphicsEdgeLabel *edgeLabel); void removeItem(GraphicsNode*); void removeItem(GraphicsNodeNumber*); void removeItem(GraphicsNodeLabel*); void setNumbersInsideNodes(const bool &toggle); void setAllItemsVisibility(int, bool); protected: void wheelEvent(QWheelEvent *event); void mouseDoubleClickEvent ( QMouseEvent * e ); void mousePressEvent ( QMouseEvent * e ); void mouseReleaseEvent(QMouseEvent * e ); void resizeEvent( QResizeEvent *e ); public slots: void handleSelectionChanged(); void relationSet(int relation); void drawNode(const QPointF &p, const int &num, const int &nodeSize, const QString &nodeShape, const QString &nodeIconPath, const QString &nodeColor, const QString &numberColor, const int &numberSize, const int &numberDistance, const QString &nodeLabel, const QString &labelColor, const int &labelSize, const int &labelDistance); void removeNode(const int &nodeNum); void setNodeVisibility(const int &nodeNum, const bool &toggle ); //Called from Graph via MW void setNodeClicked(GraphicsNode *); void moveNode(const int &num, const qreal &x, const qreal &y); bool setNodeSize(const int &nodeNumber, const int &size=0); bool setNodeShape(const int &nodeNum, const QString &shape, const QString &iconPath=QString()); bool setNodeColor(const int &nodeNum, const QString &color); void setNodeNumberColor(const int &nodeNum, const QString &color); void setNodeNumberVisibility(const bool &toggle); bool setNodeNumberSize(const int &nodeNum, const int &size=0); bool setNodeNumberDistance(const int &, const int &distance=0); void setNodeLabelsVisibility(const bool &toggle); bool setNodeLabelColor(const int &nodeNum, const QString &color="green"); bool setNodeLabelSize(const int &, const int &size=0); bool setNodeLabel(const int & , const QString &label); bool setNodeLabelDistance(const int &, const int &distance=0); void drawEdge(const int &source, const int &target, const qreal &weight, const QString &label="", const QString &color="black", const int &type=0, const bool &drawArrows=true, const bool &bezier=false, const bool &weightNumbers=false); void removeEdge(const int &source, const int &target, const bool &removeOpposite=false); void setEdgeVisibility (const int &relation, const int &sourceNum, const int &targetNum, const bool &visible, const bool &preserveReverseEdge=false, const int &edgeWeight=1, const int &reverseEdgeWeight=1); // Bulk counterpart of setEdgeVisibility (WS3 M2): applies a whole batch in one queued // dispatch instead of one per edge - see Graph::signalSetEdgesVisibilityBatch. void setEdgesVisibilityBatch(const QList &changes); bool setEdgeDirectionType(const int &source, const int &target, const int &dirType=false); bool setEdgeWeight(const int &, const int &, const qreal &); void setEdgeLabel(const int &source, const int &target, const QString &label); void setEdgeColor(const int &, const int&, const QString &); void setEdgeClicked(GraphicsEdge *, const bool &openMenu=false); void setEdgeOffsetFromNode(const int &source, const int &target, const int &offset); void setEdgeArrowsVisibility(const bool &toggle); void setEdgeArrowSize(const int &size); void setEdgeWeightNumbersVisibility (const bool &toggle); void setEdgeLabelsVisibility(const bool &toggle); void setEdgesBezier(const bool &toggle); void setEdgeHighlighting(const bool &toggle); void selectPath(const QList &path); void handleDoubleClickOnNode(GraphicsNode *node); void clearGuides(); void addGuideCircle( const double&x0, const double&y0, const double&radius); void addGuideHLine(const double &y0); void zoomIn(const int step = 1); void zoomOut(const int step = 1); void zoomToFit(); void rotateLeft(); void rotateRight(); void changeMatrixScale(const int value); void changeMatrixRotation(int angle); void reset(); void setOptionsOpenGL(const bool &enabled=false); void setOptionsAntialiasing(const bool &toggle); void setOptionsNoAntialiasingAutoAdjust(const bool &toggle); protected: void scrollContentsBy(int dx, int dy) override; signals: /** @brief Double-click on empty canvas space; MW creates a new node at this scene position. */ void userDoubleClickNewNode(const QPointF &); /** @brief Two consecutive middle-clicks (or double-clicks) on two different nodes; * MW/Graph creates a new edge from the first node to the second. */ void userMiddleClicked(const int &sourceNum, const int &targetNum, const qreal &weight=1); /** @brief Left-click on empty canvas space, at scene position p. */ void userClickOnEmptySpace(const QPointF &p); /** @brief Right-click on a node; MW opens the node context menu for the currently clicked node. */ void openNodeMenu(); /** @brief Right-click on empty canvas space, at scene position p; MW opens the general context menu. */ void openContextMenu(const QPointF p); /** @brief One node from the current selection finished being dragged to a new position * (nodeNumber, x, y); emitted once per selected node on mouse release. */ void userNodeMoved(const int &, const int &, const int &); /** @brief The full current node/edge selection, emitted whenever it changes * (single emission per change, not per item). */ void userSelectedItems(const QList selectedNodes, const QList selectedEdges); /** @brief A node was clicked (nodeNumber, scene position); also used with nodeNumber=0 * as a generic "selection changed" notification from selectAll()/selectNone(). */ void userClickedNode(const int &nodeNumber, const QPointF &p); /** @brief An edge was clicked (source, target); openMenu requests MW open its context menu. */ void userClickedEdge(const int &source, const int &target, const bool &openMenu=false); /** @brief Current zoom index changed; keeps the MW zoom slider in sync. */ void zoomChanged(const int); /** @brief Current rotation angle changed; keeps the MW rotation slider in sync. */ void rotationChanged(const int); /** @brief Canvas viewport was resized (debounced to fire once, 150ms after the last resize event). */ void resized(const int, const int); /** @brief Requests MW change the application cursor shape (e.g. while creating an edge). */ void setCursor(Qt::CursorShape); private: H_NumToNode nodeHash; //Our basic hash table for node items H_KeyToEdge edgesHash; // Our basic hash table for edge items QList m_selectedNodes; QList m_selectedEdges; int m_curRelation, m_nodeSize; int m_currentRotationAngle; int m_zoomIndex, m_zoomIndexInit, m_zoomIndexMax; int markedEdgeSourceOrigSize, markedEdgeTargetOrigSize; int m_edgeMinOffsetFromNode; int m_arrowSize; double m_currentScaleFactor; qreal fX,fY, factor; QString m_nodeLabel, m_numberColor, m_labelColor; quint64 m_edgeKey; QPointF m_viewCenter; bool m_viewCenterValid; bool m_isZooming; bool m_isTransformationActive; bool m_isClearing; bool hasDoubleClickedNode, clickedEdgeExists; bool m_nodeNumbersInside, m_nodeNumberVisibility, m_nodeLabelVisibility; bool m_edgeHighlighting; bool m_edgesBezier; QTimer m_resizeTimer; QList m_guides; GraphicsNode *firstDoubleClickedNode, *secondDoubleClickedNode; GraphicsNode *markedEdgeSource; GraphicsNode *markedEdgeTarget; GraphicsEdge *clickedEdge; }; #endif SocNetV-3.7/src/graphvertex.cpp000077500000000000000000000722231524402210300165400ustar00rootroot00000000000000/** * @file graphvertex.cpp * @brief Implements the GraphVertex class for representing and managing vertices in the network graph structure. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "graphvertex.h" #include //used for qDebug messages #include "graph.h" GraphVertex::GraphVertex(Graph* parentGraph, const int &name, const int &relation, const int &size, const QString &color, const QString &numColor, const int &numSize, const QString &label, const QString &labelColor, const int &labelSize, const QPointF &p, const QString &shape, const QString &iconPath, const int &edgesEstimate, const QHash &customAttributes ): m_graph (parentGraph) { qCDebug(lcGraphCore) << "vertex:"<< name << "initializing...edgesEstimate:" << edgesEstimate; m_number=name; m_size=size; m_color=color; m_numberColor=numColor; m_numberSize=numSize; m_label=label; m_labelColor=labelColor; m_labelSize=labelSize; m_shape=shape; m_iconPath=iconPath; m_x=p.x(); m_y=p.y(); // Use the given edges estimate to allocate memory // to prevent reallocations and memory fragmentation. if (edgesEstimate > 0) { //FIXME m_outLinkColors list need update when we remove vertices/edges //m_outLinkColors.reserve(edgesEstimate); m_outEdgeLabels.reserve(edgesEstimate); m_outEdges.reserve(edgesEstimate); m_inEdges.reserve(edgesEstimate); } m_customAttributes = customAttributes; m_outEdgesCounter = 0; m_inEdgesCounter = 0; m_outDegree = 0; m_inDegree = 0; m_localDegree = 0; m_outEdgesNonSym = 0; m_inEdgesNonSym = 0; m_outEdgesSym = 0; m_Eccentricity = 0; m_distanceSum = 0; m_DC=0; m_SDC=0; m_DP=0; m_SDP=0; m_CC=0; m_SCC=0; m_BC=0; m_SBC=0; m_SC=0; m_SSC=0; m_IRCC=0; m_SIRCC=0; m_EC=0; m_SEC=0; m_PC=0; m_SPC=0; m_IC=0; m_SIC=0; m_PRC=0; m_SPRC=0; m_PP=0; m_SPP=0; m_EVC=0; m_SEVC=0; m_KC=0; m_SKC=0; m_BPC=0; m_SBPC=0; m_CLC=0; m_hasCLC=false; m_curRelation=relation; m_enabled = true; } /** * @brief Returns the vertex number * @return */ int GraphVertex::number() const { return m_number; } /** * @brief Sets the vertex number * @param number */ void GraphVertex::setNumber(const int &number) { m_number=number; } /** * @brief Toggles the status of the vertex * @param flag */ void GraphVertex::setEnabled(const bool &status) { m_enabled=status; } /** * @brief Returns true if the vertex is enabled * @return bool */ bool GraphVertex::isEnabled() const { return m_enabled; } /** * @brief Sets the size of the vertex * @param size */ void GraphVertex::setSize(const int &size ) { m_size=size; } /** * @brief Returns the size of the vertex * @return */ int GraphVertex::size() const { return m_size; } /** * @brief Sets the shape of the vertex * @param shape * @param iconPath */ void GraphVertex::setShape(const QString &shape, const QString &iconPath) { m_shape=shape; m_iconPath=iconPath; } /** * @brief Returns the shape of the vertex * @return */ QString GraphVertex::shape() const { return m_shape; } /** * @brief Returns the path of the custom vertex icon * @return */ QString GraphVertex::shapeIconPath() { return m_iconPath; } /** * @brief Sets the vertex color * @param color */ void GraphVertex::setColor(const QString &color) { m_color=color; } /** * @brief Returns the vertex color * @return QString */ QString GraphVertex::color() const { return m_color; } /** * @brief Returns the vertex color in pajek format */ QString GraphVertex::colorToPajek(){ if (m_color.startsWith("#")) { return ("RGB"+m_color.right( m_color.size()-1 )).toUpper() ; } return m_color; } /** * @brief Sets the color of the vertex number * @param color */ void GraphVertex::setNumberColor (const QString &color) { m_numberColor = color; } /** * @brief Returns the color of the vertex number * @return */ QString GraphVertex::numberColor() const { return m_numberColor; } /** * @brief Sets the size of the vertex number * @param size */ void GraphVertex::setNumberSize (const int &size) { m_numberSize=size; } /** * @brief Returns the size of the vertex number * @return */ int GraphVertex::numberSize() const { return m_numberSize; } /** * @brief Sets the distance (in pixels) of the vertex number from the vertex * @param distance */ void GraphVertex::setNumberDistance (const int &distance) { m_numberDistance=distance; } /** * @brief Returns the distance (in pixels) of the vertex number from the vertex * @return */ int GraphVertex::numberDistance() const { return m_numberDistance; } /** * @brief Sets the label of the vertex * @param label */ void GraphVertex::setLabel (const QString &label) { m_label=label; } /** * @brief Returns the vertex label * @return */ QString GraphVertex::label() const { return m_label; } /** * @brief Sets the color of the vertex label * @param labelColor */ void GraphVertex::setLabelColor (const QString &labelColor) { m_labelColor=labelColor; } /** * @brief Returns the color of the vertex label * @return */ QString GraphVertex::labelColor() const { return m_labelColor; } /** * @brief Sets the size of the vertex label * @param size */ void GraphVertex::setLabelSize(const int &size) { m_labelSize=size; } /** * @brief Returns the size of the vertex label * @return */ int GraphVertex::labelSize() const { return m_labelSize; } /** * @brief Sets the distance (in pixels) of the label from the vertex * @param distance */ void GraphVertex::setLabelDistance (const int &distance) { m_labelDistance=distance; } /** * @brief Returns the distance (in pixels) of the label from the vertex * @return */ int GraphVertex::labelDistance() const { return m_labelDistance; } /** * @brief Sets custom attributes for the graph vertex. * * The custom attributes are provided as a QHash with QString keys and values. * * @param customAttributes A QHash containing the custom attributes to be set. */ void GraphVertex::setCustomAttributes(QHash customAttributes){ m_customAttributes = customAttributes; } /** * @brief Returns the custom attributes of the graph vertex. * * The custom attributes are returned as a QHash with QString keys and values. * * @return A QHash containing the custom attributes of the graph vertex. */ QHash GraphVertex::customAttributes() const { return m_customAttributes; } /** * @brief Sets the horizontal position (in pixels) of the vertex * @param x */ void GraphVertex::setX(const qreal &x) { m_x=x; } /** * @brief Returns the horizontal position (in pixels) of the vertex * @return */ qreal GraphVertex::x() const { return m_x; } /** * @brief Sets the vertical position (in pixels) of the vertex * @param y */ void GraphVertex::setY(const qreal &y) { m_y=y; } /** * @brief Returns the vertical position (in pixels) of the vertex * @return */ qreal GraphVertex::y() const { return m_y; } /** * @brief Sets the point where the vertex is positioned * @param p */ void GraphVertex::setPos (QPointF &p) { m_x=p.x(); m_y=p.y(); } /** * @brief Returns the point where the vertex is positioned * @return */ QPointF GraphVertex::pos () const { return QPointF ( x(), y() ); } /** * @brief Sets the x coordinate of the displacement vector * @param x */ void GraphVertex::set_dispX (qreal x) { m_disp.rx() = x ; } /** * @brief Sets the y coordinate of the displacement vector * @param y */ void GraphVertex::set_dispY (qreal y) { m_disp.ry() = y ; } /** * @brief Returns displacement vector * @return */ QPointF& GraphVertex::disp() { return m_disp; } /** * @brief Changes the current relation of this vertex * * @param newRel */ QList GraphVertex::setRelation(int newRel) { // first make false all edges of current relation QList changes = setEnabledEdgesByRelation(m_curRelation, false); // then make true all edges of new relation changes.append(setEnabledEdgesByRelation(newRel, true)); // update current relation m_curRelation=newRel; return changes; } /** * @brief Adds an outbound edge to vertex v2 with weight w * * @param target * @param weight */ void GraphVertex::addOutEdge (const int &v2, const qreal &weight, const QString &color, const QString &label) { // do not use [] operator - silently creates an item if key do not exist m_outEdges.insert( v2, pair_i_fb(m_curRelation, pair_f_b(weight, true) ) ); setOutLinkColor(v2, color); setOutEdgeLabel(v2, label); } /** * @brief Checks if the vertex has an enabled outbound edge to the given vertex. Returns the edge weight or 0. * * If allRelations is true, then all relations are checked * * @param v2 * @param allRelations * @return qreal */ qreal GraphVertex::hasEdgeTo(const int &v2, const bool &allRelations){ qreal m_weight=0; bool edgeStatus=false; H_edges::const_iterator it1=m_outEdges.constFind(v2); while (it1 != m_outEdges.constEnd() && it1.key() == v2 ) { if (!allRelations) { if ( it1.value().first == m_curRelation ) { edgeStatus=it1.value().second.second; if ( edgeStatus == true) { m_weight=it1.value().second.first; } return m_weight; } } else { m_weight=it1.value().second.first; return m_weight; } ++it1; } return m_weight; } /** * @brief Removes the outbound edge to vertex v2 * * @param v2 */ void GraphVertex::removeOutEdge (const int v2) { if (outEdgesCount() == 0) { return; } H_edges::const_iterator it1=m_outEdges.constFind(v2); while (it1 != m_outEdges.constEnd() && it1.key() == v2 ) { if ( it1.value().first == m_curRelation ) { m_outEdges.erase(it1); break; } ++it1; } } /** * @brief Sets the status of an outbound edge to the given target vertex * * @param target * @param status */ void GraphVertex::setOutEdgeEnabled (const int &target, bool status){ int linkTarget=0; qreal weight =0; int relation = 0; H_edges::iterator it1; for ( it1 = m_outEdges.begin(); it1 != m_outEdges.end(); ++ it1) { relation = it1.value().first; if ( relation == m_curRelation ) { linkTarget=it1.key(); if ( linkTarget == target ) { weight = it1.value().second.first; it1.value() = pair_i_fb(m_curRelation, pair_f_b(weight, status) ); m_graph->notifyEdgeVisibilityChanged(m_curRelation, m_number, target, status); break; } } } } /** * @brief Sets the weight of the outbound edge to the given vertex * * @param target * @param weight */ void GraphVertex::setOutEdgeWeight(const int &target, const qreal &weight){ H_edges::const_iterator it1=m_outEdges.constFind(target); // Find the current edge, remove it and add an updated one. while (it1 != m_outEdges.constEnd() ) { if ( it1.key() == target && it1.value().first == m_curRelation ) { m_outEdges.erase(it1); break; } ++it1; } // Insert the updated edge m_outEdges.insert( target, pair_i_fb(m_curRelation, pair_f_b(weight, true) ) ); } /** * @brief Sets the color of the outbound edge to the given vertex * @param v2 * @param color */ void GraphVertex::setOutLinkColor(const int &v2, const QString &color) { m_outLinkColors[v2]=color; } /** * @brief Returns the color of the outbound edge to the given vertex * @param v2 * @return */ QString GraphVertex::outLinkColor(const int &v2) { return ( m_outLinkColors.contains(v2) ) ? m_outLinkColors.value(v2) : "black"; } /** * @brief Sets the label of the outbound edge to the given vertex * @param v2 * @param label */ void GraphVertex::setOutEdgeLabel(const int &v2, const QString &label) { m_outEdgeLabels[v2]=label; } /** * @brief Returns the label of the outbound edge to the given vertex * @param v2 * @return */ QString GraphVertex::outEdgeLabel(const int &v2) const { return ( m_outEdgeLabels.contains(v2) ) ? m_outEdgeLabels.value(v2) : QString(); } /** * @brief Sets all custom attributes on the outbound edge to vertex @p v2, * replacing any previously stored attributes for that edge. * @param v2 Target vertex number. * @param attrs Key/value map of custom attributes. */ void GraphVertex::setOutEdgeCustomAttributes(const int &v2, const QHash &attrs) { m_outEdgeCustomAttributes[v2] = attrs; } /** * @brief Returns the custom attributes stored on the outbound edge to vertex @p v2. * Returns an empty hash if no attributes have been set for that edge. * @param v2 Target vertex number. */ QHash GraphVertex::outEdgeCustomAttributes(const int &v2) const { return m_outEdgeCustomAttributes.value(v2); } /** * @brief Adds an inbound edge from vertex v1 * * @param source * @param weight */ void GraphVertex::addInEdge (const int &v1, const qreal &weight) { m_inEdges.insert( v1, pair_i_fb (m_curRelation, pair_f_b(weight, true) ) ); } /** * @brief Checks if the vertex has an enabled inbound edge from v2 and returns the edge weight or 0. * * If allRelations is true, then all relations are checked * * @param v2 * @return */ qreal GraphVertex::hasEdgeFrom(const int &v2, const bool &allRelations){ qreal m_weight=0; bool edgeStatus=false; H_edges::const_iterator it1=m_inEdges.constFind(v2); while (it1 != m_inEdges.constEnd() && it1.key() == v2) { if (!allRelations) { if ( it1.value().first == m_curRelation ) { edgeStatus=it1.value().second.second; if ( edgeStatus == true) { m_weight=it1.value().second.first; return m_weight; } return m_weight; } } else { m_weight=it1.value().second.first; return m_weight; } ++it1; } return m_weight; } /** * @brief Removes the inbound edge from vertex v2 * * @param v2 */ void GraphVertex::removeInEdge(const int v2){ if (inEdgesCount()==0) { return; } H_edges::const_iterator it=m_inEdges.constFind(v2); while (it != m_inEdges.constEnd() ) { if ( it.key() == v2 && it.value().first == m_curRelation ) { m_inEdges.erase(it); break; } ++it; } } /** * @brief Sets the status of an inbound edge from the given source vertex * * @param source * @param status */ void GraphVertex::setInEdgeEnabled (const int &source, bool status){ int linkTarget=0; qreal weight =0; int relation = 0; H_edges::iterator it1; for ( it1 = m_inEdges.begin(); it1 != m_inEdges.end(); ++ it1) { relation = it1.value().first; if ( relation == m_curRelation ) { linkTarget=it1.key(); if ( linkTarget == source ) { weight = it1.value().second.first; it1.value() = pair_i_fb(m_curRelation, pair_f_b(weight, status) ); m_graph->notifyEdgeVisibilityChanged(m_curRelation, source, m_number, status); break; } } } } /** * @brief Sets the weight of the inbound edge from the given vertex * * @param source * @param weight */ void GraphVertex::setInEdgeWeight(const int &source, const qreal &weight){ H_edges::const_iterator it1=m_inEdges.constFind(source); // Find the current edge, remove it and add an updated one. while (it1 != m_inEdges.constEnd() ) { if ( it1.key() == source && it1.value().first == m_curRelation ) { m_inEdges.erase(it1); break; } ++it1; } // Insert the updated edge m_inEdges.insert( source, pair_i_fb(m_curRelation, pair_f_b(weight, true) ) ); } /** * @brief Computes and returns the number of active outbound arcs (outEdges) * for the current relation. * * This counts enabled outbound adjacency entries in m_outEdges for m_curRelation. * Graph::edgesEnabled() sums this over all vertices and then: * - divides by 2 if the graph is undirected (because undirected edges are stored * internally as symmetric arcs), * - leaves it as-is if the graph is directed. * * TODO / THINK: Self-loops (v->v) * - If loops are stored and enabled, they will be counted here. * - Decide if loops should be excluded from certain graph-level totals (e.g., density), * or supported consistently with loop-aware formulas. * * @return int */ int GraphVertex::outEdgesCount() { m_outEdgesCounter = 0; H_edges::const_iterator it1 = m_outEdges.constBegin(); while (it1 != m_outEdges.constEnd()) { const int relation = it1.value().first; if (relation == m_curRelation) { const bool edgeStatus = it1.value().second.second; // Fix #30: zero-weight edges are stored but must not count as // structural edges (density, reachability, etc. would be wrong). const qreal edgeWeight = it1.value().second.first; if (edgeStatus && edgeWeight != 0) { m_outEdgesCounter++; } } ++it1; } return m_outEdgesCounter; } /** * @brief Returns the number of active outbound arcs. Avoid using it alone. * * WARNING: You need to compute m_outEdgesCounter before calling this method * * @return int */ int GraphVertex::outEdgesCountConst() const { return m_outEdgesCounter; } /** * @brief Returns the number of active inbound arcs to this vertex for the current relation * * @return int */ int GraphVertex::inEdgesCount() { m_inEdgesCounter = 0; int relation=0; bool edgeStatus = false; H_edges::const_iterator it1=m_inEdges.constBegin(); while (it1 != m_inEdges.constEnd() ) { relation = it1.value().first; if ( relation == m_curRelation ) { edgeStatus=it1.value().second.second; if ( edgeStatus == true) { m_inEdgesCounter++; } } ++it1; } return m_inEdgesCounter; } /** * @brief Returns the number of active inbound arcs * * WARNING: Needs to have inEdges called before the call to this method * * @return int */ int GraphVertex::inEdgesCountConst() const { return m_inEdgesCounter; } /** * @brief Returns true if the vertex has at least one outEdge * @return bool */ bool GraphVertex::isOutLinked() { return (outEdgesCount() > 0) ? true:false; } /** * @brief Returns true if there is an outEdge from this vertex * @return */ bool GraphVertex::isInLinked() { return (inEdgesCount() > 0) ? true:false; } /** * @brief Toggles this vertex as isolated or not. * @param isolated */ void GraphVertex::setIsolated(bool isolated) { m_isolated = isolated; } /** * @brief Returns true if the vertex is isolated (no inbound our outbound edges) * @return bool */ bool GraphVertex::isIsolated() { return !(isOutLinked() || isInLinked()) ; } /** * @brief Returns a qhash of all enabled outEdges, in the active relation or all relations if allRelations is true. * * @param allRelations * * @return QHash* */ QHash GraphVertex::outEdgesEnabledHash(const bool &allRelations){ QHash enabledOutEdges; qreal m_weight=0; int relation = 0; bool edgeStatus=false; H_edges::const_iterator it1=m_outEdges.constBegin(); while (it1 != m_outEdges.constEnd() ) { relation = it1.value().first; if (!allRelations) { if ( relation == m_curRelation ) { edgeStatus=it1.value().second.second; if ( edgeStatus == true) { m_weight=it1.value().second.first; enabledOutEdges.insert(it1.key(), m_weight); } } } else { if ( !enabledOutEdges.contains(it1.key() )) { edgeStatus=it1.value().second.second; if ( edgeStatus == true) { m_weight=it1.value().second.first; enabledOutEdges.insert(it1.key(), m_weight); } } } ++it1; } return enabledOutEdges; } /** * @brief Returns a qhash of all edges to neighbors in all relations * * @return */ QHash* GraphVertex::outEdgesAllRelationsUniqueHash() { QHash *outEdgesAll = new QHash; qreal m_weight=0; H_edges::const_iterator it1=m_outEdges.constBegin(); while (it1 != m_outEdges.constEnd() ) { if ( !outEdgesAll->contains(it1.key() )) { m_weight=it1.value().second.first; outEdgesAll->insert(it1.key(), m_weight); } ++it1; } return outEdgesAll; } /** * @brief Returns a qhash of all reciprocal edges to neighbors in the active relation * * @return QHash* */ QHash GraphVertex::reciprocalEdgesHash(){ m_reciprocalEdges.clear(); qreal m_weight=0; int relation = 0; bool edgeStatus=false; H_edges::const_iterator it1=m_outEdges.constBegin(); while (it1 != m_outEdges.constEnd() ) { relation = it1.value().first; if ( relation == m_curRelation ) { edgeStatus=it1.value().second.second; if ( edgeStatus == true) { m_weight=it1.value().second.first; if (this->hasEdgeFrom (it1.key()) == m_weight ) { m_reciprocalEdges.insert(it1.key(), m_weight); } } } ++it1; } return m_reciprocalEdges; } /** * @brief Returns a list of all vertices reciprocally connected to this vertex * in the active relation. * * A vertex is included only if: * - There is an enabled out-edge from this vertex to it in the current relation. * - There is a reciprocal in-edge from it back to this vertex. * - The reverse edge weight is non-zero (existence check, not exact equality). * - The vertex is not this vertex itself (no self-loops). * * Note: this function returns MUTUAL neighbors only. For general 1-hop * neighbors (out-edges only, regardless of reciprocity), use a direct * iteration over m_outEdges instead. * * @return QList of vertex numbers reciprocally connected to this vertex. */ QList GraphVertex::reciprocalNeighborhoodList() { QList result; H_edges::const_iterator it1 = m_outEdges.constBegin(); while (it1 != m_outEdges.constEnd()) { if (it1.value().first == m_curRelation && it1.value().second.second == true && it1.key() != this->number() && this->hasEdgeFrom(it1.key()) != 0) { result << it1.key(); } ++it1; } return result; } /** * @brief Returns a qhash of all enabled inEdges in the active relation * * @return QHash* */ QHash* GraphVertex::inEdgesEnabledHash() { QHash *enabledInEdges = new QHash; qreal m_weight=0; int relation = 0; bool edgeStatus=false; H_edges::const_iterator it1=m_inEdges.constBegin(); while (it1 != m_inEdges.constEnd() ) { relation = it1.value().first; if ( relation == m_curRelation ) { edgeStatus=it1.value().second.second; if ( edgeStatus == true) { m_weight=it1.value().second.first; enabledInEdges->insert(it1.key(), m_weight); } } ++it1; } return enabledInEdges; } /** * @brief Returns the outDegree (the sum of all enabled outEdges weights) of this vertex * * @return int */ int GraphVertex::degreeOut() { m_outDegree=0; qreal m_weight=0; int relation = 0; bool edgeStatus=false; H_edges::const_iterator it1=m_outEdges.constBegin(); while (it1 != m_outEdges.constEnd() ) { relation = it1.value().first; if ( relation == m_curRelation ) { edgeStatus=it1.value().second.second; if ( edgeStatus == true) { m_weight=it1.value().second.first; m_outDegree += m_weight; } } ++it1; } return m_outDegree; } /** * @brief Returns the outDegree. Avoid using it alone. * * @return int */ int GraphVertex::outDegreeConst() { return m_outDegree; } /** * @brief Returns the indegree (the sum of all enabled inEdges weights) of this vertex * * @return int */ int GraphVertex::degreeIn() { m_inDegree=0; qreal m_weight=0; int relation = 0; bool edgeStatus=false; H_edges::const_iterator it1=m_inEdges.constBegin(); while (it1 != m_inEdges.constEnd() ) { relation = it1.value().first; if ( relation == m_curRelation ) { edgeStatus=it1.value().second.second; if ( edgeStatus == true) { m_weight=it1.value().second.first; m_inDegree += m_weight; } } ++it1; } return m_inDegree; } /** * @brief Returns the indegree. Avoid using it alone. * * @return int */ int GraphVertex::inDegreeConst() { return m_inDegree; } /** * @brief Returns the localDegree of the vertex. * * The localDegree is the degreeOut + degreeIn minus the edges counted twice. * * @return */ int GraphVertex::localDegree(){ int v2=0; int relation = 0; bool edgeStatus=false; m_localDegree = (degreeOut() + degreeIn() ); H_edges::const_iterator it1=m_outEdges.constBegin(); while (it1 != m_outEdges.constEnd() ) { relation = it1.value().first; if ( relation == m_curRelation ) { edgeStatus=it1.value().second.second; if ( edgeStatus == true) { v2=it1.key(); if (this->hasEdgeFrom (v2) ) m_localDegree--; } } ++it1; } return m_localDegree; } /** * @brief Changes the status of all unilateral (non-reciprocal) outbound edges, in current relation * * @param status */ QList GraphVertex::setEnabledUnilateralEdges(const bool &status){ QList changes; int target=0; qreal weight=0; H_edges::iterator it; for ( it = m_outEdges.begin(); it != m_outEdges.end(); ++it) { if ( it.value().first == m_curRelation ) { target=it.key(); weight = it.value().second.first; if (hasEdgeFrom(target)==0) { it.value() = pair_i_fb(m_curRelation, pair_f_b(weight, status) ); changes.append(EdgeVisibilityChange(m_curRelation, m_number, target, status)); } } } return changes; } /** * @brief Changes the status of all edges in the given relation * * @param relation * @param status */ QList GraphVertex::setEnabledEdgesByRelation(const int relation, const bool status ){ QList changes; int target=0; qreal weight =0; int edgeRelation=0; H_edges::iterator it1; for ( it1 = m_outEdges.begin(); it1 != m_outEdges.end(); ++ it1) { edgeRelation = it1.value().first; if ( edgeRelation == relation ) { target=it1.key(); weight = it1.value().second.first; it1.value() = pair_i_fb(relation, pair_f_b(weight, status) ); changes.append(EdgeVisibilityChange(relation, m_number, target, status)); } } return changes; } /** * @brief Stores the eccentricity of the vertex * @param c */ void GraphVertex::setEccentricity(const qreal &c){ m_Eccentricity=c; } /** * @brief Returns the stored eccentricity of the vertex * @return */ qreal GraphVertex::eccentricity() { return m_Eccentricity; } /** * @brief Returns the number of cliques sized size this vertex belongs to * * @param size * @return */ int GraphVertex::cliques (const int &ofSize) { return m_cliques.values( ofSize ).size(); } /** * @brief Adds clique to my cliques * * @param clique */ void GraphVertex::cliqueAdd (const QList &clique) { m_cliques.insert(clique.size(), clique); } GraphVertex::~GraphVertex() { m_outEdges.clear(); m_outEdges.squeeze(); m_inEdges.clear(); m_inEdges.squeeze(); m_reciprocalEdges.clear(); m_reciprocalEdges.squeeze(); m_outLinkColors.clear(); m_outLinkColors.squeeze(); m_outEdgeLabels.clear(); m_outEdgeLabels.squeeze(); m_outEdgeCustomAttributes.clear(); m_cliques.clear(); m_cliques.squeeze(); } SocNetV-3.7/src/graphvertex.h000077500000000000000000000276111524402210300162060ustar00rootroot00000000000000/** * @file graphvertex.h * @brief Declares the GraphVertex class for representing and managing vertices in the network graph structure. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef GRAPHVERTEX_H #define GRAPHVERTEX_H #include #include #include #include #include #include #include #include #include "global.h" SOCNETV_USE_NAMESPACE using namespace std; class Graph; typedef QList L_int; typedef QHash H_IntToStr; typedef QHash H_StrToInt; typedef QPair pair_f_b; typedef QPair pair_i_fb; typedef QMultiHash < int, pair_i_fb > H_edges; /** * @class GraphVertex * @brief Plain QtCore value class (not a QObject, WS3 M2) representing a vertex and its * adjacency. Notifies the UI layer of edge-visibility changes via a plain call to its * owning Graph (Graph::notifyEdgeVisibilityChanged / notifyEdgesVisibilityBatch) rather * than emitting its own signal. */ class GraphVertex { public: GraphVertex(Graph* parentGraph, const int &name, const int &relation, const int &size, const QString &color, const QString &numColor, const int &numSize, const QString &label, const QString &labelColor, const int &labelSize, const QPointF &p, const QString &shape, const QString &iconPath, const int &edgesEstimate = 2000, const QHash &nodeAttr = QHash() ); ~GraphVertex(); int number() const; void setNumber(const int &number); void setEnabled(const bool &status); bool isEnabled () const; void setSize(const int &size ); int size() const; void setShape(const QString &shape, const QString &iconPath = QString()); QString shape() const; QString shapeIconPath(); void setColor(const QString &color); QString color() const; QString colorToPajek(); void setNumberColor (const QString &color); QString numberColor() const; void setNumberSize (const int &size); int numberSize() const; void setNumberDistance (const int &distance); int numberDistance() const; void setLabel (const QString &label); QString label() const; void setLabelColor (const QString &labelColor); QString labelColor() const; void setLabelSize(const int &size); int labelSize() const; void setLabelDistance (const int &distance); int labelDistance() const; void setCustomAttributes(QHash customAttributes); QHash customAttributes() const; void setX(const qreal &x); qreal x() const; void setY(const qreal &y); qreal y() const; void setPos (QPointF &p); QPointF pos () const; void set_dispX (qreal x); void set_dispY (qreal y); QPointF & disp(); QList setRelation(int newRel); void addOutEdge (const int &v2, const qreal &weight, const QString &color=QString(), const QString &label=QString()); qreal hasEdgeTo(const int &v, const bool &allRelations=false); void removeOutEdge (const int target); void setOutEdgeWeight (const int &target, const qreal &weight); void setOutEdgeEnabled (const int &target, bool); void setOutLinkColor(const int &v2, const QString &color); QString outLinkColor(const int &v2); void setOutEdgeLabel(const int &v2, const QString &label); QString outEdgeLabel(const int &v2) const; void setOutEdgeCustomAttributes(const int &v2, const QHash &attrs); QHash outEdgeCustomAttributes(const int &v2) const; void addInEdge(const int &v1, const qreal &weight); qreal hasEdgeFrom (const int &v, const bool &allRelations=false); void removeInEdge(const int source); void setInEdgeWeight (const int &source, const qreal &weight); void setInEdgeEnabled (const int &source, bool); int outEdgesCount(); int outEdgesCountConst() const ; int inEdgesCount(); int inEdgesCountConst() const ; bool isOutLinked(); bool isInLinked(); void setIsolated(bool isolated); bool isIsolated(); const H_edges& outEdges() const { return m_outEdges; } QHash outEdgesEnabledHash(const bool &allRelations=false); QHash* outEdgesAllRelationsUniqueHash(); const H_edges& inEdges() const { return m_inEdges; } QHash* inEdgesEnabledHash(); QHash reciprocalEdgesHash(); QList reciprocalNeighborhoodList(); int degreeOut(); int outDegreeConst(); int degreeIn(); int inDegreeConst(); int localDegree(); QList setEnabledEdgesByRelation(const int relation, const bool status); QList setEnabledUnilateralEdges(const bool &status=false); void setEccentricity(const qreal &c); qreal eccentricity(); //used in reciprocity report void setOutEdgesReciprocated(int outEdgesSym=-1) { m_outEdgesSym = (outEdgesSym!=-1) ? outEdgesSym : m_outEdgesSym+1; } int outEdgesReciprocated() { return m_outEdgesSym; } void setOutEdgesNonSym(int outEdgesNonSym=-1) { m_outEdgesNonSym = (outEdgesNonSym!=-1) ? outEdgesNonSym : m_outEdgesNonSym+1; } int outEdgesNonSym() { return m_outEdgesNonSym; } void setInEdgesNonSym(int inEdgesNonSym=-1) { m_inEdgesNonSym = (inEdgesNonSym!=-1) ? inEdgesNonSym : m_inEdgesNonSym+1; } int inEdgesNonSym() { return m_inEdgesNonSym; } void setDC (const qreal &c){ m_DC=c;} /* Sets vertex Degree Centrality*/ void setSDC (const qreal &c ) { m_SDC=c;} /* Sets standard vertex Degree Centrality*/ qreal DC() { return m_DC;} /* Returns vertex Degree Centrality*/ qreal SDC() { return m_SDC;} /* Returns standard vertex Degree Centrality*/ void setDistanceSum (const qreal &c) { m_distanceSum = c; } qreal distanceSum () { return m_distanceSum; } void setCC (const qreal &c){ m_CC=c;} /* sets vertex Closeness Centrality*/ void setSCC (const qreal &c ) { m_SCC=c;} /* sets standard vertex Closeness Centrality*/ qreal CC() { return m_CC;} /* Returns vertex Closeness Centrality*/ qreal SCC() { return m_SCC; } /* Returns standard vertex Closeness Centrality*/ void setIRCC (const qreal &c){ m_IRCC=c;} /* sets vertex IRCC */ void setSIRCC (const qreal &c ) { m_SIRCC=c;} /* sets standard vertex IRCC */ qreal IRCC() { return m_IRCC;} /* Returns vertex IRCC */ qreal SIRCC() { return m_SIRCC; } /* Returns standard vertex IRCC*/ void setBC(const qreal &c){ m_BC=c;} /* sets s vertex Betweenness Centrality*/ void setSBC (const qreal &c ) { m_SBC=c;} /* sets standard vertex Betweenness Centrality*/ qreal BC() { return m_BC;} /* Returns vertex Betweenness Centrality*/ qreal SBC() { return m_SBC; } /* Returns standard vertex Betweenness Centrality*/ void setSC (const qreal &c){ m_SC=c;} /* sets vertex Stress Centrality*/ void setSSC (const qreal &c ) { m_SSC=c;} /* sets standard vertex Stress Centrality*/ qreal SC() { return m_SC;} /* Returns vertex Stress Centrality*/ qreal SSC() { return m_SSC; } /* Returns standard vertex Stress Centrality*/ void setEC(const qreal &dist) { m_EC=dist;} /* Sets max Geodesic Distance to all other vertices*/ void setSEC(const qreal &c) {m_SEC=c;} qreal EC() { return m_EC;} /* Returns max Geodesic Distance to all other vertices*/ qreal SEC() { return m_SEC;} void setPC (const qreal &c){ m_PC=c;} /* sets vertex Power Centrality*/ void setSPC (const qreal &c ) { m_SPC=c;} /* sets standard vertex Power Centrality*/ qreal PC() { return m_PC;} /* Returns vertex Power Centrality*/ qreal SPC() { return m_SPC; } /* Returns standard vertex Power Centrality*/ void setIC (const qreal &c){ m_IC=c;} /* sets vertex Information Centrality*/ void setSIC (const qreal &c ) { m_SIC=c;} /* sets standard vertex Information Centrality*/ qreal IC() { return m_IC;} /* Returns vertex Information Centrality*/ qreal SIC() { return m_SIC; } /* Returns standard vertex Information Centrality*/ void setDP (const qreal &c){ m_DP=c;} /* Sets vertex Degree Prestige */ void setSDP (const qreal &c ) { m_SDP=c;} /* Sets standard vertex Degree Prestige */ qreal DP() { return m_DP;} /* Returns vertex Degree Prestige */ qreal SDP() { return m_SDP;} /* Returns standard vertex Degree Prestige */ void setPRP (const qreal &c){ m_PRC=c;} /* sets vertex PageRank*/ void setSPRP (const qreal &c ) { m_SPRC=c;} /* sets standard vertex PageRank*/ qreal PRP() { return m_PRC;} /* Returns vertex PageRank */ qreal SPRP() { return m_SPRC; } /* Returns standard vertex PageRank*/ void setPP (const qreal &c){ m_PP=c;} /* sets vertex Proximity Prestige */ void setSPP (const qreal &c ) { m_SPP=c;} /* sets standard vertex Proximity Prestige */ qreal PP() { return m_PP;} /* Returns vertex Proximity Prestige */ qreal SPP() { return m_SPP; } /* Returns standard vertex Proximity Prestige */ qreal CLC() { return m_CLC; } void setCLC(const qreal &clucof) { m_CLC=clucof; m_hasCLC=true; } bool hasCLC() { return m_hasCLC; } void setEVC (const qreal &c){ m_EVC=c;} /* Sets vertex Degree Centrality*/ void setSEVC (const qreal &c ) { m_SEVC=c;} /* Sets standard vertex Degree Centrality*/ qreal EVC() { return m_EVC;} /* Returns vertex Degree Centrality*/ qreal SEVC() { return m_SEVC;} /* Returns standard vertex Degree Centrality*/ void setKC (const qreal &c){ m_KC=c;} /* Sets vertex Katz Centrality */ void setSKC (const qreal &c ) { m_SKC=c;} /* Sets standard vertex Katz Centrality */ qreal KC() { return m_KC;} /* Returns vertex Katz Centrality */ qreal SKC() { return m_SKC;} /* Returns standard vertex Katz Centrality */ void setBPC (const qreal &c){ m_BPC=c;} /* Sets vertex Bonacich Power Centrality */ void setSBPC (const qreal &c ) { m_SBPC=c;} /* Sets standard vertex Bonacich Power Centrality */ qreal BPC() { return m_BPC;} /* Returns vertex Bonacich Power Centrality */ qreal SBPC() { return m_SBPC;} /* Returns standard vertex Bonacich Power Centrality */ int cliques (const int &ofSize); void cliqueAdd (const QList &clique); void clearCliques() { m_cliques.clear(); } //Hashes of all outbound and inbound edges of this vertex. H_edges m_outEdges, m_inEdges; protected: private: Graph *m_graph; int m_number, m_outEdgesCounter, m_inEdgesCounter, m_outDegree, m_inDegree, m_localDegree; int m_outEdgesNonSym, m_inEdgesNonSym, m_outEdgesSym; int m_size, m_labelSize, m_numberSize, m_numberDistance, m_labelDistance; int m_curRelation; bool m_reciprocalLinked, m_enabled, m_hasCLC, m_isolated; double m_x, m_y; qreal m_Eccentricity, m_CLC; qreal m_EC, m_SEC; qreal m_DC, m_SDC, m_DP, m_SDP, m_CC, m_SCC, m_BC, m_SBC, m_IRCC, m_SIRCC, m_SC, m_SSC; qreal m_PC, m_SPC, m_SIC, m_IC, m_SPRC, m_PRC; qreal m_PP, m_SPP, m_EVC, m_SEVC; qreal m_KC, m_SKC, m_BPC, m_SBPC; qreal m_distanceSum; QString m_color, m_numberColor, m_label, m_labelColor, m_shape, m_iconPath; QPointF m_disp; QHash m_customAttributes; QHash m_reciprocalEdges; QMultiHash m_cliques; H_IntToStr m_outLinkColors, m_outEdgeLabels; QHash> m_outEdgeCustomAttributes; //FIXME vertex coords }; #endif SocNetV-3.7/src/icon.rc000077500000000000000000000012231524402210300147430ustar00rootroot000000000000001 VERSIONINFO FILEVERSION 3,2,0,0 PRODUCTVERSION 3,2,0,0 BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904B0" // Locale and codepage: 0409 (English - United States), 04B0 (Unicode) BEGIN VALUE "CompanyName", "socnetv.org" VALUE "FileDescription", "SocNetV: Open-source social network analysis application based on Qt." VALUE "ProductName", "Social Network Visualizer" VALUE "LegalCopyright", "Copyright (C) 2025 Dimitris B. Kalamaras. GPLv3." END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x0409, 1200 END END IDI_ICON1 ICON "images/socnetv.ico" SocNetV-3.7/src/images.qrc000077500000000000000000000163561524402210300154560ustar00rootroot00000000000000 images/new.png images/open.png images/save.png images/saved.png images/exit.png images/print.png images/dm.png images/socnetv.png images/net.png images/net1.png images/box.png images/circle.png images/diamond.png images/triangle.png images/ellipse.png images/view.png images/distance.png images/sw.png images/erdos.png images/circular.png images/clique.png images/walk.png images/triad.png images/avdistance.png images/symmetrize.png images/gridlines.png images/prestige.png images/centrality.png images/eccentricity.png images/random.png images/force.png images/scalefree.png images/pdf.png images/socnetv-logo.png images/star.png images/help-hint.png images/bugs.png images/qt.png images/petersengraph.png images/cliquenew.png images/subgraphline.png images/subgraphcycle.png images/clustering.png images/adjacencyplot.png images/similarity.png images/distances.png images/connectivity.png images/hierarchical.png images/clucof.png images/symmetry-edge.png images/laplacian.png images/degreematrix.png images/invertmatrix.png images/transposematrix.png images/cocitation.png qss/default.qss images/arrow_down_48px.svg images/filter_list_48px.svg images/print_48px.svg images/file_download_48px.svg images/refresh_48px.svg images/settings_48px.svg images/rotate_right_48px.svg images/rotate_left_48px.svg images/open_48px.svg images/new_folder_48px.svg images/chevron_right_48px.svg images/chevron_left_48px.svg images/edge_add_48px.svg images/edge_remove_48px.svg images/edge_properties_48px.svg images/filter_edges_48px.svg images/filter_close_48.svg images/filter_centrality_48px.svg images/filter_attribute_48px.svg images/filter_restore_nodes_48px.svg images/filter_restore_edges_48px.svg images/help_48px.svg images/node_remove_48px.svg images/node_add_48px.svg images/node_filter_48px.svg images/clear_48px.svg images/add_48px.svg images/search_48px.svg images/zoom_in_24px.svg images/zoom_out_24px.svg images/colorize_48px.svg images/format_color_text_48px.svg images/format_color_fill_48px.svg images/format_shapes_48px.svg images/size_select_24px.svg images/resize.svg images/format_textsize_48px.svg images/line_weight_48px.svg images/node_properties_24px.svg images/edges_48px.svg images/symmetrize_48px.svg images/text_edit_48px.svg images/file_upload_48px.svg images/layout_levels_24px.svg images/export_photo_48px.svg images/export_pdf_48px.svg images/random_48px.svg images/node_size_48px.svg images/radial_layout_48px.svg images/ego_radial_layout_48px.svg images/color_layout_48px.svg images/relation_edit_48px.svg images/node_48px.svg images/fullscreen_48px.svg images/science_48px.svg images/system_update_alt_48px.svg images/view_file_48px.svg images/recent_48px.svg images/code_48px.svg images/wallpaper_48px.svg images/assessment_48px.svg images/select_all_48px.svg images/communities_48px.svg images/centrality_48px.svg images/heart.svg images/person.svg images/person-bw.svg images/close_24px.svg images/exit_24px.svg images/tip_24px.svg images/about_24px.svg images/cocitation_48px.svg images/cursor.svg images/cursor-pointer.svg images/cursor-move.svg images/cursor-hand-scroll.svg images/cursor-hand-drag.svg images/select_none_48px.svg images/nodenumbersize_48px.svg images/webcrawler_48px.svg images/symmetry_48px.svg images/diameter_48px.svg images/sociomatrix_48px.svg images/subgraphstar_128px.svg images/subgraphcycle_48px.svg images/socnetv_logo_trans_128px.svg images/socnetv_logo_white_bg_128px.svg images/socnetv_logo_trans_64px.svg images/file_download_48px_notsaved.svg images/controls/checkbox_unchecked.svg images/controls/checkbox_unchecked_hover.svg images/controls/checkbox_unchecked_pressed.svg images/controls/checkbox_checked.svg images/controls/checkbox_checked_hover.svg images/controls/checkbox_checked_pressed.svg images/controls/radio_unchecked.svg images/controls/radio_unchecked_hover.svg images/controls/radio_unchecked_pressed.svg images/controls/radio_checked.svg images/controls/radio_checked_hover.svg images/controls/radio_checked_pressed.svg images/data_table_48px.svg SocNetV-3.7/src/images/000077500000000000000000000000001524402210300147315ustar00rootroot00000000000000SocNetV-3.7/src/images/about_24px.svg000066400000000000000000000011251524402210300174400ustar00rootroot00000000000000SocNetV-3.7/src/images/add_48px.svg000066400000000000000000000002161524402210300170640ustar00rootroot00000000000000SocNetV-3.7/src/images/adjacencyplot.png000066400000000000000000000002541524402210300202600ustar00rootroot00000000000000‰PNG  IHDR szzτsIDATX…c` €‘‘ρ?:¦‡8аρ뚨Α1²85ԏ:E`@Σΐ€ƒZΗ5.υ£@Q8š <`γ“Χ€ˆ:E`4 x. e\γ2Τ( GsΑ€ηl|rΖΠm–ΈFv.H [NL3wIENDB`‚SocNetV-3.7/src/images/arrow_down_48px.svg000066400000000000000000000002301524402210300205110ustar00rootroot00000000000000SocNetV-3.7/src/images/assessment_48px.svg000066400000000000000000000003611524402210300205220ustar00rootroot00000000000000SocNetV-3.7/src/images/avdistance.png000066400000000000000000000024661524402210300175700ustar00rootroot00000000000000‰PNG  IHDR szzτbKGD.l— pHYs  šœtIMEή 8#βΙeiTXtCommentCreated with GIMPd.ešIDATXΓν—]lUΗ3Ϋνχ‚]¦έn»vcϊE "RC1βƒXƒ"’²ΡΔD|Π5†Q|0š`|0 j\_0"ˆ1j0’’Bω((νμn?Άle·Ϋ–έ™λwšΝ¦K»₯}σ$“;™Ήχž3ϋ?ηόGhZΉκpψˆμφΟΕžξ΅€ΘT9~΄Ν2w™Μ9@ž“Χb˜’6ͺi€°X Tc@pIΉ $.φtߞ/Κϋλ·Tδ; \G§η+†Ρkx4<4b w\΅’ΓγρdΫ`¨Πu]™ΟΧopΦr4uDβΔ|ΎˆΐοΧΠJaIw^cή,Ξ*ξώυΥυεΔυρO^uWU%Ψ΅{”m[ΓOΤUΧΤ  |ΰσEŠ-ηUUI|Ύˆϋ”>ž@#πƁ`η° pdy?lΰ:-@ P ΄ίέ/-±†6Ώ_…μΣΟB!;~ΏΖΖɊ·­,xOζωYΰeΰP6ΜΧΉΛεͺψa€{»»ΆhΩΆ­ψ|$ Οh₯4΄ŸλU$ Γ@%°ΈœΆΗ   Χ,πxVκύφΌ}μP₯ΣιΛΆ_  vNޘ Ψ;JΫΗάΙ ka©©©QMΣ|x x@ΡaΖΑαααίMΣ,–|HΘB4aΑˆD >ϊ™ˆΥΪΦκ),)ά«ͺκJ*•:>9Χέ·Τf³½<'‹Ϊ—Š’|688koeφ ―€%Σ9π…Ε‰€ΪΦϊ`Ι’’›3ΑήυKf…ΐ‡Ί™%3Df/xΨ&™ι^iUXRΈ`έλ¨[^@ΰJ€3]ΈΌ.#x-xHUΤOu]Ώ2ηn¨λϊ ιόgY&€Ώ]™σUUέP·ΌŽ2­Œ2­l:w­; …ޝŹm¦nˆλ—eΘΦΝξή ²δƒ’*†$Ω(`ή£§2υΐ\Ju»έ‡nΩ,Ψ£‘(ΡH”ΐ•»‹ΕOΛιωΉφκ{šΧλ-0 Γ'„xΣ^`_ήΌΉyΖy]§ΊZϋ{ϋ{$ΫsAΐ6‡/_'„ψx(0RΖαρΨψ[ΕKŠcͺͺ–›¦ωo,;vα― ϋϊ{ϋ²—$3κIΊ™™€+ Q]]νTε%ΰUΐ-uΑ'†a|‡£@‘Ό,ΈοHςNΚϋδ\@έ_Ώ₯&ίQPut*ΠYι4ŒΥcή=ΏŽ^­½₯LPeψZQ”ƒύ Χ68k79šŠNGβ€7 ΄Rθ덝œPΕqS5κΊ_ΕπϊΓ­―4―i νB„ˆ`Π.šΧ4‰oΪ“ £ηcΆΖ‚;w–?½ωφtJ:&‰„Κ©^±ώzΘyΆ‹#™dWΚ– n©˜MΊ©-±†ηύ~-œE±<+½hǐw~ΈΟΥαΞP,h₯Π~οz3Νς--1[=εZ±i΅VSΤNθϋJ§1X,ζbθ¦θœΌ‘}=‘#”ΉώΖYͺ¦0£°LΙΕSΉ–-Wϋ ΎχmH IENDB`‚SocNetV-3.7/src/images/box.png000066400000000000000000000004411524402210300162260ustar00rootroot00000000000000‰PNG  IHDRVΎWbKGD ½§“ pHYsΔΔ•+tIMEί 8%τόž1tEXtCommentCreated with GIMPW‰IDAT8Λν”± Γ@η,9T3ί€ Έ’ϋ܁Pΰ*άΑw!8ό΁m€Po>Ϊπ`na IΜΜBΌχ8ηDT՞ͺt™ŒMΓ+%©ΗΠLΠ <ͺ €K©ŒNΠ :&¨ήpΞ4/ŸεXg$Ζh}Ϋrύp― Έ χi’RŸρSω 3\ƒIENDB`‚SocNetV-3.7/src/images/bugs.png000066400000000000000000000033451524402210300164040ustar00rootroot00000000000000‰PNG  IHDR szzτbKGD ½§“ pHYs  šœtIMEΰ %l-ύnrIDATXΓν–]ŒTgΗηkΞ̜3³3»3³»³»ΐ. hΆUQ>Bw[ˆ6bͺؚLS1Τ*φΒ-­ΖΆΡx§υ-šV[Vι…!₯†JΦRό E„mYvf—afw˜ο9ηΌηυΒm’Mh' 7&>ΧΟϋώοω„SLΣΌΠΫ~ (v8ώh;Ύj>ΫΆ·~»€”ΎmΫΫνƒ|΅6π„išΥ0nύ”γά0G―δ8 k‡ §‹ύjvvφaίχ§―…”J₯½K‡†F\xs“emΎΦ'ΎrώΩΧΧη{žwμvσ*Z­ΦΜ!8wc­¦}Ρ0ΎpΘu­θΐΐIWˆfljjύǁκΞLf‡eYŸΣ5/ΔΑΑΑŠmΫ"ΡΘρ―mNώ%έ“”‹ϊ“ςΑ­‘ƒŠΨ044τέΑΑΑcך@txxx ΰθώΡίύπΫέω λΊ₯ͺ€δMk{δO ŽΏπτ²'}ddδ@iXk“€ΦΩΥσ‘gwΙ―Ώ₯°qf’fύαδυΨΆ‰kŒΧX7܈mωΊ³"€…£―=t1ŸΏτό5MA υ‰ο¨rΩ`BZ=*O:%wμΨ!Σ鴌ΗγRՐ?Ϊ†L,y»ΐν*€­υ/Ύf^/• p‡'N0==γ8xƒοϋ|jΥβA)ε‘kΦf$²u‹Poh—γ’Wν£εkτ€l ©γΆΒ,.΄XιIΖZNδρV- L]΅φΫΎwM€CžΑ–YYι|2%7Ι_)W―Y#Οί(έΑNY9‰*Η°δMƒC~ΔΆW^Ν R‰ΔŠPWηG«ŠkQEG" ™Υψτθ›Έ^αyάrχy Α:>0<’)•νtο^ `ηΝmΧ€‡7†B‘ŸτφφŽn=Ÿ?όz_|u-dςαZΐπ0‘Ÿ=ΖΛΉI2Ω,Š’PrZ|َpšθυ:/-ZD  vdr».€Sw₯R©»9ς·J₯r©³³“F£qe’ΡθžX,6R.—o=}ϊτmΑP`Γο£až΅mTEAFρλ ξI₯θqUαώ8έ¦‰ͺόCΧωžσΫE½άf³ΩΫ3™Μύλή‹Εv‹Εών{ †1ΰΊξ υ‘ή…β™°NΏγπb&ƒο$8'A;–«E„– JC@I αMB&Sp«PG²›Hι7IENDB`‚SocNetV-3.7/src/images/centrality_48px.svg000066400000000000000000000014411524402210300205130ustar00rootroot00000000000000SocNetV-3.7/src/images/chevron_left_48px.svg000066400000000000000000000002271524402210300210140ustar00rootroot00000000000000SocNetV-3.7/src/images/chevron_right_48px.svg000066400000000000000000000002271524402210300211770ustar00rootroot00000000000000SocNetV-3.7/src/images/circle.png000066400000000000000000000007251524402210300167040ustar00rootroot00000000000000‰PNG  IHDRVΎWbKGD ½§“ pHYsΔΔ•+tIMEί 8ΥHj„tEXtCommentCreated with GIMPW=IDAT8Λ­“QjΒ@EοKΖ8 ¨ιBJΣ$+(nA%~+Tμ’ ΪnEΘKDι6 ύŒά~A1±F0΄–c81†ΟΦ§ƒδΛ%[ΖπS„J³aΫσΈQe₯€Χνς§Bpœΐžοs¦η²–1WIΩμxΟ:ΉtͺΜE8KΐαHΤg )‚=)Žͺ2΄·ΰxl6‘η9,Λn%Ϊο‘ͺpp'œ(Š Ζά,H]qί±l˜‡ψ©-Zˆ`š$§‡mΟγΖϊψπ·§lTΩσ}n―”Όψ>ΏV«ςFΦiʎηq.Β’DP|სՒcfIBW„΅77|²–ίF£RΑΕ†σ<§ͺβ8F•σΏk!Ξpz”IENDB`‚SocNetV-3.7/src/images/circular.png000066400000000000000000000041001524402210300172360ustar00rootroot00000000000000‰PNG  IHDR szzτsBIT|dˆ pHYs₯₯pΔήUtEXtSoftwarewww.inkscape.org›ξ<½IDATX…—ypΥΥΗ?ηχ~YΘΒHBBσ=@ ‚¬‚ e+”›–‚RY:Œ’γX@­TŽb‘N‡‚ 8PdK2¬B(‹°Δ°C–Όχ;ύ#οαΟη Z3w~χάϋ=χ|ο=χœ{―¨*?τεMxΪ™•Υζ—MΒCΊ\,ΏσεΏΆŸ}sηη/ βψΜψLβ‚±ΉiC=brMM­CDήš@…§TͺmFC†€tmάΠlχΧ '?SUλ'‘ ۞ϴ,5,K 4**4ψ(yˆ€Y"βξ-]wΤ»ΛŽŒŒ 24© ==zHqqeΕOY ΛΛ²ϋΔFq"ZQQ΅QUΫ0·€S^‘{χ–½F Oέ²Eƒ‡Γp“–rςdε ΰ΅M@DB‰ΐΥkΝ 4·hή°kaΡ₯_~kαβϊGE5zΰ³ΆΦ2M‡Έƒ‚†_°ͺ~―m©Τ-msŸΎDo½τθ i>ύq@Ϋ?,Ξώ΄βΚΛwjξΞ―^ϋΡ³#"BFϊ΅εΗx0h 4φӟό>Ώ ·ΟΑ΅%°ˆχρb‡ Jμ3q’3†Ύγ‰ͺRπrζ”Nšs²~ΓΙςM›OΟ.ͺj­Χtya– ͺ`\wXVTυd=. ž9uJηδφΞθδσηoοί³§l…Ω=#&ώ£UΓ§€D†Ή\–Ω§OlšZΊrΣη§Οωd8Σƒ @Α œp¦Ed °SUΛ|\]ϋΞΫS¦Oν’`Τ‘‘f˜‘–5,%%2LUED4:ͺΓιŒΰc8VD¦ν;ΰΝ pΑ\ «ΰψΨ¦ͺGUυΟ@’ˆδϊŒ!q±³MS\.—eΔΆ Ο2vμς˜TEEU±¨*csC’ΪE΄iVy πϊΥkŽn΄Θ>QΥGΆΆDU=λΗE1ΐ}U½α‘s€ ͺz`ΨΠ€δμήqσZ΄l”^RRYZrκzwgΐŽο €ϊ‹!ει‹"lς >ύ±žUΕ›š¨κοLΊŠDΎ"²`Ό:Β|gκλ[»¬ͺ—€‰π4•ˆH+» > £ν,ϋͺ]π n†Ύ+0{69kΧ²qσfJίŸ™™Dϋ`Z ‚A+aΝr8ς(@œ§ΤγD<ηUμoB•—€Bυλ°ά—ΐϊυμR₯Ϊ[.d±Ζψ± ΖBυ‹0ΙF`(π½ό|—Βu7uξ±€r8mΗ„†85•φΆψxΊΪε^Π;R$θ }ν‹ˆ—€ΓΫzFυΪ:X»Ξν…še°εέΊ£χρWSΓ‘ΒBΩێc»]ή{Β ―μNΐ^$DUy—k¬mι€ΐyԝ˜ί9T•„r–,aιš5όcΞ~ϊbς!gμ\GηΓΟX^δΪχΐˆg$ΌΡ4€l#σu7ž …κ Eάtΐa?-νaΘμ_€ή΅3οlΝέyφμšT6}Z—©6p0ΡSZώ?¨;šΗΫΫ‚ƒΝ@3 GU1;wl‘ί;+Ά…ΛeIf˜θςKwΗ«Tυ‘ˆ‘žͺΊ_DnˆHKU½ "Tυ~>O~ΘUΥ7&MμΠcΐΟΨ›Ÿ^TtιΚρ―Ύω5€δ0 ±T‘ΰ 3Ğ\T΅h("™ͺϊΈυ’Θ¬u°aœ^-ς—φ"Ν|Œ‡/_ˆˆ)"mRR"—;ζ©^Ξ΄¨ όΙΫvψ™ `:te]ΗΝR“ΪE4:Uzέ]Xtιcυ¬ŸΔvI‘Ιΐ–Q0m$ h&Œ>7EδΌu7©m@+κRσ•Ψ֍#DDέnΛaš†«I“ΰ81QU:wjž8sF·‚ΤΤΘ.@ή|jDΐ¬R¨ͺ…‡΅πΠΑ~`40Ζ»‰Xl·£_ΝΘλΪoο?ͺyεώύ{σͺ½–½μqψ9Lόήί<ύ²v+T[Ps ͺ'xBΚο#wμΡ=ζΥ·ήθήσγΫOπFF}FRΓ›ΊgΔtσΤ“Σ!b.Μ_ «σ`p=c΄΅ΥϋύύαΔΗέφŒΟŸά1dΤΘΤΙNgtμξέe'Ύ±tζ¦Ν₯‡ύιΔΔ„…Œ“63))rpeeΥρ-<σaQρεn@‘ͺžς§Sο»@Uo‹ΘΩ½cKϋζΔ·7ΞωτՊͺY"2‹Ί‹Ηwž[γr³__Ϊοχ–₯†ͺf‚τ**Ύœ₯ͺκ³γ±πνŒΒZ·nlš† ΐ²Τp»¬φΤ]Α2D$IDD$]D†DE5ΘsΉ,Σ²Τ0 Γκ›ίΌYΓ†O²ρDεεwnύ{OΩΦͺͺG8βΈZuΏμ❷ωφφΠ ΞVU=άhš†Λ4 —ϊί37_­ΈwύI6κݏ"2a|ϋ‘ν›f_ώlλΆ3~ύΩ£uԈg“ηΖΗ‡ηάΈyΏdίώς•Ÿ¬ϋͺθIγkΥo«)ΝΧIENDB`‚SocNetV-3.7/src/images/clear_48px.svg000066400000000000000000000003221524402210300174200ustar00rootroot00000000000000SocNetV-3.7/src/images/clique.png000066400000000000000000000026521524402210300167260ustar00rootroot00000000000000‰PNG  IHDR szzτsRGBΞιbKGD ½§“ pHYsψψΟΑζetIMEΪυ»μI*IDATXΓ΅VmLSW~N­ΒZΫjˆγΓR\c;¨·ιBΔΨ`³³ šIξζœΛL¦ϋ5‘,.ΈIγ’?φcαC’“ ΡΝ\" εK*…lΑΕ2‘₯hεμΗlυΆ΅lΌΙMξ}ή―ηΎηΌη=„RŠιˆRI>MMΕz™ŒY䉉λιγζM|βvӁpβp BˆN‡ŠουώΊlwu5vwwΣ+3F@₯"ϋ\b, €j‚½U^-₯ΤνG,=5υwμψnK^^Ϊ+‘‘°υχΏ­ΣUύZSS €ψΈΈ$ΝκΥΕq±±ρ.·{Έ£³³²ŽγΞπ”>Γ0―Γε J2>™“|"vnΫvμ=ƒ!Ν‡%.^±Ϟ͏η P!—'Ώ_XxaGAΑbŸA―Ν¦Νέ°!ξ—κκbrΉΙ§1>J!π½Η.Z”£ΟΝ]ιo5o2Σ³9:έ‘ΙΙ )11r‹^ΏςκΤΐΤyη„Z&‡·|ο €₯R‰„Χ.2"B*PΘε)|JV»03##ΧooΗ…`ΙΟb[Ί»ρƒοϋ»w―[mΆq>[ηπ°]011αεSz½^<}ϊΤγ76βΘφ~φ&ŸΟIϋ Ή_SJG}Ψ_·V\ΌxΕ띚¦Λju7™Νg„·»Ί,”R%!dŠAΥεˎφŽŽK|‰8Cω#μ‘τthΕbfέ³g¦F«ƒ5η ςzi₯ΏύOΥΥ;έcc§2”JMΜΒ…bKOγV[Ϋι–ŽŽσJ₯15φY,.»Υκ²[­Š²2Η›kΧΒ`ΘR«i–ZMŸJ)|˜ΰ;@μdL844tŸςV…\x<2βlko?ΧΪϊOΞΚR«Λύ6&€ZŽ#Ω ήτ €iŸ„“Τr WΟ'‚+Ή?ž­ΡPBˆŒ2λ?Ο‚pώŒΑ0ŒgbΒΤ"0œ ¦«mmΨΧΧG[Β&09y]Cƒ€qπ%€½Ξ©Ηu₯Šν;{Ÿuv){H`β-MIA‘L™H‚}UUh§!ΨͺΥ8Ξ—66εm*φ BΘeJιp@7£JΕ”νu:Δΐό­μPY‘–*3ڏTŸΣλ“Ωwœζέ„„QBΒσδ“€˜e……ψ6ΔΪΗSΚ(C‘LHψwRΎP΄4ΌΓW>ˆŒdvek4»‚Δήύ2έδρ`<`FE!Σ”,΅ϊ”Σiκ eg³αFΐ ˜ΝψωD†δ$_D"ΣΕZŽn |εJς^ΓΙ@ϊ=NΆφή=œX±1j»ίtΪίρ„D‚kΧPκοΜfZZτ'ϋβMJμ¦Ε„mjmE₯τYΘs ?Ÿˆ‰aŽ€XlͺΌq%υυ΄ώe—cωrbP(°5!Ρ?|hΊb·£«‘ό/ͺΣΊϋdIR%‰‰Ϋ₯R©ΈΗf³665•RJ½a 4:ƒ=:­ΦPzόx·o|w47»ŒΓUσ%—0!$zνš5_nΞΛ‹υab‘G^­ί΄©$άxaX–’²AŸ——μΟ ‘LK˜qΡΡΡ1Ρ"οΖ™3{vԌΈm±\ͺγΈ>έΐΰ`οŒν-―¨¨xδtN©BeUΥΓίλκŽ…½§¦Σ†„AŽNwP΅jUΞάΉsΕv‡ΓQΟqίάΆXjυ7ΆΑ―Ίƒ(k•IENDB`‚SocNetV-3.7/src/images/cliquenew.png000066400000000000000000000027201524402210300174340ustar00rootroot00000000000000‰PNG  IHDR@?PΞΚbKGD ½§“ pHYs  šœtIMEΰ .8γΖtEXtCommentCreated with GIMPW8IDAThήνšKoEΗ»±½λ·ΧλΨkcc› ΞΓ1–—œ  "$βΒΓB€ΈqAHHpΰDQQDBά8pΝ!άψ ρ$ρ!ςΚ‚$vbosΨθŒfzz^»λ΅KZΝlWΟΤ£{ΊͺΥ°Λi_eO/―%ΰ`}·8ώπzL«Ϊ• ΰβn0ώ &8€~fT­³OύˆeNJψ-KΣΐφ §”U΅-WͺΘ’SυR([gœ…RφGn‚ ·~½Ξ@© XjU‡γLr‹ͺ‹Q&Ή ΜΜ΅ͺΦ‘B;[žΜZ{…VŽσUW˜UJΎη§@]ᨂ¬ͺυkM«ΕϋΌΊFYέ%§ͺ ξ’SΧ(+Θ)ΰoιΧ²τ~t©.>PΘͺN>TΠ­€5`ΉU ο,€*Τύaΰ*p2՚CψψLψ­fό1μ€Σ »AΉoΦ¬ζ(€ΜN7ώœΣ«΅K[Θ˚ΑEΰWێ£ΧHβύ紦ck3eGΡAQ|Ϊ‹)Ό!­iΦ0ΪS~ŽlFjΧ”νσΫ‚ ΏΓ•­ŒΞ ?Χ¬Ζχ‹‚ΗL€Ο Φ'£<#}zšΙπ¬6κ… ΞοˆΑA4άLŸDN9ζρC’B•Υ"G>-γ&7/δϊΈ‹?! ΄}‘τΟΠει#λδ#!uYe\!Πo蒏0΅4}²$·!«\MP9Κ”²0Ξ‰₯Ϋϊ²θ nK²:a9Ίεˆ \$έ³υ‚¬ πSΕΞqΏ:³‘p[›7duέY&Ηκε.ΈΑ Ώ‡θ»ζέχ€M έauΞδ+Θj.nΒΧ–Ίe€ΎόV©-€6#{ΊίW`0„Ύ‚Κ§qαΆT +ŸPe’₯λ@"ΊΫ@VχϋέΏΫ: L6Ά‰NIΐmΛΐΘκp8# އHVΒη–Ο•€ίΰΥέ­αΆa¨n@¦ ό•4ρΙΨζδΒ6‚Š6οφΘ†EΧ?EχbΛ0qΘ ΜΫ^‡–‘%š8 ο#ΦxZ%S‚ςšΟB7GyvΑΥ<$νK†g©+3νώ$δ~"†Θ>χϋδX@*]HΣL«±Žο%°]uΆΟ=r=μB’’ΨL…vΐ !whq`mlι²Τ/ΥΒj±`™8ŠBζ ξΡ ƒώHΏΗt@‡εh8ίβ0/χ£!δŒi ΰ“–‘€'m„ ž²όvέ+· 23­9O—TΨ_/œπ… %Ώ‚ˆa”:\ȎNη-©z8 [„΅(γ·ZΟ{|«NςrΪ&ΖT‹~Sς eF΄ςΨΉ/Ζ0°\o˜άd€-τ₯‡ΈnK§ΏaΰΥ·€ξcd»G%Ψ΄Cά―­ψγeΕΩoΔuΐ‚O(2ђLj9Ο½h!sΖΥ|¬!U"ϊ±«yΑ ΜPPR£ρ 2_ςΘ7Fi΅‰πA—‚^ϋξg„7" :κΑλu͞|PD 4"†ΆΆkΧwd»-hμš^π]₯ϋ7-ή{«]ΈWwoα? wCdΎ%at­R;m^mΔ ΈƒΥηrΐI"”½r€Uω,+°’„υχ&:eވίMuΚΌ'Ύ›ξ”yPωlΉEdΙ]>K¦Υ|2χhφhZ—ώβ:#΄QγηIENDB`‚SocNetV-3.7/src/images/close_24px.svg000066400000000000000000000003231524402210300174320ustar00rootroot00000000000000SocNetV-3.7/src/images/clucof.png000066400000000000000000000006501524402210300167130ustar00rootroot00000000000000‰PNG  IHDR szzτbKGD ½§“ pHYs  šœtIMEΰ :η2y5IDATXΓνVΑ ƒ0 <£>»?$θt”ΦQΪ9ψ±oά‘idHŒCι£~Drζ윏'Ψ{&ΝFΚ\τ·ωΕXήUgΦ_χΑ₯υž πΨΔνΞ€™Α£07‘T†TϊM ΤUΕAΧCZοΒ€o»Žt€31,ƒ»όΠ° όπˆPρQΰ›’`iC ΈP‚₯sYjB€ώρΎεutΰ.뼘}πpmΞ ΐ!ΈΡ4|Ζ“΄σβ€ΉχkqAŸί΅]=;)kύcRLα$ϊ]¦”@RΪ%ivσ’»^Z§”@¨ΜΐΪΌ o'hΏ†~²ϊΖ$1 J(δXN[’XΕκs7ϋwΗ-–|W¬±c–²XςΏ-‰01`΅δΉl³Ib_@Ϋ‘Jhηξ“IENDB`‚SocNetV-3.7/src/images/clustering.png000066400000000000000000000003621524402210300176170ustar00rootroot00000000000000‰PNG  IHDR szzτΉIDATX…ν–Αƒ0 CύσψκpHΤΤ›Π"εRΡδΥ1ΰ…‡|Όx,±§"βIρ €R¦zH ”5`²CX²βLΨΚ&8ΩεΎVχPm΄ρΤΧOΘΙΚΘΖtŒF‘p  Ό '`nν©$WΗAΕ«θQΈ=4ί”Ÿ&(ΐ»kμynπu ϋkHX‹\ήϋk-–Σώ$@ώ|ŒOԐςu£:3ΕώΐC°ΕΊοΔ0σjIENDB`‚SocNetV-3.7/src/images/cocitation.png000066400000000000000000000003541524402210300175750ustar00rootroot00000000000000‰PNG  IHDR szzτ³IDATX…ν–A€ {εήόόO=‘ά±Ϋj’Mz’έYˆHE‘"Zӣㆧ›@πTί4°•’m"x_jBδz ΰ Μ@/:2€κ©πθ>ͺˆ«ŸuŒΛ:»Z“ύ!U½–aΒa!LZ`›2~Vϋo6έ`ΏΐΌ Σ[0kdΐ β!ϊ«Ψ ¦Ύ†ΘDϊ<`FQΨP²2RΑ(Ύ9–ΏΚ€e" ŽL€ΓYqωΟ† Š©MœIENDB`‚SocNetV-3.7/src/images/cocitation_48px.svg000066400000000000000000000012261524402210300204720ustar00rootroot00000000000000SocNetV-3.7/src/images/code_48px.svg000066400000000000000000000003121524402210300172430ustar00rootroot00000000000000SocNetV-3.7/src/images/color_layout_48px.svg000066400000000000000000000012601524402210300210470ustar00rootroot00000000000000SocNetV-3.7/src/images/colorize_48px.svg000066400000000000000000000005061524402210300201640ustar00rootroot00000000000000SocNetV-3.7/src/images/communities_48px.svg000066400000000000000000000046751524402210300207050ustar00rootroot00000000000000SocNetV-3.7/src/images/connectivity.png000066400000000000000000000002541524402210300201560ustar00rootroot00000000000000‰PNG  IHDR szzτsIDATX…c`ƒ œ’–ώŒιjΊbJA²y4 kόƒ ηSΫ$ƒ‘ηlšΘ΅œVζ‘ θnα¨ε£–Λ±δ끳œξ…Λ¨άΨAWΛFL^Ή–ΣΊ”Γ›€iΕš?κ€w6EΤ²œω0εP_–PΈIENDB`‚SocNetV-3.7/src/images/controls/000077500000000000000000000000001524402210300165745ustar00rootroot00000000000000SocNetV-3.7/src/images/controls/checkbox_checked.svg000066400000000000000000000005411524402210300225510ustar00rootroot00000000000000 SocNetV-3.7/src/images/controls/checkbox_checked_hover.svg000066400000000000000000000005411524402210300237540ustar00rootroot00000000000000 SocNetV-3.7/src/images/controls/checkbox_checked_pressed.svg000066400000000000000000000005411524402210300242760ustar00rootroot00000000000000 SocNetV-3.7/src/images/controls/checkbox_unchecked.svg000066400000000000000000000003211524402210300231100ustar00rootroot00000000000000 SocNetV-3.7/src/images/controls/checkbox_unchecked_hover.svg000066400000000000000000000003211524402210300243130ustar00rootroot00000000000000 SocNetV-3.7/src/images/controls/checkbox_unchecked_pressed.svg000066400000000000000000000003211524402210300246350ustar00rootroot00000000000000 SocNetV-3.7/src/images/controls/radio_checked.svg000066400000000000000000000003621524402210300220620ustar00rootroot00000000000000 SocNetV-3.7/src/images/controls/radio_checked_hover.svg000066400000000000000000000003621524402210300232650ustar00rootroot00000000000000 SocNetV-3.7/src/images/controls/radio_checked_pressed.svg000066400000000000000000000003621524402210300236070ustar00rootroot00000000000000 SocNetV-3.7/src/images/controls/radio_unchecked.svg000066400000000000000000000002701524402210300224230ustar00rootroot00000000000000 SocNetV-3.7/src/images/controls/radio_unchecked_hover.svg000066400000000000000000000002701524402210300236260ustar00rootroot00000000000000 SocNetV-3.7/src/images/controls/radio_unchecked_pressed.svg000066400000000000000000000002701524402210300241500ustar00rootroot00000000000000 SocNetV-3.7/src/images/cursor-hand-drag.svg000066400000000000000000000065271524402210300206240ustar00rootroot00000000000000 SocNetV-3.7/src/images/cursor-hand-scroll.svg000066400000000000000000000022451524402210300211760ustar00rootroot00000000000000 SocNetV-3.7/src/images/cursor-move.svg000066400000000000000000000024351524402210300177370ustar00rootroot00000000000000 SocNetV-3.7/src/images/cursor-pointer.svg000066400000000000000000000042371524402210300204530ustar00rootroot00000000000000 SocNetV-3.7/src/images/cursor.svg000066400000000000000000000023541524402210300167730ustar00rootroot00000000000000 SocNetV-3.7/src/images/data_table_48px.svg000066400000000000000000000006751524402210300204250ustar00rootroot00000000000000 data-table SocNetV-3.7/src/images/degreematrix.png000066400000000000000000000012011524402210300201110ustar00rootroot00000000000000‰PNG  IHDR22?ˆ±bKGD ½§“ pHYs  šœtIMEΰ %ΓR&IDAThήνš=/Q†ŸY+[…DψJˆF"j*F#QωJ­Κ Ώ@΄l!Q @DΨυ•Hζ&;χž{ΗΚӜΜΞΎ3χέ™½σΌw7 ιΡ•:Ξ=R=c$Š]²¨dγ76Wδx(‹+“ςe29&„x »ΐ° {#’Y“ΎHŸidh·ŽFΞD3&ύ4>ΣΘ‘lO9i‰¦_z+>ΣΘ£lΧcϋςšj+΄m$ΝZϋŸςW‡₯_Z±Φ'9”>ι8ξH_”Ύ]„ΎΣ­5"{žγΌΜ#5οΐΎΓτ›W=v—'»ιF¬9 ]Vu6«θ·2RρW…F€$ϊΥ’Έ+9{Ηx-ŠkŒxΓx Šk#€WŒΧ Έ6xΕx ŠgE€k`Ε7Ζ§iΚk£–·Φ½ΌΏ‘pάiΰ%γyϝ™G4(žrN0ŒΧ xZ˜n€%ίŸgΕW­Κ';βXδJ£ @Ζγ54TΠXω§F¬b@ŒχςΠspŒχΌΡ¬¨k΄61 (Ζk΅61 (Ζk΅I1ΐ™~5+κm/_ƒΎζm¦_͊ΊF›"ο2°UΖk΅i1 f€σΏΔx_ύ ,”έHΜwq#uΚSζǚΐf|g™ŒDY;Λς7'ΖZa|·Υ'@Ψ€Βq%°ϋIENDB`‚SocNetV-3.7/src/images/delete.png000066400000000000000000000014771524402210300167120ustar00rootroot00000000000000‰PNG  IHDRΔ΄l;gAMAΦΨΤOX2tEXtSoftwareAdobe ImageReadyqΙe<ΡIDAT8Λ}•KhSA†“ζΡI([5ξZ»P²±Ϊ…]΅Ύv)-BIWν΍ ’ΰΊ«„Š+QDE ŠUρ°V#­’ΕΆ’j•Ύ4ΝΓγnΞdζN>ζήΞ?fΞc‡ˆ<&ψσ~¨Pgk m‘ ςΪʜ!ςŠQ^laΰ―aκw΄JΫ"βα5…ŽιΏ‘!*uwS‘,f6ƒ(ΟWiS)*υτP)TΪ8hpζΝγ—`Zμν₯BK επώ]›oUζΚ΄4RΎxθ Φ σςIΠ·±ΰ€>R—΄©}mV:τ”Φ²ιρˆΪ₯Φ ΜεH;Α–5.ϊ¨Μ)mBΦκ‹ήϊ4ρ‚MNœΚቬσiŠˆ¦YΦ¨SykužO>ŽμδΪ hλMν=©—–ρ+IENDB`‚SocNetV-3.7/src/images/diameter_48px.svg000066400000000000000000000041721524402210300201330ustar00rootroot00000000000000dSocNetV-3.7/src/images/diamond.png000066400000000000000000000003601524402210300170510ustar00rootroot00000000000000‰PNG  IHDRVΎWbKGD ½§“ pHYsΔΔ•+tIMEί 8¬” tEXtCommentCreated with GIMPWXIDAT8Λ½ΤI @γ/^”ڍ`Α[±‹c| ξγ"„g":i'£‘ώΝΘ@dπ yaθ †.rcS5lς§I‹-mΏt ₯+R^ZΩ7"‹ν| i .IENDB`‚SocNetV-3.7/src/images/distance.png000066400000000000000000000025201524402210300172300ustar00rootroot00000000000000‰PNG  IHDR  «hsBIT|dˆ pHYsύύβ{ƒ“tEXtSoftwarewww.inkscape.org›ξ<ΝIDATH‰­–ylΤUΗ?σΫ ¨–ΤΤƒ–ž4XJ*)€Aj (Π &ΰb"$I­ΉŒ‘4ώ‘ΔhδPbšJ©„(½„B)ηφ ]ΔB(AθΆΫ=~γέΥ²₯ΥIζχfήϋΞυތ¨*±hζL™SXΘΊΤT2|>ϊ.]’©’‚΅ͺz/恑HU£Έ €…ΥΥΈUρ„9ΐSVF=0&Φ™‘8ζζϊυœ ζώ~ #Cγt’^ϋ|Ψ¬v;LžLΦH"x»»ι /μvό¦‰ασaΈΟ€ͺfCgύώχμv|†YSƒυΤ)jG;qπΔͺUκθΰ*ΣΔsψ07ŠŠΨ 4yΓΝ‘Δ+o€‚Y6aω½½τ΅·Su‘qΐ^ΰ{UέχΈ=(ξ!°ΈlRUσ?‰Θ| Ψ―ͺw"dοΕΐ[3ςςJΣg#’ξΞΞ“g~θ’!˜GώάΒΒ;υǏ?p»\·Λε©;zτ―Χ—.=XΓzΦΗ V|*Y΄¨xΫ–- ŽQ£lΑ`0`±X‚™™™ΦΟ6n|₯―§gπ  $"’ΓHά€‰ ǎ5Qνχωύφ`0h΅Z­ώ'Ν©Nη찞Ίά&"Eδ<ΰ‘vΩ/"ΩCY,–Ρ! ±X,A@ Γ0 ΓHλYEΔ œζG€/ΰeΰ$pχQ@n· Θ3MΣV‡έή–΅wt\¬ϋ>Ιή#Ρo‡dq‹!-99僋 ‚ΧυΊ].OΗ•+žKK›’““Ηύσ`CV9ͺΪ:ΨΰΜPΥΖHDΔn±XŽ,\°ΰώ΄άά$._½ΪVΫΠ°ξφνΫ]ƒuο~ΐˆSΎΗε°Xϊ8m’°Ά8i/?"²hUΥͺx:a2€K€/ΔΈ(˜€˜ͺͺ[‡ Σ|BsxzPHl „%ͺ€iΐ―€cXΏ·ˆ| lΊΘIπ"πgΘΫΉ„ŠADžφΛTυΦ`‹ΣΣ%sώ|ΆN™BŽΥŠ΅­ΦšΆ66jγΰ€Ξ>~ΎV†ΌzΨ <8BžDυ£δdž-/§9rΞΨ΅‹λΣ§“?ΌI~^‹%[±‚οL3z QΕSVΖΑΈŸκ΄άάβτργ‹ΡN·ϋ· ΝΝ³€Λͺz0–~V9"±οJI!;–Υ²dρβέ‡«ͺΊΓίώ‘ΚΚs \„ϊW„Ύ˜΄z5§|>|^/Ύ>‚^/Σ€WΟΆmΤE5Ύβyσ>ϊjϋφ O%%¦iώ@ΐζρxΌο••}SSWχ;βρ@hq:Iͺ¦$3k €ΕnΗ/‚ή»‡¬\ΙΧQ‘{>/o^DUΕησ9DDFe€₯½ άš UύcπΩεΛeǚ5,+*" ©‰ώςrŽUV²! Θa·'>΄v8Ό"’©©©ͺϊEμLΐΎ}ϊŽΣ)?eeρͺΝ†αrQΧά¬G+*b4Ύ7oΆya€0uuu΅FκGΕ‹z8Ή5@ž¨­έόΓξݝ‘ϋ;χμ鬩―x( xs ΚΞΚΚ™•Ÿ_ž™‘1ADΈήΡΡrϊΜ™υmνν-#ϊ鱝2ŽƒΤIENDB`‚SocNetV-3.7/src/images/distances.png000066400000000000000000000004341524402210300174150ustar00rootroot00000000000000‰PNG  IHDR szzτγIDATX…νWΛC!δ”&, €Β”υš2—Δ‰?Ψ%’—0ΓΙ…έαEzΛ_nƒ…,ߟ·βFbΛ“‰l‰£ZΣ€WJ•;@Έ%ψUlDПkυxϊeΉ#χHΐΑμ(ω>ς=ςxΉ*€ν O9ςγJΕžΉžrT€ןPγΠ‡˜ΊWΐ– t#Y'ΐCm‡"σΌl ΄Ψ@΄ύEl­WΆ‘Ζ„=HF›qώ ±b–μψ-–y―@bX*†Ή&=+φ0ζB@m„ΧθIENDB`‚SocNetV-3.7/src/images/dm.png000066400000000000000000000103201524402210300160330ustar00rootroot00000000000000‰PNG  IHDR szzτsRGBΞιbKGD ½§“ pHYs  šœtIMEΨ  }:o"tEXtCommentCreated with GIMPW+IDATX  ίοζkζZ?ψ@πΩτβQόεJί+PλψUmβ-KμEρβTλ}μ»ϊiοοitš°² σ44ςΜCυ6Υ44Σ$θψ¨τ„TωωR|•Κ44υρ44πΤ?>επ|‹ρρŠ%όζk₯?ψ+CΩ¬uJί+ώmβ-ΣΣ ˆΚ—@|:ΔIENDB`‚SocNetV-3.7/src/images/eccentricity.png000066400000000000000000000004401524402210300201220ustar00rootroot00000000000000‰PNG  IHDR szzτηIDATX…νWK!ε.\x©^©—κε覎?ΠG%mJςV Ό0o‰Ύΐ8Γl ’C>ψδ%σ+Aˆωus>œ κ#?L 6ιΐ§ψ•€i Κ+'θέ5-7ζ­$NΈ„iA€ω‘А ±ΠΆΨnΞ‡#ΔNfΒS;~‚^s†ƒόΦhkΔ=?:S ΌΘ ~€ψΚέΉv₯±²ΐΠvήˆΕzριν' Xi‘Π€΅ I ±b1EcˆF υ}jσr>t£L0ο@SocNetV-3.7/src/images/edge_properties_24px.svg000066400000000000000000000012111524402210300215020ustar00rootroot00000000000000 SocNetV-3.7/src/images/edge_properties_48px.svg000066400000000000000000000035271524402210300215240ustar00rootroot00000000000000 SocNetV-3.7/src/images/edge_remove_48px.svg000066400000000000000000000007351524402210300206230ustar00rootroot00000000000000SocNetV-3.7/src/images/edges_48px.svg000066400000000000000000000010441524402210300174230ustar00rootroot00000000000000SocNetV-3.7/src/images/ego_radial_layout_48px.svg000066400000000000000000000147441524402210300220320ustar00rootroot00000000000000 Ego radial layout SocNetV-style ego radial layout icon with many red nodes, inner ego circle, outer radial node ring, and thin black edges. SocNetV-3.7/src/images/ellipse.png000066400000000000000000000007401524402210300170750ustar00rootroot00000000000000‰PNG  IHDRVΎWbKGD ½§“ pHYsΔΔ•+tIMEί 82πGƒtEXtCommentCreated with GIMPWHIDAT8ΛΥΣ?kΒPπσL &C6۝L3ˆ,”βΰ"-tΜχ’ZkΗBΘΠB·n%†‚ΰŸ/αι  ¦.ν…3=ψΑ㞠ό΅‡¦Σ)'“ ’(”J%˜¦‰b±(Ž’ϊ}VM“M£«(τ$‰ž$ΡM§YΠ4VM“ƒc‘N₯Žσ $x£λμΦjϋ1+“αk π3ΐ²aμb=Χε{d›±Όk΅6Ψ|>η…’$FΆ9W.—K¦‚ @c½ώυΪ]Ύο#uj@, '|νLQΈZ­>·Ν&ΗB$Fή„ΰ}»½»Ή²aΠO€ΌΌΜfχw©λ8ΌΦu†1ΐ'ΐ+]g―^oχΣpHΗΆ™SU6iz²LO–ΩPUζT•Žmσy4βΡG;›Ν†!Β0ά4ί²`YςωΌΐΏ˜o7Ψ*-ͺ/!IENDB`‚SocNetV-3.7/src/images/erdos.png000066400000000000000000000034401524402210300165540ustar00rootroot00000000000000‰PNG  IHDR szzτsBIT|dˆ pHYs€€’ƒ<±tEXtSoftwarewww.inkscape.org›ξ<IDATX…WyPΣwΌ‘˜@Κ%b-‚Γa‘xK©Ηu—Ί+2ng[»t·έjo¦φ˜i;γΊ³C γŠΤŠΗŠΊ΅λΊ:c]ΫZdι.ΥrˆΆ!¨ Α"DΞδυϋ#όγΎ™οLήύ~οϊ~CΜ O@DΑΜ<$ΖKM₯T½OͺTΘΎv ­uu8R[Λ{4θ Μ,zς6+qΧέΡ“—šwνН~²ΰ‘ΕB>€€½{ΡΒ «σ΄΄``αB¬pgSμHέ–‘–=·~u\ 9v;Ϋ|ζδ?φ~·aβ…:Ιɐfgc€3ή&@βŽκ—$Δ#Β""'‰©zr9’½uξ1€φΛƒ°Ω~κΦށO„8q£εMMΈ-ΤΟǧχ€Ϋڐn\_°c{ϊΎΏˆί @ € ΜŒΒBΌψ0.ΑZSƒήηŸΗΏίOx'ΜsΑ“¨™‘‘x`Υ*lMIΑ1έ¨(Θ7ψώ1gž4GŒOχCpΤ^ΙΜmš/5 3_Σ{$ΓwQΑrίΚ€8Y O|lœ¨|ψΘο…2’=0k)σσι­ΜL28Κ4 ΐ₯tγΜά`€ˆ2ˆh†«ΤIaRœ fL'©.Q²V‘ …Ηž~š>ϊπCΤWUaλ‘CψδΝ7ι‹˜’%’IA€§Κ䭘ωΫΠP\FDιDtw΄mvš΄ΐFΗ0b·OnάIυHOGφ… °—‹ΝkAήv”JΝΜzπΉ§’ώ]sβΡώΎΦ_[–lΚOψ 0ΐ\f†~ΎojIΡ΄ΖΖ`λ‘βiζ-«ύ^󸈲³Q0wξδTK$€ZJ°L―xvϋΆωZ’;ΔGWΔFDΘύ―mμ-λμΌΥLDAD΄€€Άy\»0ΕηηΧ,Ά†3›]3>©WΰΏC"[Ώ·—?{ˆH1žl‰ΣΉ24ΣυiЧY΅πΠ@Sσ?Ϋι3›CBHΆn½’ΡPφ݈θξζ:q{ΚΚpI8uu­©Α‡α«’oώ0φk£t΅ηφ "-€―˜ΩμhΤΈM›¨²ͺ Mǎ‘Έͺ 'ί}—ΞΖΖRδ”1T*)`ΐRR 5›Ρ^_½ άΰδQZΆV]φ^Ve\L―“^²ηbΫλού'@ €NfΎεδ%&œŠ |Υβ"06oF©Ϋ=ΰθx%3·ΊΠƒDgjδ³uiŠ_Ιε ίΆί4Φ6Z>0™τ1s―P§°JφνΓ>vξD«Ϋې™G\ΗΞA"’ι_6έ8 ΰ€ °xVWη`6£Ωbd²Ιt‹—έ^Fχ€qΗ&t:ΰΗΜί‹ Ÿ;‡Κςr\ΪΫ1^]W1©™ωkΊͺ₯‘[αRΙρ3]ύύeζ.'ρbUςŠ%3 ΎοΊeώkyS3ίV©(zΡ"%'CίΣƒN£«ζ3χΊ„Τbτw^L<ΫύεJλυσΉΦ}Φ\T=ξδεζΖλΫ.ݘ)ΆŽ[KK9εΙΗ}—`νς¨uω«bt>>wΑʜˆΈΌ\Ε'ΩUAB|XΰLkΧ>œ••₯ΜrgO4"’ 3σ‹Ί·ΆηœΝΛSm#’@λuΚΪμŒώΑ±>'ήΧ?dΪ2w ά΄XϊΫέ :ς“ήΩω§eE3Β¦†‡'t/½z.ˆΆ0;Υ}x₯A±>dΊΤοτg=5ώήU±ί‘»ΛΩρς4­6Κ`ξΊa4vU΄΅έκσ`ꃀτƒ’ά―G†^Ά Ο??ω₯Y₯šγ”[ Ÿ½d‘,SDŸΜΡibTξϋU7χaΩLWΊF₯48 Ν½nΎ)ΐ7uM]nΏZSzΐdϊ‘σσ/Luτ#GΏm­6š‹‰Œ$ωκΥτ‚RI*ΜΜ^9 ΎjΊZ±»ΌΉΥ? «u ϋΆ˜Œυ¦˜Ωζ*»y3ν)-EΣργxcχn4<σ ή.©{‚‡ΰ³|鬍kΦΜynj©ΣaiGn /ΓCC°Yƒ"o_Şξ€ž‚Χλ±Q₯‚Ÿ—H` ΄Zδ(υ&ο]Έ~ίΩνSι==θφڈ·©;!!;z—„oΘΪZX ρΦ†W <ZM™žP*± »ί8T_Ο§ΌΥ<9c–[2b―IENDB`‚SocNetV-3.7/src/images/exit.png000066400000000000000000000027301524402210300164120ustar00rootroot00000000000000‰PNG  IHDR szzτgAMA± όa pHYs ˆ ˆεŽ)ItIMEΡ ³CŸJbKGD ½§“UIDATXΓΝWkOTWυ7τKΏΆ|«ΖΖJ[HtZKB_Φ ΐ@A΅ΨX!0<Γ»„’‚DAPΓ£΄€ƒ †‡„Ηά™Ι†ΰπ( cΫ4Ό1-UΣμξ}䞹‡{bRSo²3™œ™»Φ=gν΅Χέ·οe»}^Ρa™°ΰ?.Ί§n/π:ϊqkΐ~L G~"8ςΎ{ξ`ΟI{v<Ψ²γΐ–‹֌3`5Fƒ”Rš,©‘`I‰€α+α0œC—CaθRτDƒŸό|d"u»‚^Πƒ«Ή&ͺ`€0ANcΐš‰Ÿ™˚ΙΚ&W6~W”-/ EgΑQݝ6‰νmgΰΣυ…Π]!‡ΐzΜz_θŽπΓ?ΏƒOςτFϋCοΩθ‹9}qGαώ9τ'|ύη?„Δ@Έ?‚‘€Oπι?Γ]8Ž;r€Œ0°žΑ{•Iθ”L΄ντδ/|85,ι§pυ`Ν5@‹οkLJμΜiΫχ·₯ιαΧφZؚ’–έ+nΨιES%κ Τ+Έd ‰dG@W˜?ہ ŽΞάψhn,ΐ"μu=žq "΄Α³ΒYυ Τ €j'Αi/4–Βσ^Ξͺ+jπœHφΩ{LM€ZΤΎ7ψ?°2`‚‰D~ζšLx<λP‘˜Nΐmy§AΚγQΎ―AϋœZM ξ,KnΈ5mKr0άƒλF˜,šŸωXIΐ“GΒFŠγ8Έ½ šκ‰Φi@“‘>W ώZΐ•j_4]εkcΕρόΜωΡO=$ΦGΝœΪˆ:’&πΜα άY~IΨφ­Ά:|‡―ΞάΜ7]“.μΒΨ·ηΈ½(†θ6h`φjΰ}Ύaλβ7Xl©Pυωͺ₯ΝCΰVJpOΖωΊ»£š“JH _‹@ # › ,{ZNJ9©2™UK;_Ÿ½―Rϋb[_ίοcΰ#₯ ψ›p0‡Ώ«&@ƒ…Ψv8ε₯εp«]†•ΪηπtΟζΔ}>ZŽ­NΒό4ΰT£‘"Ϋ«@@Γ^Χ¬ …8 ξ—Λ<&ϋψΨw_჆£ΫΎ­&@#•ΘήN+_V£^e―k6Ή¦bœηξ¨α돜ƒ |Ό2  Ÿ:¬Aη9SYν»lόΫ«Ό}Ν~ΟCΰϋœχΤνδλΏυ60π‰kΙxΠό–š… " «}Ύ‘X8†ήΎξθδkσΝίΰS7Δ6œͺ52πΙκT$ 'iΐ$CaB)8aΘ̍ ­Ά4ΨΒΧ\·r8ψxΕE΄-ƒό>ΓΑ7ŒŒΐ½γoͺ PŒ’£œ«6[ ρ'ž₯ΠjνΧvgσΰfŸͺΛbσ¦γΣƒ0ÁέΤΞΟΤ|&―^ζΫξͺΟƒMμυΧJ| ‹ψψ€šHF@#L¬Ϋ;Ÿ{?μͺSOΧ€ Gώέ ύ0½R€τ–d暊°‘Ψ˜Ξ|»B όAS '„φΐ7Τ(:SzυšdΆϋœΞ|γη^ΔNΛe¬€U7¬αΤ›i,·άΥ\}˜Z|TL”ΫŸ ‹P―ΰΚV“½]v8ΉΟw―HΞΟBσWU‘”ΕrΚν@ιυE€›C|‘Υuu,WΎ˜Pn§θLιU‘£€LEeθΩT£ΑBήNφJG=Ί^d°Hptζ΄ντδ πέߎ(·St¦τJ’2Ε(J2&hžΣH₯©Fƒ…Όμ•ŽL†ϊœZΤN‚£3ίήvοΰ/ΕΛιqύ ₯·s•M]ώΏIENDB`‚SocNetV-3.7/src/images/exit_24px.svg000066400000000000000000000005021524402210300172750ustar00rootroot00000000000000SocNetV-3.7/src/images/export_pdf_48px.svg000066400000000000000000000006241524402210300205110ustar00rootroot00000000000000SocNetV-3.7/src/images/export_photo_48px.svg000066400000000000000000000003411524402210300210650ustar00rootroot00000000000000SocNetV-3.7/src/images/file_download_48px.svg000066400000000000000000000002301524402210300211360ustar00rootroot00000000000000SocNetV-3.7/src/images/file_download_48px_notsaved.svg000066400000000000000000000002251524402210300230450ustar00rootroot00000000000000SocNetV-3.7/src/images/file_upload_48px.svg000066400000000000000000000002171524402210300206200ustar00rootroot00000000000000SocNetV-3.7/src/images/filter_attribute_48px.svg000066400000000000000000000021261524402210300217060ustar00rootroot00000000000000 Filter by attribute SocNetV-3.7/src/images/filter_centrality_48px.svg000066400000000000000000000023561524402210300220660ustar00rootroot00000000000000 Filter by centrality SocNetV-3.7/src/images/filter_close_48.svg000066400000000000000000000011061524402210300204350ustar00rootroot00000000000000SocNetV-3.7/src/images/filter_edges_48px.svg000066400000000000000000000027561524402210300210030ustar00rootroot00000000000000 Filter edges SocNetV-3.7/src/images/filter_list_48px.svg000066400000000000000000000002271524402210300206560ustar00rootroot00000000000000SocNetV-3.7/src/images/filter_restore_edges_48px.svg000066400000000000000000000023011524402210300225300ustar00rootroot00000000000000 Restore all filtered edges SocNetV-3.7/src/images/filter_restore_nodes_48px.svg000066400000000000000000000020531524402210300225550ustar00rootroot00000000000000 Restore all filtered nodes SocNetV-3.7/src/images/force.png000066400000000000000000000034171524402210300165420ustar00rootroot00000000000000‰PNG  IHDR szzτbKGD ½§“ pHYs  šœtIMEί4%ΛSΘtEXtCommentCreated with GIMPWwIDATXΓ½–kl\WΗηή»{½Ώλڎk' ΔI[¨HhΤVŠ%UK„T ©‚τAΛ‡FQ¨T U<  ΕuΤBJh•@Ϋ#E u€¦ς3o“Δφ:Ω΅³Ύ»λυξ½η Φv¬Δq–F0ξ‘v5ηŸ™ΜΕiη?r Ejk΅&sΦΩ!« [Ή"Κσ r.“ ŽͺΣΡtmΉœ/άξ>U.πίκ”έ^‘Ϊk+ν§c1ϋ™PΔΖ±@Y`͜ 0Šb^η³zχ5‘σƒ>oτ)0·ΊΧ.ό(‘Υυκ› ΅VG,6ΨaU1χ§Μ|ΙlH ;lUUT…*ΎΡTι H/½ ϊceΰ½(•kšψaeŒ;R(–=/ru=–Ίι4E‘δΥΒwš2―±@&¬Εΐ_UΈŸn`KάεΗFhZƒ1 ƒ ΘLϊ₯τϋάi°BJ5άνξZΫΈΖ’ΎΨΜϊ˜ΓσŽΜπ7 2))G˜¨0d]MΑ5›9r³D,‡Ps£ϋrGΘj*[ΗkhltΩ^YΑƒ2 RŠ)l.6ΓFH‹OΑ (8…°3ˆ£pŒ…₯e^‘vȊ7†•Όœ,Ύ7ΗY| Ψ .Ζ6‰)εI Lϊ6§¦i`»‚ο’Βp1€Ι„‹TDŠAK]4hΥUj!¬ΐ”τ`@)ͺ«qΆΕΕ Τ°$nρ}Η*eΣςbqΖƒ‘l€οŠη]-πN_ώΎ,ιφj’4ς‰•5<Ό΄*ΏΉ"jˆ¦MIηV©Ϊ‘°j8·|"«έ’@§CΈΪζ«1‡uF—D”bdJ14aH‹Œ ;~>NgΜϊυεα/c|ΌΥρ9ϊkύQCtNf D7V;«™Gΰ&ΆEiqxΞDfR(.d!Q4©3?{1ǎωΰ7XπτΏθHψκΐl'`4Ρ&œ›ΦΛoΩ‡cΈΝOE–™™/J1’WœΝΖαΝ_ΐτνζΗXΎκ³ΐθgψšnΎ₯-Es]ˆg­™θ- ν+ΞζγFzΟΒ―œιy_1¨Ζ˜λγN γ‰uΕHlA{!΄ΤgKΔ‘aΆ΄RŒgrZgαπpΌάέΡδ7—”κ¨EJjS„ά‚%X»wϊ»7~ι{δJ Ά< §§ 'ςΡ‡π‹,•ω6τHγzw:0šΤ2šbA38nŠE8wίξyΥY²qΣσ•;χΕΓ‡Ί°Ώ΅2–ΞηΜtΏ'ˍΎ17ω"‰ˆ.Q6Ξ' 9aΊή½Ιa```uh-ο‹ΞedΌϋΈμ_ύ€lƒξ‡φrΑ‡ΧΦ>Y\₯¦‚UHp’?…ξEφ[θŸ@׍ΒwΪΪΪ¬ΦΦΦJ«U@)΄α’―9ν„3“πV?œ)—@m6·]΄T5§=N₯`0u^Ή±ŒΦΑƒ?‰DΏ>ΆΩL†{zΡ+WυrΑğQEΏƒ)ν£bN¦0Πύ&ΈiφχχU_Βσ2cΠZ3:2B¦P˜˜¬~=#倯۴΍ης?@KΤ]"ΠsςΒτQx‰λΟ–λ>t^šSƒύL\'™J‘L&‰D"}»vνϊ}ΉΡΏνe€pΪ 1₯jzyθυ0WΰύΏ/$>ΐN$Ζ mmmυ―}ζά™sςωυλ­ΙΙΙΤ‘#G^κξξ>QxWWWλŠm?έe?τ…»$ΔeКccš ΝԟαΫΓ‹e2ξFΦΆ46ύιλO<ΉγΔ‰ξw=ϊΧrί‹žηύΚχύ| "ˆ^Zώ½νYω ΘVΨ·¨σ–{œΆ%-ΟΝkT— ήΣΣ³χύ AΘάΗy}o§μ„Ιεpοbώ–ˆΤδςωƒσΪC€Ιr ΄΄΄|Χ³D©ϋΦ²,. 14šΰ|4q%pu<™Ώ6q–i©Tj…ηyn₯ύ‘5½½½„m'ΉoΩ²_ήΞί™.ξΐR©δ˜ ‘uκλοB)…οϋ$Ζ;Rƒoηoq‡–N§ώσ]vz7§±m›ιιι Ώ+ΗίΉS=φ΅q7²ίrμe_ήπΗσΌ5}}}―ΨΆ}•΅ΕͺκΤ²{Z7΄67ί_S]B[·n}ˆ•{‡Ί-MΝQz-ztμJRσΆ†Ί:ϋNοψς /9H 6IENDB`‚SocNetV-3.7/src/images/format_color_fill_48px.svg000066400000000000000000000006231524402210300220320ustar00rootroot00000000000000SocNetV-3.7/src/images/format_color_text_48px.svg000066400000000000000000000003401524402210300220640ustar00rootroot00000000000000SocNetV-3.7/src/images/format_shapes_48px.svg000066400000000000000000000005451524402210300211740ustar00rootroot00000000000000SocNetV-3.7/src/images/format_textsize_48px.svg000066400000000000000000000002311524402210300215600ustar00rootroot00000000000000SocNetV-3.7/src/images/fullscreen_48px.svg000066400000000000000000000003001524402210300204700ustar00rootroot00000000000000SocNetV-3.7/src/images/gridlines.png000066400000000000000000000020341524402210300174160ustar00rootroot00000000000000‰PNG  IHDR M )sBIT|dˆ pHYsΚΚk²tEXtSoftwarewww.inkscape.org›ξ<™IDATH‰ΧYUEπ_Ÿa™†aApdD άB Α0l‰‰ИθΧςΩO Q#&ϊ¦/"HΐHBΠ e5ΐ”η\Σ·9wζ^+ΉΙνUuUWUWυI‘J)-ΐV gp…„q†pg£ΟΣlr)₯U˜ΔœΖՈ˜žA~#žΓ}|χώ—)₯1μΕoρύŒ^ΆλWΨ‡Ήψ΄gD"β‘vb UΏ]0 ΏΒ¬lγ?”<Œˆ[N5ͺo…Q¬T‡z —p·ρsyβ”n,Œˆ“=#€₯ΨΧγ${ρVct^™EΨ€w°Ή…Ώ ΫΊ°ŒΉ Z”Fρžž-™Ξ|ΌΤ8’ ή›ωrΖζΒ8Φ―αŽœhqβPW 4Wg("Ξf9›‹8·3ΌΒ«˜Vίϋα&z7πYD<(rΏcρm†M⯈ψ₯γQ[θ·b²ΐvανΖΰPΑ[ΤΘΛŠΫƒγ)°Γ:]Š[ς~° ^(ΓΩβψZœhΑί+Φ―5)Ά -…2‘­ŸΕžr?Η μ ¬ΟΦ#Ψ_a<"ι¦' l'ΎΣ'5up&₯΄-ƒOa}&sσ*,Μ•›"{­γ\D<μׁ†Ξ«λ’CΧq·©ΊŒ5τ€ξΣ.Ÿ‡{κŽ ’Ž{*e*uΎrΊ“+ͺΫμA­7·πΖb=ΏΥ7#_oԁ”8.π?Εϊa…άFΔ}¬ΙΦ—°nPš=Ne­ΑO…LUαJJiYιYΣ ;t>₯΄^Ÿ”RQχƒΛ<†‹…Μ=κJέUάΩe˜jιf­S°₯RήόMpΓφ:Wq]Σ‘:W1RZžaγpJiΟ '<₯τ>Ύ‰ˆ›k;.Dχσl<"nt†ΡŒFΔιl³„#β£ΒΘ3κat Ώ6a\ψ#">/δΧbUD|a“Έσ°Αœ–qzL§cyΪ‚Žztύο1Ν¦Z6ι8±c€Y0„=xK ށό ₯βRμmΩ°ROΒw±bΓ£κΑuλZψ[°)ΗΪ₯»ΥΛ3Q0³§φŠ&·Τύ}K©›ψΏGφˆΙς>_vμq’Iυ΅™mφγ±^5RΤΧκ6ήL&#؏ΛΡχ(ΞτS£?'">ι)Χˁl£•Ψ­υi\)SSΘ―Εfuέ|élγωx^]¨Q›ΤΣu όqϊ/GΆŒ΄±όIENDB`‚SocNetV-3.7/src/images/hand-pointer.svg000066400000000000000000000032731524402210300200470ustar00rootroot00000000000000 SocNetV-3.7/src/images/heart.svg000066400000000000000000000046751524402210300165710ustar00rootroot00000000000000SocNetV-3.7/src/images/help-hint.png000066400000000000000000000031511524402210300173270ustar00rootroot00000000000000‰PNG  IHDR szzτsBIT|dˆ pHYsvv}Υ‚ΜtEXtSoftwarewww.inkscape.org›ξ<ζIDATxΪ΅—YhΧΗί™3۝«{-Ι’κΨRdY8vIˆΣΝN]‘.€”Φ]θCmpθB!}Ρ[i^Σ6Π6†Bi0”RόBš(KΪΨΩάΚVe)–,EWWWάmΦσ}e”W“τήZΏ9ία,σγc8 ‰ϊbv\ΙcώqϋΦ!οM _ΝΡ½ \9ZΝΓπ§ΘτiεM„w—ƒ‚x5ηξΒΊ8ω?­‘Κ¬ΊoaυŽ πίΗΏΖMώ₯šzͺBΞhθ2@JP LΘې΅‹Fž€j=­OίϊΥ0y1} {NPžQŠ  v‚H!’М·πΖHlsY?|+Œ@αCΰΏ<+έθ‘lκL½r›ΉήS벩u8―uߏ›zmφ+­|ςΛΕ'ς+ωOπ!h|ςζ‘1Ω ŸΙͺΓkβZ1™u&&’ΫΌ/»5Ψs:ωΐ°Ρaγ»|yκ·κγ‹WϋΰσΖ)₯4ρu1ΩυΆ•.”― ’Ϋ(@L,,K‘šxTΙΚ«ƒ¨―ύΐ} MNΘα―4Υΐ'ΪykΓD―m+{Δ&+°ˆ*Š @„Δt ›FFΕlπ€‹©{Rzϋ™#ψχ€ΎυτΘGΗΘp™?eC6»„Œ-ο°G$LΜHΪa'%ΩΞHZ9!1€H,οx`9žP˜¨αΟx(&nΣ©ž3 iτYΆu’ΌC›εށ²ŸœΆμ`½ν†·g`ΰ¬j†Γ”%Ά „‘-ΓΧνΰ8A7fHy’±½5Jώμm>ޟΌΉ mΏEaΈyDK«–?4˜€~9αλK*1ηDέN‰²,Βιυ?’ΰNPΰ=―Βp?8³–FQ[κύžδ™’ννύTς~‚;)€S«)Οs%>€ΤΦn‚P1hr›: JήΟρ?’Ρ Ύσ25ΓαθTε 16b°EqδQ³Ή΄5‡O.7φDΰυε‘‹γjγ[XήΌΧ“[# B™Ξœ$έ<φ'Ÿ°'?~q’Άv‹§žό*F‡<Μ_~ΜΗOΜΰ‹>ŽΗΞώPαα3—ŸΨ+6f₯Υ‰ων…59uωώ‡@ ”ΔΚ–Βv3AE·Π ½@Θγ$Aš¦π=Ώ0‚₯5ξΏZΝ&κυ:ΪνΆƒ½ΚR“Š’Iš`yyΪυJAK©"φže 8yž—•‚ φ¨ξ3cii ­†QsΟ?}κΨNΨ|ιΨ±cγA)€bZ#l6»ΆmθΒ… Οξ©@οϋw{ίyξΘττΨππΉ»yξάΉΩK—.½ΣηνΈwΞΏπό47ί¨Vν™™™ίίDόΉ1OplIENDB`‚SocNetV-3.7/src/images/help_48px.svg000066400000000000000000000006001524402210300172610ustar00rootroot00000000000000SocNetV-3.7/src/images/hierarchical.png000066400000000000000000000002161524402210300200540ustar00rootroot00000000000000‰PNG  IHDR szzτUIDATX…νΣ; DAξi<€…λΚΖζ‘X@γ„OαG /ΈΙ|DχΒι Μ38ՌΤϋœΰΜϋ°ύεΚ':τιMΟ1ueύIENDB`‚SocNetV-3.7/src/images/invertmatrix.png000066400000000000000000000012061524402210300201720ustar00rootroot00000000000000‰PNG  IHDR22?ˆ±bKGD ½§“ pHYs  šœtIMEΰ ±?ρ½IDAThή혿KAΗ?—σRF±1D°Œc'J$Δ@ΑΚZ8D$)L#€΄ΕtQ΄cγ?` Q ‰rX£1c3‘»7λ:;7 σ………yοΝϋΜΞμΌ ςL]ΐ τ“*5#ΐΞ €T‚¬H%H;pŒO%LΚΐT©άXκ`C˜ HΓ(=3@(`Χπυ£DΧΘLΌρ€ ψδ«₯5’ΘۈŸ6A2ΐήA–|ιΌ#„τnνΘΌπΊΠ6ιΣ/·RXδ&ΰΌDϋπȐaΔW΄Ν‚`3μ Θ7!Ι!mσJ°Ωχ‘ΡPΔgM°P. !©Ν>ƒ‚Ο‘Ε£Dde!©Ρ~5ΐ…ΰχ²·΄j|—ΏΟA¦ 'I}†E_λ β!π+F‘υyη δu‚ Ψv2›0ˆZ\L«‚ΔΛϋn ψ δ’¬΅zMί]1'’ ΰTŏ1b ρ“1Λ;bΔΜ ρΞυ“uMζt6FΜjΰΛλΥ πCθπΣ=b/ qΏΨi5L«ϋ,ΜΓ)³ή&ȘΠYAο/q•3όDήΫΩ:𳇒%(((((ΰΌXξ+­–IENDB`‚SocNetV-3.7/src/images/laplacian.png000066400000000000000000000005131524402210300173620ustar00rootroot00000000000000‰PNG  IHDR szzτbKGD ½§“ pHYs  šœtIMEΰ  6+ΗΠvΨIDATXΓνΦ±JCA…αοκE°΄±Sl΅³MK[m‰©σ‰–)Ύ‚`ηSΨ V–Κ΅™"\BΨ”90°svΩωw‡…%•Z²ͺόsμOελαύš&hZ±­,»    ™!Ί‹¨fXσρ―Z0Ψv΄αύπ±†U\bάαUUπ0Qπα=EΡηΩΔŽΡΓkΙΫ<ΔΞT>@{<Δψ1_γͺ.ά¦5ήΒ#^°χέ·$ΐgkσ7Œp‚wŒ1,ρrREυΗ)eš¦ΰIENDB`‚SocNetV-3.7/src/images/layout_levels_24px.svg000066400000000000000000000002401524402210300212120ustar00rootroot00000000000000SocNetV-3.7/src/images/line_weight_48px.svg000066400000000000000000000002421524402210300206310ustar00rootroot00000000000000SocNetV-3.7/src/images/net.png000066400000000000000000000024361524402210300162320ustar00rootroot00000000000000‰PNG  IHDR κ‚£AsBIT|dˆ pHYsξξΦ²4tEXtSoftwarewww.inkscape.org›ξ<›IDATH‰Ε–]P”eΗΒ©|ω ©Δ )³¬Γ@yQ~±:]„£‰_•©!!ΩMdήT3 cZMy‘3IΞ4cMYβΪ 3 N¦°¦ΆΪަΰ Λ’œ.XŒέeί­™ΞΜΉxίηœσ?Ο9ηύŸW‰—ϊλΈ`ndκ,ύ“ΣωΘΡΦφΰDKKΗMΝADD³nΜMω€ςΜ¦;ύ½{μŽξέφοΎΝi]»ϊι­q”Φ²gΏ°υΐμ}±³ΓGT­ςμ͎’χ*^ͺΆTω+@2`0Μ\6`ρσ±‘σ#Vk‰₯\4Aηι,$Dηρμ?olΊ[m³υyίάtΏ·εΖύΣΎΖQJ5πκ+Ζn[vu>tμ±?tμ±_©Ω~/77eΏ† X₯yΰ†D―,^‘ΦηxδlΌv―όβEΛI ·^ ˜ύϊΤ€ΙΐY?}-"~“Μ€C«“Rj`Ξ e£Λ2™Ά,_Ίt5ΰCφ)€ή[οV€‹_²L¦%%Φέhh°7ΦΦΪχ›Cβ8AŒ~GV ˆ3Σσ2—-‹fMNN²Υjέ x© ?Sjb°ίοˆH+@ΰ€‰gΆ Χ+₯–0ȝ"R=¬o‰ΐoΎ"*₯DdXl‘ΗτPW_½³Ώ±ρ›M.VU} d_F₯”A)δ2yBDzΌN›6wUvβ‰Β‚ σ»E‹nΫΊ°h˜iΚF$­ΜΚzmajj&Π[m6Ÿ>ΜωyiηφΌ<Ξ] «΅[6n.=|ώgK!hγφ ˜ΑIwŠΘ©€6`SllΔϋoξHΣ{ =IF/zΦ>HΞ)"΅"²CD6<5',$..Βk)β#§+₯¦ΐ8ΏQJ©@ŽδΨXRP4Τ/wewήjoοUQQ‘Έeικ:ΐΛΝ υς–-T•–Ryκ_––rdΓ>LNV&O>mmέǎύΨάαιΌί9@]ύ6Χ§ηώ.5•ΝǏsGϋh=tKR&OLφlΖ“Ÿ^ΩfstΩ‡koW‘=GZsTTθ¬Η¬η†CσσΉκxHss©ρΆ΅’wο*Μhϊ©l­ώςφž#‡_όseVBELΜδ€Ά£§Neρ… tz?z+?—‡†₯ΗΕE­ζΈ³3p11τzρΒί „……‘όξΙFDϊ€*—Ί•1ΧάΜ³™^O—.ρWg'fo6ΎΘp§“¦’<ŸΣ ee4Σώ-ΈΫ~ΕΔ0//»}d―ΫΫιYΏž`:ππpψxFλŽwK2·oΛ5₯ΤsΧ―σωόωΔ‡‡i³q―ŽΖŠ v‰ˆ(PJ5{~ΐ’υβ>-–a;ΩέΩλJΤ2ΨΖro,¨܇䦋Θ]ΧzΝZDδκx~μ­N8_²IENDB`‚SocNetV-3.7/src/images/net1.png000066400000000000000000000026131524402210300163100ustar00rootroot00000000000000‰PNG  IHDR M )sBIT|dˆ pHYs₯₯pΔήUtEXtSoftwarewww.inkscape.org›ξ<IDATH‰΅—HΤwΗ_Ÿ;—‹ΣζθkQjuZ‰e-4όq‡6·¬μ‡b60ΐj`e4nLΨ² έΖͺ,ϋE›,jζα-+ϋeδR„R—c¨ίSΈϋϊΩvϊυ:Νχξ>ίηϋyžΟϋύ<ο{NH)™ΜZΊT„FG“Ltg'Ν΅΅œ³Z叒Rzlΐ΄’"J‰β΄¦&zV­b£ΗgM&υλΙR”‘ΰNΛΝεŒ›dΕxgιΖBFαg6‹=B?—}ƒΑ@”·7zUm&!„ΐhΖ΄4‘μ9"~OJ…† Q‘Α‰²2šϋϊPΞ₯%#ƒ` °°QSC—φφv;Jb"ǁ€€ƒωEϋάα@Ω»—oήHAT[ΪΫιqΎ88ˆ­­˜vjύvνβ«Σ§yl³‘άΈAGv6—ύόxGJIXΑUUt; h“8uŠFΰνq8p€sš¬T•~)Qςς8ο†_Cl,Y ¬έ·XΨάΡA―λUUt8n Ό|Ι UE―Σ1¨Σ1¨έw‘O©¬”Η››ε_Ϊ}«• εε4θυ¨CEX­άxϊTΆŽ[ώώΜ―¨ Σ™΅”(Χ―ΣΉlAžtJJ [ ±>{Foc#έΉΉά]»‹«Ÿp"!Δ΄ΐ@>MIa^` ο·΄ΠP[ΛΟΥΥςΧ±:fΌ%ϋϋωϋξ]nk₯”—ΗEΨθ'£o2Φ§¦r2>žέkEΌΔύΑ33ωιΦ-^: tu‘pΗd"Θ5 ΰ3ΥΑ#"0?xΐ?ΞpΦVz:E^BΗΆΘH6=Κ²Ά6*ΒΓEI}½Ό?Ξέ­πpΆ…†β ΔPν  ! ‹…”ΦVϊ΄Ως˜6U¬^MςσηCΊ08ˆΝΩaϋφρ#‡sQj„GJ”ή^” ψdͺΔώύΤΨl#q\‘cΕ b½όό˜ν ™˜¦Š)₯B¬~ς„]K–°Ή™ΞϋχωόΡ#ω””ςUu4••Ό ρLx<@#˜1όέΧ—™99\ͺ¬€³·—ώοΏ§9=/§8θτΩ³Yτκs‚φΩ°†…‰ω³fwυ*]RΚ Sύœ9bfRωX‚‚0^»ΖΝςrξΥΧΛCZ~\³ζNΕΝ·oηk'½NŠKKiΌ‡γΩζΉs1₯₯q4/Š­[9¬εm‚°λOœΰΆ3Έͺ΅Ÿ’ $&’ετσrέ’E΄εδπǎLp8°,_Ξ&!D””>Aνv#H£ΣλQνv„ͺ20&RJvο¦X+R’τυ‘$$ιΞίd"(6–,\d<9™/μv»}D~‹‹Ή x‹€ΙΔb!02˜ ΘΕ‹Y |λτBΜΘΞζ‡’"#"˜qώ<{22ΔΕ’™PVΖo6‘ρρΜ›Η{55ΤέΌΙwRΚadά"šJΎΓ1R<ͺJK ŠΩ<Ί…’’ψΜn=šΧΤΠΐJ`3πͺtΐ»nkΕέ¦―/3’΅uh8­«£{ηN.14―>ΜΤΙWϊξp0ΰ΄δdΚ'Z¬n)θι‘έ@dLŒψpαBΦ44pςΞy―΄tΔGαΣήNŸͺ’©Χ£ΨlΠߏu‚…:ΉFN‹Œ$‘©‰΅ρ~I_› =]f³ΨM²ΡHπγΗΤUWSάΨ(οMτύΆ_οAhυIENDB`‚SocNetV-3.7/src/images/network_three_48px.svg000066400000000000000000000007351524402210300212220ustar00rootroot00000000000000SocNetV-3.7/src/images/new.png000066400000000000000000000015241524402210300162320ustar00rootroot00000000000000‰PNG  IHDR szzτgAMAΦΨΤOX2tEXtSoftwareAdobe ImageReadyqΙe<ζIDATXΓΥ—ΝNa†λ5”•{qαΔΰΈβ\Έτ\±0κ–&bXΈ°‘X ѝΏ‰€±R€HE”ώΠC¦¦e€γy{ϊ…QJ‚Ι!†Iޜ3§σΝϋœσM›N„ˆ"S‘sΐΗՐn₯ϋ¬Η==d ©π12<<Όj4::ΊV<―&“ΙV:v‰Dk`` Νknh€8ααq™Ίο·Ι²,Ϊίί'†ρxΝλŠ@?σ°Η₯LfΉ Εb±:―{pˆSww²lΫ‘ωωΟdΧuιωΔDB&{ΑΙͺ7JΞΝSψp]‹T‚@gOΞν¦Ggfιυ›·Ÿœ€©©izχώE£Ρe^AΐονΆ%†…šγ”]ΝΡδτ+z2ώ”žΕ^ΠLb‹Uځυ <jΝ67«”/nS‰c·…Χ~QπyΜΝ]΅Η{ΫξŸ;Ύδˆ]Έ½ξβ”Κ3ΰuδΖuWbΨ9κζ3DΤ§ΰτ:­9"˜˜hr€˜kPS*-Ή1βNSŒ ςmϋ(Ά|E›;ΫjόiŽϋΕS(πCΈfIχkωR‡ΧΎTΘUΊN­K―…ή·Β’=O¦³ή£±qλΪΠυ˜³n©l4€kΰZSΤ Yoδξ½β₯ΑΛK|mŒu‡5¨ϊ·έeΩ<ώžΟ7½ΙΊx&oFhς™”žι?ΏBΣτό½œž₯~Ql%‘’• wIENDB`‚SocNetV-3.7/src/images/new_folder_48px.svg000066400000000000000000000003651524402210300204650ustar00rootroot00000000000000SocNetV-3.7/src/images/node_48px.svg000066400000000000000000000002411524402210300172570ustar00rootroot00000000000000SocNetV-3.7/src/images/node_add_48px.svg000066400000000000000000000004211524402210300200670ustar00rootroot00000000000000SocNetV-3.7/src/images/node_add_outline_48px.svg000066400000000000000000000004231524402210300216300ustar00rootroot00000000000000SocNetV-3.7/src/images/node_filter_48px.svg000066400000000000000000000005761524402210300206370ustar00rootroot00000000000000 SocNetV-3.7/src/images/node_properties_24px.svg000066400000000000000000000010671524402210300215340ustar00rootroot00000000000000SocNetV-3.7/src/images/node_remove_48px.svg000066400000000000000000000002731524402210300206410ustar00rootroot00000000000000SocNetV-3.7/src/images/node_size_48px.svg000066400000000000000000000005761524402210300203240ustar00rootroot00000000000000SocNetV-3.7/src/images/nodenumbersize_48px.svg000066400000000000000000000017221524402210300213700ustar00rootroot0000000000000044SocNetV-3.7/src/images/open.png000066400000000000000000000040311524402210300163760ustar00rootroot00000000000000‰PNG  IHDR szzτgAMAΦΨΤOX2tEXtSoftwareAdobe ImageReadyqΙe<«IDATXΓ­W[P“gφΚΞμΜφb/ΌΩιΞμn½ΪΩ›΅ΞΊ;{°­Μz±ΞΞ:³vTpuΫβΦΆT»₯ mΑ‚QB€€€!’@Hs$!gr€B œ G΅Txφϋ~0X‹}gžω/’όΟϋ>ΟϋΎί—]vύ˜ ρ ‚Αu‚ΟύώHΌ› αW―ο΅*ŒΦeΫ`[ 'ρ3ϊ"όά>vH~Š ΉΆY2Ο­B9ώDφQΨΗζ臆={φXSR,Κ::NβεIΓΔ1·>Iω,`›]YSMMΆ4λϋ 1y`Ε»οΎ?Ε«Ύƒρ‰)LOΟ’οΧΌtŸΎγ²CO΄3@‹{Ν=.4λμ¨Wφ ‡S…2^5CΌ°τΑ`\&ΏK|αH$8Aύέκs'ρΉ'H‡—ΑΧ» "U7ά7’ΈNˆ,V>ΜVΫ:q,k,όΞη'‘6“•?F}₯ώΔoτY1Ά~ο Z{œΰ?0‘L(CF²ω&ωΕeΜ-,!4Ώˆ½{χZΙ;~Ίm$~CF=5iu³€?ΛΔβšP‘Z΄lρY Ά ¦]/²sq·ž 1% ΝcbjƒC'δέΌ-Σ°;’3&ΤS΅ΣϋXOˆΕ!ˆ,CPΊFΠν Bε›B›sό©ΟZξ*tΘΌΙE §l“Ο›ˆ'§Γ€o’ΦΦ%ΒQΫ(‡Ζ―‚/b”M‰$"κR-šB«θy‘ΕΟStφ £Σα‡Τ<€½ ]Υ ‘• /yρΖͺ©Τ³s LΕ”Ψέο…Ιb·Ό₯ε•KσΛ«#7ΩζΩ„—Λ<Πhς£έξ_'–;† ³xΧ}ζ€ψ<3*6ͺάS3!ŒŽ3Σ&δ7 ρΑŏQs―Ύe`ό ##ςΞ‘Ύ]ΉΈQƒttM§ g€©ΈέκƒΨθB“ΚΜψ|εΛ,ˆΪ$Q‰§g熆G`w8I‚:$|ψ!³ α™\€o Аήα,«Δ}οέaλŸΐΉφ!HΜύO}ξΤ"U„ͺšΊM>GδŽψ<<„Σέ½ΑˆΒβbœ~/=τ/ƒ„ΌΕ-ξ:C(Q‘χφρNάΏ}„3iγ τ3«ΰΙTh58ΡΨέ žX‰¬\φ3>Gͺžœžeδξχ’ΧlAC'bγ ιΦΐEΟR$Έ†²žn΄ΫPΡD…Ξ‹J~ mΎ›['Ρ ™ψe"ξ)τ(Ȑx5 ­>ͺάc“π Γf]ŽΔK—ΓΚΓΤcΐNzI1Nϊ‰”[;„|…%jJΥθς0£("ψω3 t.‘¨Ύ₯| ²J*πΟγqQεφFΡης@«7 ύj’ΏHƒΝ7'©Ϊ@Lΰ{ˆREέύ qA—ΕέˆœAόωΝ·]„λl΄CΠ(χN#§ό²K«ρQκWHώoκϊXQΉG‚γπ ψ`4™QΙ«ΒϋgΟAώ@?ιn²Ήoi›*/rεδuφ‘πΎ•ωœΚ)ΒSΈέ)ά+vQΘΕ•ky8Ÿ€›·n3c‘Ϋjs@"mΗ…„Pt» σ+€Ÿ4Xχ$ |„JΣ8ϊa†œVύU³¬;Έ:Ω!z/ΰΌΊ]&ΎΑƒ”oΨ8ŸœŠ=c―™ιn*·FΧƒ€ΛΙH:‹ŒΥΗΡΨ3ψ81 †^›ψέϋQOΤρ™ξšͺ­“`+] 9υΌπΎg½κΜ=JUm”ΤρCαeS@πΚχ%`+njΗ©„DΔ9Šά|6ZZΕ8ƒ/95Θj˜ζ’Υ'υšzL‘Id·™ΫnFΉΪ₯v9,9=ωNμΩrΤG ;«Fˆc9ίξϋ=ωœΏ€“ρp£­ΔOJιnΊX¨ο,ϊ”˜P(·@ι<ωWμ)*w-ΑgϋΆΉδDΎΜ²Zόγδ<τ7°rσ°οΐP Ρ!‰'e*¦K…>Ώ!ΥFδ.[!° εάβδ‡o<―<η–5œiEεψϋ±X?‡7φοǍ:’«€ !νpκ5U!‹4[Ι7*4nΤI:ΓrsŽm•ϋ‡$ΰJespδψ)>|˜Œc.2*\"ΥΣ]~Mά 6ιtv§wŒδˆΆωž„·•ϋ"½Iύ€ mτ2JxLœKIΓ΅¦.|Βme6Yρƒ\—šΑQ{ σΧΞ%&6ΘύǝΘΥ‚άΞ^NEX…xφ]\©Uͺϋΐ–ΫP­uγTA/Κ rΏΊΣj£·’Qψ―COΉ-ˆΛζαšHͺ/š5ζΗzσ-Wx¬¨ά―½¬άΡβέ\uήΛ―EΉv2g`υΒ§—©άχ©ά;ψσΒρ‚ΚξP 9”Έ ΒΘ£‡Γ/"χΪmŠέa™ΥΆΨk»^2Ύ/‰fΉ_ό€=ΟIENDB`‚SocNetV-3.7/src/images/open_48px.svg000066400000000000000000000003411524402210300172740ustar00rootroot00000000000000SocNetV-3.7/src/images/pdf.png000066400000000000000000000100661524402210300162130ustar00rootroot00000000000000‰PNG  IHDR@@ͺiqήύIDATxνZiˆ\U5KΜb\‘QΤ .Š‹ŽqFg ξΛΈ(ώqA(Šϋ. ?DΕ•ΔΕ ΡΜΐ„LΜ’˜Ε˜ΨφI/ΥKΧΎ/ΥέUΥU]]έΙ7η\ήχΈσF['ΎtFπΑαV½~οžσο»Λ{MzόvόvLf³9Ώ2ΜfίΙaŸΙϟ?!Ϊ%ΐο₯Ώ2°ΟKΘa_E88I|>&&φΘzμΩ#ϋ|ƒα²Η\ΰΑQΜηe°X”‘ΑA2<4dCJΓΓR*•€\­Jmο^©‰”+)—Λ’Νd$K"3Ώ/σ·?4ώ'Žίσ3――πήΫν—ΣOφ™9ΛΎ p‘ΰΰEKΈ¨C΄¬­ƒt°::*©φvΩυή{ςΚ+²υ‘‡dΥHΗΫoΛ(Β€’II§RFŒ3a~ΨpΞη³YI&όΜs0ηΎnΨƒƒ>σ ‡_,€QYoΖh«B‡kπiΛ›oJ²ΉYF'&€ Tk5‘-·έ&;ξ»Οˆ@ς™tZ@¬ŽŒŒ8­ύΘεrΐd‹dξM!x dΉFβ«JΌ€Δ-TλuΩϊΨc’Ϊ½[*Vη«μ4„ <α‰­Y#E\+ƒθ‚ΒR,ξy@טsΆ;,!Κ*Ι+p]ίpmΖ)Hή±pmlLώzΠA2!BnI†-XΏ^V{¬Œ‹Πώ6L‡k¬ όΑΟ9πΌP14}* ΒoTaΫ‚!žK·΅Ι»`N£H5`”ΐχqΊ`ή<ΙνΨaR&‹ά&9‚Ÿ)Β(jˆύ?lsŽ[xΎJaΤ%žtΡ`hΩgp”&lςεb($B€>*uΤ‚šE€ŸIl ηΫx@v=ςˆ4π™iEςy9ˆΐsuόΦ€–`šςy½οη‚€Υvšψ'@ΕͺΤ.Hή±+­Ώόˆ#δ³3€ΠΥ%5Τ„šC€Nΰsi[±B6wœpX―γ7,‚ ΙΟ<ΗίΌ6ΐsꊚΊ‹Ÿνͺ"π»οX„mΤ4†΄½π‚όmώ|ωΗβΕb: Β$© εpXVΓ)γ Ζπ(bΘΚ ’s@!ψΧ%ŠΒB©ŽPθ½-WhΏ4%όΐXΜ*Pϊ™0dA°R_~ΈόX{ϊι2Ž‘ΔΗHή!ΛΘ―C*ηΐ D(€4­δΠς< ήΓNuX°]‘"ψ*€S•ˆΌΪœ$“6ΘJ€Α*ˆ°ρŒ3ΔΡ†%ΐζC•δΗKc|œη #Ζ’LβBPήkΜI bTΙ³υΧ~²ΟΎ  U¨€Q%π™•ΎγΩgeͺύΤ„ φF0„Ρ $IΎ?η Ώτ’Lΐ1A1”8σ#yδi_Ψφ(α―ή|·+δzρE#BΡΐK^οΓ–ύDZXΈ‚BL½ήΫίU€ΎΣN“θUWICY ΫeaΜβ ‰τH!%˜<–/—!€]㽞χ~LΛ LΕ€"@2qΣM¦.πϋ0φpC€α"…‘"sώωRϋώ{3»l@< MKΞ΅ &I{Xμ>]2wέεζ;Ϋ:_ΌύvIaΈLAˆ$€+bsηJφϊλ₯>0`„›D'I€›,ϋSΕΟrŠ@’3gJαΙ'Iά­ ‚Ž(aʜ:λ,γˆDˆ1™3G2·ά"΅hTŽή~؁!ΚεΚΐ:ηDk€„0|ν5³|ΆmΝΏrφ‡Άψι§’ΐΖIΣθ(DˆRΜ ώ7E!ϊϋ€υBG«ύ7πͺύ³E@”+ΨŽ"še9oγ΅tl―Cz}Cž…’#Eb„ D„ˆ_{­”·o—qPβ¦όu€G€:ρc.ψQ…¬Δ‚‡<―²γžΉ?s—δ9΄©c BˆF‡ ŠcB ύ’Χ ž{?\b;FρΆnικςG€ΊN©w¨Ÿ½kž“r?Τ‘Λ^ŠNτYΝω{Vσo*Δπ_HψΌσ€ΧθΗhzjF.I`έ1°l™t ΞμΖΖLευΧύ@‘h(¬5W‚ 7ή(ItŽŸ]g\Έ¨ύ•ΈώCΑY “ΥΞNIaΊ5‚3ΚDθΊ Ρτ\q—›ώ₯€KήώμΑλΒr@ϊδ“9Μ‘EqΒ]±Pΰζ§#š^Γjς 2Ο?/½'dΦέ@'W¨ pΙνX†WήxΓί"h‹ iπ£"xάΐάL̚%ΉΛ/§J”ΧsWwt‚I)ύ:„ΏŠŸ|"!L£»±Ή@δIΌ σ„ΐ™gJτƒ$ΉBψw$€YfΔhΕ½*o½΅ŸPΨ"Lβα˜@‡‹x†HR*Œ½°1‚9ΔIΊΖ'ΙOGύΔ©§2&ͺhΣΧI<βfr|ΖωΠύχK:“‰ΓΪIDœ€ωΌ³θrΞVΊΐK*ΐ…ΎN…φΜpR Β5ΜΘ’\Ωέ}·cΖbK—J5!Δ™†FcyΏυVIƒ’FDq§Ν3 ΗΙ#ΝVW|ϊ²E’8κH$’,ζψ+€“ˆΑh½ϋDQΕ‹xo( ‚!%ρ ˆyΈ#tΙ%’jiag5Χ¨λΞ―>?t‰§=„cpKs„0κD μEQ-Γΐ¬_Ί->)q{ΏP["f)E„ΓΑ]Τ0ς°~γxκ«―$‹{θpF0Κ,j$―WJ˜QVΒύX!φφφJ'f‰MMMF¬•+W~Η3|{. νdB΄>ΙW»»Ν’6„I’ΐΉσƒΙM"eΡYZœΡζlλ’Χ¨ΣβρxœdYιλλ“žžιΖ΅IxΧ]ΪΪ*;°Ÿ°vνZ όm;ϊ0d|@[bRGΠ "’Ηƒ0μΞ¨ς\ΣΓξρgž‘<Ÿύƒ`Qηhΐ!‘Χ'yξθ2ς$Ο¨3$ω-ζ›7o–M›6Ιϊυλ ΩuλΦρ»ωαkrίνάٍΎ8˜ξ›*Β€°θ!’‰³Ο–¦¬&ς΄=ΆΏ#X dΑ<’œΕotλ[_r Œ<]@Ϋ3Χy’©ƒ#«†΅kCθχŸj}_πΒ~„λρςΧ_KKSξΰΈΆΗΔ&…!/ίΣζ$GΒz ϋ­΅>sžωNΫΣΪ<μ­q}Ζίη!T .ΩΎuk }Ύ 8RΙοWάσ€λύ>’ΘFqCΉ’^(yόFwk½ΧΠθ«υ5ϊ¬πΜω-[Ά/ω2Ιηr$Ώ·Ή©)ώ^ ,Rςϋ]›|αΥWŐΗD&Lλc»χ^³‹“Ζ«²9ytΊ,Ζ1F(€Sυ) «= sœΗ—Φζζϊzν΄iΣ£©όΐΙΣX(°ΠΒr4ΒηP’Ο='‰ΰΑNJΰ>βάBi ΐΒ†ϋS(ڟC«ύF<?DΎ½΅5ƒ~ώΕCήF 뽝W°2‹`–έ£ˆz¦­MA¦‚Θυa5Γ{Βyt˜€4ο]x='IjΨΩa1γΫ‘δ7f‘λQιyxΙw΄·§ΡΗ«@ώh?ΙOώŠŒε†šˆΔπ2T6a»«ˆ&Ι1ΎύΰƒF€ 6@uΡ’±_caτυυΦ!ŽϋŽύ“χC!ιΒ8ΏΓ’E~W[[Œδ)ω)€Π”Θ 2!nQγ©NEčl ΰΦuΓ^Ώ%)%―ΰ5}©‘οφrqC‚ϋγ°°Ϋΰ€@ŽΓ"¦»m--ύθΫ2ΰ(-xS*Α|NβGδΔeTΔ}s„ŸSXΒφbΧ&ΣήNBϊ?^0ϊθ ΰζ?ˆτχ3² {IΎι»ο:Ρ―? •ό” ΐ¨qκšώμ3Ι<ύ΄ySΤ€Θ•1λΑσΏf{9b~ΫES° ωOQ³Ξš? ›snί„•c;„ΔίΆmήάμΜπθ oJ`”μ iWŽΣώ’wάaΪ2ύx€ΊτRΙ:γΊ!KxίησN~¬όOXωΏ+Ίβ_«WoCΞŽPςS*‘ΉOΛΖΖάΞΖό|πΛ/eΰψγ%ςΤS’Γί•Ό]< »ψ1ϊjoώΣώ-ˆ>\Αy:τε\{n?UόѝPKv–-Αό―’ςΠ朧;Jά ½–[ύα&·ϊƒ<'?Œ~(άΛ‰Ο+V,G?Ξ›JςSocNetV-3.7/src/images/person.svg000066400000000000000000000117271524402210300167700ustar00rootroot00000000000000SocNetV-3.7/src/images/petersengraph.png000066400000000000000000000042261524402210300203120ustar00rootroot00000000000000‰PNG  IHDR@=ΥoΑbKGD ½§“ pHYs  šœtIMEΰ +κR‚ŽtEXtCommentCreated with GIMPWώIDAThήε›{pΥΗ?χήΜƒF„%d°hZeFT24VtTR!”&±<L‚€N©Š―Aa” )£Ž(> 6˜ͺQ)AEiRA€…ΖbrOΨsΓΉΛΝΎξnςΝμμέσώύφœσϋύΎgη9|έΥρdθΏi‡ŸšaG?hYΥ γˆικη?BΏLhœ&Ӟ‚–΄ŸΣξ>¨ BWD!ΌέcρwG§# ^ΏΑeηή€κσI ‚ηΓμΏ8—Β΅ Ά¨€ƒo€”Ÿ«π2‘<˜Ώλ¬άρΜy₯@Vd+ΙN4ΛpNγΐ{ω) 7°Έω\ά{›‹€±Ϋ8όΈxζ\zλ©ΐRx3Νιΐw@Ω>Κ5ǎΎΐΫύ θ―YKn;ΠψΨ L΄Q/΄i?‹-ϊPτ›κYƒ‰@g«V―ş0λ› σΎ€<Ρ`³ΟήgΡέΒΏ ”Ω«R αO'‹Ει+PΊΛA$» XέKΰrΉ.‹εφͺ> IiY$\>Š«FΆ9Μ6Ÿ ]₯€ϋ€ΏΘy―#υ=τ/ZƒαοrΎ:|Uΐ(ΰ-`Š—Σ=x(p₯΅a• Μ<,˜Σ(Θxυ8ΜΎՇ€u^?8\ls5\?Ζ<Μ€in΅;ψφΓλβΞ6šΗ€υ(΅Nω½ίƒφk{:Β.cYe‚―dώεψKΖK{{ζΑΰzΚσ–νΐ>m·+Ώt9Ύ’!§P±ε«(oT΄ ΅ zΝξ'k€+•ηρ@žGΡN<½ζh2U΄ Κ›±ε+uV`J2™Ω³ˆK€@όΘ_δέδ‘n—¦+„G=ι)˜w-ω‹4™ˆK€ΜμΩ0%YQ€Ώ)©ατ~±Ρ|yΏ‹gXΖ'δρFXΊ9žΠ=7K%b PLƒ΄ΨT›¦Bš1΅}θ:M)©šΜ§1*@κS‚₯Wφ€ŒΫ" i‘t4ΆK¦&‡‘YΖ{΄ :ώVpά’ξ„φάhVy ί0ΚΥ ¦C†β •‘QlΥΐZθΧnΡdZ,4SW€ŽsένԏΔ‹λί!uθeωφ΅ΥSαh•ͺ7ΰigϋRΔz*Ή3ψ•Μ₯’/ ηχ…Β‚ 1n•fJ%B/ώ δκΥϊΔΓζqJζΐMΰZι˜ύ#ά;ύvln υΤ’.N’αΐκλ2‘Ά=dζ"(™W€F“Up…ςάŒe{9(ύŸ²Οψ”έ?C²D>ωBΒδ {E7.Ώβ±ϊΓό%Ί5πi‘Ή΅ΊZ*v•5ΊΒ΄ΔRκ*K`=AΙ‘έ™€ΧH¨Sά/S–ΐ1Σΰ‘YΦ7Λ0φΛCNΫTΆσ8'(δ+&' ‹Bƒš-&ρG,πA'y1ΐ«ΐ‚B¨|š_†ζ"ktYΰ ,“SΖ Ζ_IaTτ—©ΎζL68νό$Αᘲl5<₯§W\§€ύΡdϊ‡p“Nω Χ£d―6:©δ†Ύ€vXŸΧδύ ΰVάξ}N*F» „QΐΏ₯?a’'˜„v>a}4ϊΖδΛi8ίBΩurΚgΈΤχΖh*owiο+aη–Nx…2Dηr„Έ-šΚ+]‡S₯η§Ξ¬zΉ4BΘ‰°NέΨƒ&KήΠ1.—ξi4(FFH;°xΈ?Bώ½2jŒΥΈπT ƒˆ„dΉ9ξ3 Ϊ^вοΝζt9>8@Θ(1–TΫ`ΰWR "”»$ α+=Η¨1R +αΰ]Z#0&BΩHGισΡN‡`—άo\uώ?œxWω}ΪaŠΡ ο/юΌ’•±cΟ΅7ζ­V YύNp“,ΫfZςι‘6ι@c~ŒƒHd3νfγή ¬…AΨΖ]-2(hQ0Π’E;ϋ€Q‹’Ε,§œL Α(θk£eƒfŽ·ΝzΞΌ―ΙΙ<ξ{‡σξωίsΟωŸ{αοd-°"GW’τΛ"š…^/t’ώ³θΛθ«θj¬23: sj<¬τ+έx8K™fγ\ξkρ§ϊ“ΡιH‹?Hεo‘σ\£Ÿ@/Π`±ο:ΰJuΠ$Β*’=‘*-c2ϋμiήπV-ς· [},B€ϋ“ΡiΓά ύΙθ$0eD\uξBp»α[v_ͺ.6™%ΈΧΥ™z«ή3ΐΧΞTΈfω΄ŒMFZόwUΒΘ"$ lŽψΗzŸG>56UάΞεy9`b΄SΘ‚nI„&š²―nΐI Τͺ F€χ@7 ςζ΄ ]κle0VX8·°(R±βoˆΘ£Έ`Ν*r‰₯dΥ° ₯…Fΰ«*½ ’€l¬£›3M!ί Λ¬r`ΞPϋefΔϋϊœQ]S>ξΖ©Ϋ4©,σ͚΄Βƒ—ύΊ««7£.“oΦ €fϊψρS©>ƒjαΚρΤ‡ ϊ υ―ζbήi d΁Cš=‘°/1πh―l ˆΰAΟ6—yζ-€f θY sAτ`K‰ξ³kΕ?•ƒ£6$$—@6š•sYίΰK<ŒLΥΤ‰Wΐ7ΰ€Υ(ŠΕ‰žsμ,e8φ% e•i ˆ+ΆϊΞaθŽα˜λŠ~ί]+²{³ΰΥnκaΣLδΝ™u¬1%έPρXG{XΓ™Ά€x29εȜςΥ £φG9%œηε@]H <~)ξΕρΤ»άΓ₯žς€Ο€ύI€@‘²^΅l})\ί L2Ϊ ά†βxΆ:υώ’ˆDxγxψΰΊΧ‰η‘w‘°/œV,ϊΟhAτλm1š–±iΛ€el¦5 >wŠuΙt’}CQΉ»VlΚ*^MN6Υ?\mq~(ϊέπKG{rΒ¨8qμΕπ¦Π‰~c:`0Ξ;IENDB`‚SocNetV-3.7/src/images/print.png000066400000000000000000000033041524402210300165730ustar00rootroot00000000000000‰PNG  IHDR szzτgAMAΩά²Ϊ{IDATXΓ­WiLTWv!b•—¨%R Eͺ€I΅VMli°h£ΆΕ²Fύ£,aQH$dΒΠ"BYD@@φ‘}S6 ²©$ bƒ‚ ©_ΟΉ™1£€΄σ’/σήΌ{ΞχέsΞ=χΎyσT.;»θ/##.HΣ«%—.VGFή¬ML¬¨­ͺjΏ/•6A‰+WJۍχ|I¦σηiθZhoG‘όρsL ΅˜Σˆ‚νΫ¬=λ—‰ήP ωε2Η‘ιη‹‚­mXycχ8dΟρ―ΰq[Θξ+‚–FΨΨϋ€ΙΠ:΅`ητœΜ~!,шKK―‘—ͺΠπΤB%3Β -Iχ€ώJ9 "’š §g(Υδj˜GΝ¨(Ώύ-ŠΊ1' Ί€άN Άt††;κΙΜTcu`gĎ³:€Œ6 ­ Hi’ꀄj ¦ΘlJυνγhjκ€――ο+25ψίδ‰D.•– «p©R”1qPσzžŒahh½½½xψπ!Εΰΰ PPP€††xxxLnΪ΄ΙiΞvmcc³+,,L„Ί££χξέΏrΉ\Έ|ω²ψοΥ«WΗΨΨFGG?IήέݍΚΚJΉΆΆ68p`ˆ¨Ύ™u•9r$O&“‰Π©Š`C&βό–••}’œΕ666ŠΠs 0ykk«(Tφ“ΌY{Um «VζOU;aBΎηœ*ΙyΆχοί©ΰΪ`---‚πΙ“'xόψ1=z$Š΄¦¦ΑΑΑ Zθ Ίοg€‚ΒSΫΩΩ‰ΉD΄··‹™sρqJ₯R‹wL¦ ;“²`&δH0nήΌ) Τέέ[·n•έομίΏΏ– P8K?σΜωjΞ•K” ŽWΧLvvΆ(d???899αΨ±cπφφζ°ϋ¨i!&&ΞΞΞHIIŽUEp01Ο’CΝbΣpύϊu±b’““qώόyxΈΊΊ‚z 233αο οͺΓB„±±ρμφνΫtχξ]QdύύύHOO‡———XzL¦Α‘M† yuˆπ21 β4•——‹”ω%ΊΒ+ηWξΨΌyσμφξέ+a’7oήjž);c!"―bžεΉsηD€˜˜Ÿy‹γœ«’³ ηŸ{‘‘‘Ρμ¨KxΙMOO ‚σΜ5Α…διι‰γǏΓΗΗG„—£ ΜΉ*9§’ί]ΈpT[\ωβ3gΞЎi8»€;wJXι»wοζΑΕΗME™σΙΉ~…PΪί·hΟMΞΝΝ ¦¦¦}Dη4Cΐ† τi‰dP?xΛa›K©’s$N:jd }...s η ‹myS£1ΓDηFНqε}\WWΧcΛ–-rvΚEχ±ˆχδ Ϊι`mm=+97-Άα«©© 'Ož%|hρ˜λθ6_‘Μ|͚5Ρfff#ώώώΒ‰Rw=eΨ³³Λ ‘dQ|ή“σζΕέρεΛ—Β&55”ήΑ₯K—Φ’ίTΒYΕ~°θ“ηAΒjžuλΦ•μΪ΅kŠw@vxλΦ-±ς °²²¦’τΓΕ‹Υ΄JŠ©[v‰ΩrΈIΜ4E²oρβΕ•δη*Α•π΅Βο"uΟ%<Π@[[ΫqύϊυmVVV -ΫΆm=Z΅ͺYGG§ΔΔ|ˆ…€¦ζΒΑΑaBOOO¦₯₯U@ΆN(Ξ‰+ώΟY‘+Φhωςεϊϊϊ ,(’ηD‚;αŽΤΚ•+γV―^έ@χY„pΕ7‚‘"₯ 5ς΅€˜‡ρ ",Sœr΄Ÿeί~TJ—¨σΕόΡΒG΄cςΙόIENDB`‚SocNetV-3.7/src/images/print_48px.svg000066400000000000000000000004071524402210300174720ustar00rootroot00000000000000SocNetV-3.7/src/images/qt.png000066400000000000000000000047161524402210300160730ustar00rootroot00000000000000‰PNG  IHDR szzτgAMAΦΨΤOX2tEXtSoftwareAdobe ImageReadyqΙe< `IDATXΓ΅—iŒUΗηΌο]ηΞ>N;Σ)₯₯-LK€@΅U!mQjύ€bEEƒKά>˜ΈΖΈ%γϊΑЉ+$@R(`Ω„ΣvZΊΜzg¦sηΞέη½χέΞ{Žz%-6€OrrΞ‡χ=yžyaŒA³lρΉ[ΎΡusš™ŸςfGuηBίL ΐ cLΓ°›λ›[;boXy©΅΅o½½up»`ΧιJ½¬ͺσzvδoD1δ€c@ (cœ™€b“”\™ξΠοp=ΕΕΛ»ΉΌ;EβtT=Ώci}°aέ6{— [¨Θf‡Υ‘Ε‰0Ÿ=.!Žΰ$0mŒ11ώυρ/₯;­«κΕP*­θΙτΠgwΡΫζ€4hβΧ§βxƒ…ή`΅α³δD~΅ΝΥ‹¦0ώ€:ςτŸœ“BˆπbΣRycLυU d:εf;ΞΪ: „“’ΌΗ γH!±¬–l‘/£ιoΧ" ΗwΥ†{q‘ζ^|Ε6λ[>žΖ©ͺκμQ5œ;e§^PsBˆ»Œ1S―H@kγ0ځK€‰t­#Œ0Dϊ¬U]©€€-iΣbI:ƒm wy/πC%Χo/^ζξ8Sr?ζϋΡ9`κΥDhc0B`0QĈT€AAά’,λJaiΑμlƒJΩiθιI°bem{ψ~Ξ4 φX¬ιu;v$τk}H a$ΠJ‰P’Tάb §…Ι1—‡*Q^Šΐ@drθˆqέ.:ϋβδ]όΐ"26D2’ΧL@ˆ"P!(qΫ’―7Επ³.ϋ“OwO?-Ζ ˆ'‘ΡɏΈ{Βγ¦[Zθl₯XRX2‰”ς?Šg‡₯‘Ρ(½Ήι~k±ͺ•bu—ΧΈβΪ©΄ΐw’MρΔ:jε€PψŒ<άθ–ΊρMW_ΏjυΐϊžΥύλΊ―Ήκ­+O !nΊ€ π‡ ”$3Η "?ΑμTž o;C‘αR^ŠJρδ=ŠΉ‘Νd³%6ίpŒKΆ₯xζξ-²πψGYΙυ=ύζ›«b2&tdπ>οΊωΆ σ“jΰ―OοBτχœ§KΪΈΡZƒŒΗ™ŸŠγΥυ₯WυW(WBΦ\cτΡ:ύς"V―I²uχΛ‡Ζ+3ΜΝτσΨύ³Δν|ψνŸO[ρΚ 0$πŽ;[ŽŸ~ώšώe{Ζ査Iž+@)$^TΓ ŠψΊBa±ΔΒό"Ή…Jι_Υ ϋ|™½ŸI²lE+³s“€{'馊γη8ub’JΉŒ2>‰t†ΠUΤk.Š£BMΰΨ- ΄6¬μΪpΩ¦Υ;6½d,"}™2ΜΜΜ£Eα†f|>ΉΣpιΪ4GF#Ιpθ ­¬{Γ‰Έdf6ΟΞ >£ŸfςΨ8ίϋٝΨΐWΏόm,;†R‚ϊ’Gd4FGH!¬{¬FPw—\!(δs„όγ~ɚ6qθθq>όνIRˎR+₯ωήΫψσ$‰t΅WkΊ6ηωκοo¬š}rbΊxbΦw­ Ϊf|"‹Φ!B΄VβΐΑβ‚bΣ5)d’2ΰ{5 bμ;ΩΕϋΏ^cΛ΅6_ψ₯ΕΐΠ₯…Ž?ΣΑ•7TΉθu›ή’βΪΩΣυ(‹Ά.ϋxΜn‘Ύψš§F'NΡ·\_(] \ „Τ–B쎈έ‡ΣσΔMםAΑ·Ύ4ˆ‹ΝU]άϊ1I₯Ό€T u(”XRΠ!°φφwms₯\#“μddψY&fŽ“Ld˜Κ>vϊΜ3§μ—ΓKΞ¦&iΖΖK M>οΣΡγ§Ο―ΰ#Ϋ³œu8ξυ՜!οΌ­΅Ϊ9~J°jsœρSu¦Žψν7l»-*M‡α·χύˆRε }έϊ…©‡Ÿ2Ζ› Š Ήγϊ"ΓΟ―αΎ½ώψΓEΖOx CW¦xΟgΨyKNΝg¨$ΙN60‘ 8£3-+zb’$“s㌜x‚Žφn Ξ\ΆαWξ3Ζόχdt.ΦcγεJΐeΈρ£έxK#ΐΞ@yΙε™αΎoΆD‡·¦iM-K&νv+)[ynςqB€„%―PNS”bŸŸ”^¦‡fV+”}ΚΓ±˜E,n0\O†Ρ γF€Λ†bΥΜε'’Υ½e­^ΤAθ:…κL©T›[F^"`ŒxΥJV4?ΡZγyš†―‘Z „D{’ωq₯+9½ψwΧ}Μ«FΚ8€ϋγ‰Ϋ£»ξM·Ψ2΅΅tIΗ­ΥΌ°ώcLρ%ΣGƒ{:ϋμΓfΟ+’hZCȏisxΏλϟύό”rΚσj(Ν½Ρέ6π[wάyY%,fϊ θjM"…BLcφ؁§ο\˜ vχΆ^l|Ή9–Š‘ΙO(½0©Τ‘Gυι#A (Σΐ\sςΝ†₯ l^Ϋ·ΥJΕΊ2[Φο$Pžύ Ά-iI΅,Φx=°Ώιγ !Fœ’yΨ‚μh0wdΏ-L…‰μhΰΙ7[΄1`Ό V8°Έ@Π¬WiMδ!“Sc¬kym …D‘ŠšοχΌz`Β­λ3㇂G‡Φ˜ΩχΣΪ @H5sΐb³ ͺ&`DΖ}ΎfDv‘SocNetV-3.7/src/images/random.png000066400000000000000000000145061524402210300167250ustar00rootroot00000000000000‰PNG  IHDR@@ͺiqήbKGD ½§“ pHYs  šœtIMEΰ%0[όtEXtCommentCreated with GIMPWIDATxΪΥ›yœœU•χΏχY«z©ͺξͺκ5[gι$„@„D@e“QGΔ—Ν* ¨3£2.ΈΟ¨18 ,.ŒƒA!Β μHXB謝t'½WΧώ<Ο=σGUWΊI έ!ΜψήΟ§“Tε©Ϋχœσ;Ώ³έ‚₯5pίκ}ήΫόΩΟ9OΟ?²Ίσ[ίnρœs?ωάρKVύuFϋ3λίsϊO_Ήπ’ΦΚsΧ^χΆK½έ‚oώό?3ύ»ίΪϋϊκkbι瞟¬=―M…Bο †Σουφμ™d²₯ˆΖ:΅ί ‡?:oεŠ‡­Ζζό―€τφβ&Ϋ xδQκήuς˜χzξZΩ΄ηΞ;wu- †‡η‹ΦGK [ƒτ0xFk+fλ$”‘ ΖίΌ… •ΒJ&q’·DO\όυΆο|gΗί4vήx#­Ÿώtεuοͺ•Swύϋ-§ϋCCηx{zΫ0Œfα‹sWάω=€Ύίύžψ9gί)`ΣW†s/½΅ΟšYμκ>ΏΨΥύχΕξΙ’5 exΟ#ΚΙώαC8‰zlΛ.+ΐΖq\ΧΑ[υ;ΏώMŒͺ*Dkͺœ§Ψ’%ηMω—/υΌrαEΜωεo6_{ΣΏ½ΚλΛ—Χ ~`†.δgkΟϋ» `‰.Ϋ‚T Le˜?@Ύ@~Α|ŠχΤ€IXυuXα0Ža`₯ΠΏόΕ‡AΩvΕm¬db[¨mΪ§ζύώž?(₯tΗ΅Χ1cΤ9ί²6žq³Wί ΐπK/ΦoΏώ+';w.φ‡†WJε§3 R,@ΎˆQ_‡ ‡lŽ Ώeš(Χ5pω>bšS&#ρzT(„hŒ]»Ν[J{AιΈ¦¦‰©Ν;M7ΟΏgε?š‰―γ3Χ2γί?tΨsΕ•‰ΎgžύnΆ§w±/€–˜‹{]Έ*Œυ™«΅ΟΑr,,e`f3δψ©ί¬DŠ–5±Σh ε?J‚›Φ)'a·ΟΔΘ{Hg'…uΟRάΆ³6"V’~}ΛΛ/jΉςΚη_<λ5οήίΙ[Vΐš³ί_₯»ΊVZΫwΌ·Κ4ρ|@| Ÿ§xέ՘§žŠcY8Že;ΈŽƒc[θΝ[θϋη/αmΩ †ρJ‘Ώz=Ξ’E%—°Λa(Š=Bχ–‘1ΒαBν±‹>=χ?Wά °ϋΆ[hΌμγγϊϋ=]Κ+Φοξλ{ο€Γ"˜€TV–‰lάDfx˜l>O&“%›ΙΙdΘdsθφvͺ?q9ͺΆζ-1΄Šls ™L–L6K.Ÿ'›Λ‘σ|¬³Ο$ώoίΑˆΕΠΉœ;΄ζΟ·Όpκi·οψΖ7›F„OΏςςΑ)ΐ˜4ιΤ=τ# h͐hpΛΛΖΉΏ°~}…Ž2Ω,YΟ'γyd<tj/™μΖΎνv dŠEed²Y}}θΕΗc΅M«πHφ₯—τά½κξΟ9χ$€š9sΩφε―Lά~qμ;ώ΅Ώ£γ³ŽRΤ(EDD ƒˆR8(<PA€ίB0sڊ©ΕΑκލ¬ύ+Σ‹A₯ΤΑ)Dκ Η‡ΟC΅ΆΰΤΦbη X/Ό€ΣŸ#ƒC£žΧ‘HέΠπΓO<ϊM₯”lϊψRfές“ρ+ΰ–9‡=‘ιιY’”ΒͺQD UV‚A…F("‚ΐΆΣD‰€η!ž‡ψE¬dXAoΊXΔp\”eN˜ΕΆ‘κ*pœRΈΥ £ςyΔ @ˆ ³‰l/ΤΪϊTσΗ/»¨ρβ‹·O·±@R₯Β€όPX)"JQ[FCm)έ‘ˆ ―ί&`ΡQ8ŸΌ‚P[Žγ`Αϊυ ό?ΘυΏ'„ύVDPΡφ{OΗ™:εϋΘζ-δ{Œ``D°ρΞΘqǜ7λΆ[Χ(₯φIœφQΐ«w―\°ϊςO¬«_NcE)\ VΤ*EΔ4ˆ``–• GΆA’ψŸ»svϋφvΒaBu1ςη‘‹E‚B{φlͺŸ@α™gπ;wd2g{½=Σ―9ω]/^ρΒσ½ίύυ :Ίz_œY_©|_Bq.dΧά9τΔ’d ƒD?Rx”ςQ ˜Jα’0ͺXΔΨΌί(N™Œο8h4  E  όuΟα­yjoŠ{9‚J #{φ›9Ώ¦ tοC€ικΖ{ξyDhθπ`hθ΄«ήq|ηM―ΎςΚς΅kχΊΐΞ5kh]Ό˜Ÿ}Μk[ΆΜ Ή™άϋΟa(N ‘’yΓZΆw’Mp€¨aQ%’¬Α@E@‹F‰ΰ9™93Αφ<ŒuΟ•’D‘€:©―#Ώd1ώ‰K0¦NΑ …± …ωη΅θ›‚ττ"£ψŒpΘ·§Lώ—£}ψ[κu€ώQΛ€ž X¬ΆfLΗΎδbRΓΓ€R)†‡ΣθΫ™Ίζ)L?¨|Ζj E­2Λ$©°(£D• ͺd/lG¬'J•HΆΜ(­Ωoι|€@.Rζ*­Ηׁl΄Tˆ\B ₯Ώ5BΈ}Φ5c’υ‡―Ύϊˆΐχ KΔ#œP‰TΧ%xρx)Ξ—^-ψ*ΐ7 | ’ \JJωB%ώ"TŠΰ}០+ρˆγΰ47:f5^@ζ»ϊΙ­H.?ξΙ(bΦjΔΪRΆ<ESΘ €΅0¨}ίs,€tχ.jšZΘ .@a’…έ»‘-[H>Ϋ4K(0 2‹ήL5₯Ν}­Ρ(|2¨.£‘8%Κδ™ίΊ•β¬YΈ"ψŽƒ_(ΰεr8KθΒ πΊΊΙόϊύ’`tN #BΓ^xK)Rω@^4i΄h†DMJkRA@](΄Ζ¨ij ί?0WΚ5hMηoW1ιέ§ΠΠΨ€ ae²δ‡Σθ7€¦y­ίΑΗ FƒΒ‡’KαeΛI_±oΞάlίχ±mίχρ, ilxS«€Fγ +k_ /BF4)R#B‹0hςh|Qh-΄ΦΧί_ ƒ[ού}Γ+χόn©φύΚΉ{a`€gŸeκΙ'γVWΣΨcτ=ψ Κ΄x8OJ!QZU• ¦3„Φ<…twγW…ρ]ρ=tΑ#xβ ?Ύψ‘wtώε/Ώρσ…Ic¬θb‘pc#ΉžLΧ·_š@UΉ˜ͺUЍQβ]>ΈPΎžJΧί‡@C(TŠ#–-?ο—μ—}:WΆπ©2Ό3ZΘHIQ"¦YjΞxHΙEbΙ†Ύ\:=΅bΚtww›±}’Œib†ΓΗ-όHΌΥ@F"Πΰ+E2JU₯Ϋ‚‘dHa/cψσˆΠ9¬HΩ§Λ‚λλΌΕrυYΙkj ₯#ΕL₯(nά€ν:6M;,°Φίv;σ/»/“mSπ† u‘δ“ZΐWO)|£TS”xa„°­JαjtΜφ‚i†E“%τ°δό²ΠςΊǏΕΪgαD"Ψα0αΦVό­[ΡΕ"΅uu^ψ…/­ω—]ŠˆX·~Δ4ΏP8tC‘ _@Q C’0΄ΖWBΔ0°QΡe,‘ΛD6T&²Ό…”Φ䐊Πj?₯­ ΠŽMqv;‘ͺ*\Ϋ&d[8–ψ>Υ‘^>ά‘§ͺ-€‡ΎΊ^ϋώ΄RJυΦSS₯…\K 0X,°y33·n'(  Pxάr=QD(dυ(+‹0¬R"duΙ§Υ8:ΊJ4ΕY3±C‘Κ°ΕqΓθž=θB;9ήΪΊ›uΟbτ¬ί Z#υq\ΫΖ±mάPΧuH=ύ …ž^μPΫv^8ι#d !œLN)¦ΣΦxΰ―΄f0™`Η€Il Cο›š¦I,#‘LOΔ‰ΗγΤΆ4γMš„6K©GP†ψΆΐηeίgkΰ3$š‚LΠβϋ)“­ΐΗ W©Ζ6 Χ%½qc©UfύΡdrΐΪϋξΕϊγΗ—š©ν;¦iΟ; +­ΙM›FίΡ *θμΰ°υˆ€Σc Ω,5JQ›H”ηv(„7e2ςΚFT‘€_fώ’@^kόrzύ–Θ0ΞhΗ!o;€:; ²TΉhR†κyη9goψΑ“Opά™gaeϊϊl+Z""lO‰RX³Ϋ‰MžŒ98ΘpΘe›³Χ­Γ.z ϋϋQέέ$Ž;ΧΆKuD8DΊ‘, TbϋHiͺα¨Zώkψe…¨ 8΅±ΊξΏάηΆVτ•ιι±ό|aώˆ…=Ϋ&0ŒŠŸΩΨ±©M&I&$ βυqœ©SI΅΄Œ©γ•i²ηΑ?ΞfI64L$I64΅,”)\JeκHfwΘ—aμ;•R*¨ojz`χζŽc»ώϊ4^6Ϋ¦΄&Ϋά̎£ςΪasι‹ΕφQ‚=jl›d2I</)‘±5i2ͺ΅₯L“TΗf6.Ώ‘jZgL§!Ω€ΩсδreαΛΥ[ωηc‰Φήδ9sξhœ>λ˜O^yτσwά–…Žγ°b1²Γ)ΆΗ’˜Ο―'šJUό[ϋ>ζΠυ΅QlΓΔ±œPˆ\sS)υύ½DθΊt―yŠ'/½œϊ#ζ“νλgpέΊJιμr`ίΖϊ!_Z„ϊΖFΉζ–Ÿ¬”Ν‹$0’Qb“ZI&β$λγ„Z'10s:ώ¨ ―2 λΦQ‹L&ihh ‘L‰Υν7UV†AΊ³“νχ?@οΪ΅θςtydΠ:bύ‰@Κ3‰ΆΟjλλψα'¨Όg οκ:|„ κbu„jͺK¬νΊdt@Π±3•ͺ@{π₯—xθa¦\xn8„[UEψ}uP)ϋΏ?*ןπΔ8im„βυ°iήΆνγΚ`λ››~pυo•ΣxΕcPŠ``ˆSςοD"A2'6­ ©³‰U]ΝΛ7˜Ξ;οΔμξΖά±ƒΒ_Φ"Ήά8‘X†Ύ*E€‰Ψ±Ψ‚Δ|μ†$NM αΩνήA7β}ρϊ{^_VΰϋΊΗ[Ώ¦#,₯Ž‹]Θ“―­έ—`]— 7ύ˜Ϊϋώ€r{φŒ»ZτΛ΅ύ κρJ&OFO›JΨΆKω…λ@*uΐ*"Τ54Ύ:ύΈcφΔΕίωφ^Yͺ χŠΦKηoWQγy465‘L&iL&qόύƒΤt]2έέδ{z&T*λ1$XBΐψςϋΊ₯Ή”W8.λ!Χ±ω€*@¬!y0FxΓ ‡>U?sζ“"%–_{ΥΥΤ*ECK3U=½Hΐ›ΈγΔFή{ύΏΔΚώ¨Fιςϋ ™ΐ©ͺΪ[ΰ„Γψ»vQμκzΣ3`˜&ΣηΝ[ω†ΣαηoΎ9ωόΏ\1πΪk§κ ΐ΄-/aΰε—ΘνήsΠ͐}P\nlŒ”»ΓZΘ‹ίgŸ‡ΣBΘu UUa{ύ=N±·χMPj%† ™ά”ΛΏωυΤi—]6ΈψŽΌβŠžΉgžunlzΫΟ­PˆΐσΩω§?RαKπWε`$Ž“΄Ζ,KΦ·,tOύO‘ΠΣs`ŠΰVU=6cαυΒW°ςŒ³ψΰκ{ΙχυU―xχi—fϋϊθ₯Σoν‚Σ~V~Tγ2%‘<:P³4€$ΘζΖg₯˜ΉΰΘΟ~βίώuΩ¬c φ{G胫οε‘Ο\K(ΟL=ύΔ§žςσhDDλ ¨ήΔ’R†γή"‚ρζJA‘@ΠΥE±«/ŒOxΒUUAn8ύτώ„sIκΤς%ΓΊisε̟lEσQGRL;ΫRŠαh„ΒΜϋ₯}©δ2αA¨šπ… Γ²^ͺŠFwŽϋ–ΨΒ₯K8χ6-\xΊm8Π/U"τ,YLΗi§ρΜIοdΛΤ)ϋLtΉϊ F…ΐ·»₯pΒ‘—/ωκ—»Ζ­€lwgϊW9ςcžŽΧ?`XΦώso­‘–fͺζΞ‘©Žd}œy‡ΡΫάΌOόΚ$ψ† ΠAδχoΆlΗ‘:{α°“NΞLHUMI~‘σ9αΛ_^ί~ϊιΤ͘~“²,φαμhŒx’\"ΗγΤ'“€ηΞΖ5HιύτυΊιn C!¨ͺ>” ΨS‰ύ` »{ό Yg―ψ«ή.'/ϋAτSίύΉ¦ ΎβF£c|W3Ÿ§>%‘HH&hˆΗ ϚE‹Ž-eo(R.₯t΅Ύ0g6κΨcpN\‚5uκ!™KTΥFΆ}ϋ‘Ÿ¨kjšΈΞ½g‹Ώφ•μ‡λώ―F'·~Ϊ‡²#E¬2 ;v΅­R!OΗI$“¨––Q(w€„½ΪqΘ͟‡Ω‚Β­ͺ&ΤΠ€šθEλŠDJ›Ά]lš1γξ³?~ιϋ”RωGξΌ“ ΉΐώΦκσΟΰ‚ΗQλΡ‹–Ί‘Ψžυ …ŸN“}ϊiH6$I&K?‘šZP£jŠ+”g‡¦I‘mV4ZΊ„αΨ„B. Œ7‹”Α8‘ΠPu$ςlϋΒ…ΛΎπ‹ŸΆδωη>ψ‘/|±χ[ηΗ».ΈΰMϋ‡γ^½/―'1w>|φσοyε·w₯˜Ξ―}Σu9nΩχ©]΄ˆώέ{ΞeΩφ΅ΘΏψήHJ£­a]FC"Žno'  ‡ …ΓΈ†bΰρ'(ΌξΚλ…‘Ψ«ͺκ'δ>om}δΪΫn{¨yϊτa€_ϋ*ω—/«:α΅ϊcsΖ/~ΖοΞϋΘ΄ŽŽ·n;S<ŸͺζFζ\z)u'Οφ‡fΛ-·αg³d΅’¬€@“6Mό™3°Z[ ».αp˜P(D‘£ƒΏώχ~£Φ”"Z_OM,φKΛuοŠΖγΎ~ΆŒΥι^jκΖχυΊƒnΕύηιοεΓάΟΣ?\έxχ=·φmΪτ!],bΨ6†c£=νyh`Xk††΄fXk²"₯Υσq“IBKΈͺ ιν₯χ‘GЁ_JzFε†i2}zWUmν׏=λŒί<υ›»‡–­ύKqDπ{—ίΘYW}ϊ Zθ½žΈώzήyΓ ός'έάχκ«KŒΉDΰ—Ώs0$%θ§ΚΣ sv;αι3Ή6ήΦm =ύΜήW €h™VΊ©­νΙ#–,ωζ.[;²ο¦ηΦ1kΑΒC2CxKkυEγŒ;~ΐŠSNΉf`σΦ―‡‡kG,˜―LyuY ΊB‚Κ41£Qτπ0~6‹²,lΧEΖΞH]ύ6§:ό«Οίzλο§/ZτΆ|iς(``γ&κfΟྏ^τOωF1“™-eΈžνgDWξι0rΉQ ¦.TE#O†yΣ΄iί°ϊΎ?μϋη?Ο₯ίύξίFΦ}½ˆ3γξωΐ‡ίσ‹·gz{MšTΕ…œh΄"`Ϊ΅±ΨΦζΆΆ}]]wyΚI]Wέτγέ#ϋ½ψψγΜ;ρΔ·΅^xΫζ»Χ<Ω°jι•χtνάuό° BJΒ5U$'OΎoΪΌyΛ;ώ„'·½Έ‘xΕςε•rυ_Όž}γώΏ]Ο,_^ωχŽ?aεu rI}<Έ|Vϋ«Χ.^ό©£¬ςœξbύ#QH" SocNetV-3.7/src/images/recent_48px.svg000066400000000000000000000005471524402210300176230ustar00rootroot00000000000000SocNetV-3.7/src/images/refresh_48px.svg000066400000000000000000000005431524402210300177750ustar00rootroot00000000000000SocNetV-3.7/src/images/relation_edit_48px.svg000066400000000000000000000003641524402210300211620ustar00rootroot00000000000000SocNetV-3.7/src/images/remove-old.png000066400000000000000000000040611524402210300175110ustar00rootroot00000000000000‰PNG  IHDRΔ΄l;sRGBΞιbKGD ½§“ pHYs νΐ,tIMEΨ#`ŸΠv±IDAT8¦Yψ 6Q 6Q  6Q 6QcdgώtijpU@εϋϋCg ΊυμΰCg§˜πρυξπυαημ›‘Ύšωωϋ©ΊΜφχωϋϊόμοσωϊόΟΦβϋϋόBfΛ±όσιϊχύριώώω VmήάύαέχΖόύφώωςύέΨ*σρυτϊαΣύώωςόΨΡϋβΥύόφλχΜ·ύ5P‹7n2Χ½IENDB`‚SocNetV-3.7/src/images/resize.svg000066400000000000000000000003311524402210300167500ustar00rootroot00000000000000SocNetV-3.7/src/images/rotate_left_48px.svg000066400000000000000000000007421524402210300206500ustar00rootroot00000000000000SocNetV-3.7/src/images/rotate_right_48px.svg000066400000000000000000000007411524402210300210320ustar00rootroot00000000000000SocNetV-3.7/src/images/save.png000066400000000000000000000022431524402210300163760ustar00rootroot00000000000000‰PNG  IHDR szzτgAMAΦΨΤOX2tEXtSoftwareAdobe ImageReadyqΙe<5IDATXΓε—͏TEΕ·κΦ{―ΫnΗω@‰FM4™DH˜ΔŒώL\ρ.M\kXÎčΞ‚htA\ βΔΖ=`PQ`’ ƒΣύ^χ”‹ͺξω`ζ „QVR©Ξ{·λž:χΤ©zκ½η~6ε>·>€]»v½μύςN5λΨ»ο½£;vœ<:ΟΜ—7XœοάS¦Ϊ ςάkΌΈg€‹|ψξ€Ϋpόμί?0x.ύΈΐώ·ΞoΛryš #–ΣŸdόΥ}›k@E°@xp,#Λ²m00–aP|#%"+Ψ‘Υ•sηΞƒ*Έ² ,ŒI Iήw:η·‘Υ‚v+Žm(r²ϊ8Φ γ―ΌIkρϊ欬°FΨ½{ηk놀Z¨VκmQ―΅‚χ ¦„g5‚¨ •,Γ# ΐπ/ή#&|ˆ#Δ!Π―ͺ‹φΨ'aa½%% π4M£:Γ³˜‰lΪc V˜_)FόaΝra-Ρ€: TIO4/ΰ…Δ!‰Γg ’iΨ‰β‡X‹οv‘Ό€Όνίj#νςŸwPEΥ x:λTx›œ3x$Όώςσwhθ6h€ΎρλΖόί±R^‚DM_„ ₯8ηΆΕˆžKΉv³RͺΈPοZΕ0}±Λ@Ε°τ&­WωU.ααΖ2υΜp}Ι„-ιJœΐsεf—+7»¬QW?Χͺd~Ε'’)¬Ü ΅w¦l¨Α© Θ5―›5Y1žώ{ιορ‚ο›X0+WVU!ѐόηSίςλ™,-ήΈ§ϊWj<υμNžya¨3[1|‡χχΏΑΒΒ·nέ’( Dk-ΦZTUΕ9‡ͺb­Ε˜πίεεeς<οχ#ΝωΈς-pVPά1:ΆrO8~œιιiŒ1xο™˜˜`rrςŽYΨ1:Φί†Τ Up6¬’V―χk933ΓΠΠΦZΌχ4›Ν»P«Χpш΄Τˆœ '\ *¨V«dYΦ§Έ7ήis©AK­8Η½#΄ΧŒ1ˆDߏ:Έ«›―5¨ σφ.=Žƒ)mγΥΫ©χεVl­τ‘Ž ΓςοΫΰ‘‘aΤΒ΅+—YœΏΎ6ψΐ`­ ΫΓΐP­ΒμΕKœύξϜž¦6`$^J ν‘'©= o8Θfǁ':ρ*η52(ΊpωκUΞ4ŽΡόϊ‹ΉΩNo೏>ΰ§Σ'W™ιΪ£†Uζ»<_o|wξΩ_‡Ζ_˜šώτγυsl}ς5β·ΪέΧρ?MπKΉθF‰―IENDB`‚SocNetV-3.7/src/images/saveas.png000066400000000000000000000021611524402210300167210ustar00rootroot00000000000000‰PNG  IHDRΔ΄l;bKGD ½§“ pHYs  έ~όtIMEΣ.*Ÿ½'#ώIDATxœΥ“Λk]U‡Ώsξ97›χ£¦ιKiK+ΆZ‘:+‚νΐώ ‚ΰΠgŠ¨Σ‚8с R,­RΕ–ΪUͺΦ›>€Iš›{ο9gŸ½Χ~8HL“Ά Ί&k³ψρ±Φoνq±π>Υd<ύΐ Όωjsͺ}΅=uι£Ή³Η8ΙܝΪδ~ΐq7»Ίv†ύΥ‘λΡΰήWΆ5λΣogϋNπΝ;N’ցΏ>~ξH\‰'¬x$¦ΩΚΡΖβ¬#M*Xk1Ζb­γ׎“§ϋΧΈφ)Qϋ] vήΚ‹‡? /wΒλΑi5}wΗΞΝΔ•˜ΝcƒyοsŒ±<0ΪΗΔk/­ ƒd˜_^'φ1”@Σ³xu–ƒ‡Ο7¦7ΦM§΅P”†RNŸƒΩω&ͺ4kd[?JudZέ ύψv/ΏΫ1χΰ½;t—ΗF,Fa₯02ΨΓξνyhλθͺΘη7 #  ; μ’ΉPεχ§hΆ ŒΘ]ϋHŒ±±DΡrαΰsϋΨ:>Μ†αή•fΎqŠΈ£Ωψ .λΓnzœωλc4› ¬uχ F,Ξ{­‚Γ/>Aσ‹mpŠώφWT{`±Ίƒv^εrm/KΉC忁-"ŽVVrξ§:Φ:¬uˆυXηυxvΟ4¨A €ΤΈ8-|©-‹y›RkœϋŽUiλqΛΩ:‚δ<6v†€o\BΡpœΊΉ‘‘ΓƒRkΑϋ`ω6\bΔaΔα} ΦUŦ˝jmιγG6m `zΆ‡ΏΝΠμΫMKΉ™hmpΞϋR  ¨&ΡZ0"τφtςΒώGpΞS–•5ιlž¦64 m‘FήpΘ–η9τδ3q|όΙ1ŠB!b*ΐΘ'XVΎΫ?ήζΉ"ΟK€\’#ͺE@7ψ!n5"ΪιTih΅2ςΌ@κήk-ˆ8ŠBS―/07ΧFΔR± †ΗΗ@†!ͺβlΜΟΧS=1ͺœ’³3A©c ήω;Ή>1b)΅ΑCΊ»DIΦΒϋνPέ…“κ“ί2Ϋ±"OšΖ„ΰ#ˆΌ_;–^'Z ν¬ Z‰Ι²’f³@Δ±4O64@TD\>SημΜƒ,%½ΨLa­#McŒŒΌχh­@=L†D)M‘+|š bqΞα½#Λ ίwžξΚ$ΆPέ›πbρήαœ#ŠΞYΌ³‚‡Ι|­±R₯ ei°Φ‚Η{7–rϊ WΜ¦w3!„Υ‘“€B΅šβΌG¬[kΕνε©’$[ja’˜‰T*“JDLŒmJ›d ‹ΰ½‚Β²βΡ9HΝ—Γ₯―oΰsλLJ₯υsΘ%-+_‡Φτ₯B5z*@¬Z4ΜΞΫF@rJ‘t*`œΔDp±ΰ‚ΔΆέz –Œ:υΒ¦-‡£υΪI ηœw­ζP”ω˜ΞΑ}Ÿlz7|»:Z›Οϋ>οεƒσ΄Κββi«οΉgΏ{ζΜRH§ΝZ·nά711" Η CV?ώψR€¦¦&υ_}ησQτΡ£7ΗΆΆ †aGΪ6ŽγΗSκ6n4ώ—ˆˆͺ™>έ·d}ZšΜ™3gœRj~νθs+kk5»w?ͺ†ΐΰ”ξΟΛU}…!$1::8p– š€p₯Ί#QX8Χ-"1½ ΐ „β˜@p°œN§«¨δΣλ?*n_΄πΩ—~9Φ‘zE„=˜,  H1 γcΛκœ2x`iώΨτ°+“Νυο-sφE"ƒ€Z¨a[ͺDZ\rπ“h©\½tqo¦ΐϊiΖΧ€α”-]όd³oKZβD~Šξ*-CD$’7S @pBƒΌȈ+[Σ†T 6œ»£ΡbNΎi―ο…yEK––^pu¨Αμͺ@ ΓΨdΏςβΔνr4Ξol‡΄ΖvΘαX‘Ÿ]'·V§΅Ÿ‹¬ ΰ.*R·ΘX‡·₯:@πŒmΫν€γ·6»ͺ¨€ί‰„xן"‚χ§γQ±1‘΄RSSZφμΩ»ΘΧ μΏύΓ€n–=Y.°Nƒ`9π;pΥιθ‘”8ŒΌαιezH©PΙyψΧΘΓ"‘ΐ(₯”+{ŒΔ—OάΫS§6Ώ9a‚T—”< ΈτΊt“™Πr zΨYXX¨Β”RbšfzVV–Γ4M'0Fϋ[0μ²ΛNƒX5mΪϋ ]uΙΙς~CΓΰN %¨ Ίι< \«ƒζΒn (θ£›Ψ¬ΏUiΓ΅ΒύΌ^’.Ώό" R Ξo–ξύ΄όϊyXμgΆΆ‘;»§-11ρσ11©εζήςŒ­kΛΛ'°‘¬,£nμX©KN–³g/Ά…˜fv Hςφ1P Ÿ«tIϊ#D@&΄mRGŒ0粂‚9+œN)ί’€$i8r$έΌ5/οιwΜηeš`e—eUΟ ΞPι@60E3ξ zοήpΰ;WLŒcΗcΡή~—³³S)e~=ίνΨ‘ΕΧ--κvΝπŽΜ̐€ Ν₯ΊKIέxύu·ιΔΪΤVΛ€oΉή{ίziδHY²8+«ν\ θ=n[G_5*βΥr ©xύΪo_)~βk`‚R*8μαZΈΛMΐMkͺgΖ%ΫvξœΠΰρ8Ξ V Ο·«aΣψo7‘†΅ƒ₯¬rŸΏs€—/ϊE#0(Ύ tzw^žΓο{ώ8Ξw„½±dŠΘYϋβ€φέ_κ Γ°υ’RC« \Ϊ.z}ΖΑϊ‹m9”`ΛA—l[3ZζΝ›? ˆ ޜt:ΜAF‘4 «έ«ΜͺM:,zΝƒ…ŠXΆBcΨΛΐ°Λ€©@|@ΚΊ_€  œΌ`φn¬Όν@ύ‘γ»Δo‰7ή’Ÿ¬/6\jΗ'¦ψΕV \Πόω­j.h˜|Ύhlά\αΩΧaΥΦυ7j?θ―j?θ―ΦΧτW‡Ό!<=cά;@†aiΐv`½φ)Δ=I1MSMνg΄vK\΅›ˆ3ΝΣcT`ϋΐuqΫ‘Œ΄Ώθγϋ€€D τδδόCΝΝIΙ£GοinlTη*·³šΫνVJύσӜœ ς%@Ž‘”Ϋ‰XŸοŽ.+ί6|8‘³f₯6ο›Ήu«Υ—w˜Sεζρzoή%Ά€΅2?w€Ω³Uo¬εg².Γ —λ€Φ½Xΰ8ΩΡρΐ‰ΞΞΎΏKn©―7Φ̝ϋΤζΜL«&;ϋ3ΟαΓργ{ΈGώИ3: IENDB`‚SocNetV-3.7/src/images/science_48px.svg000066400000000000000000000002671524402210300177530ustar00rootroot00000000000000SocNetV-3.7/src/images/search_48px.svg000066400000000000000000000004751524402210300176100ustar00rootroot00000000000000SocNetV-3.7/src/images/select_all_48px.svg000066400000000000000000000006711524402210300204500ustar00rootroot00000000000000SocNetV-3.7/src/images/select_none_48px.svg000066400000000000000000000005161524402210300206350ustar00rootroot00000000000000SocNetV-3.7/src/images/settings_48px.svg000066400000000000000000000014231524402210300201750ustar00rootroot00000000000000SocNetV-3.7/src/images/similarity.png000066400000000000000000000036221524402210300176300ustar00rootroot00000000000000‰PNG  IHDR szzτbKGD ½§“ pHYs  šœtIMEΰ *‘0ϋKtEXtCommentCreated with GIMPWϊIDATXΓν—mLTιΗ30γΌάΩafΈΓ0Έ8Π qG·ΛξšΨXΣv n’[5HcΐbLlL ΫΪ4’˜&j6’»5iFtkΤ’]ˆ₯q]e+/Bλ Ε"‚ε%‘—./2ΜLΏšYΖνφƒϊaO2Ι=ΏœηΉηž9Ϟ _šΨΩ³g΅uuuρ­­­ξR@ee₯ώϊυ놆5??_ PUUenjjςΤΦΦΎ°aΓ ΐωσηm­­­)Ǐ_ΆΈί… œ·nέJή΄iS @uuuΜΥ«W“ΝbœvρΒαp8,ΛL&ΣΗiii―ΨνφWEi0›Ν'Φ]λ°Ωl?PεŽΕby'77W‘΅ΗEιp:Ή'OžΤ:UUk,KΛϊυλWLOO{G³’(ζηη;£PUΥΰp8V9“Ηγω @||ΌΣαp€ΪνφD―ΧλΆ">>>Ξf³₯gffš\.ΧJUU ͺͺfϊ|> `IIIIWUΥθυz3fggΙΙΙΙρρρqλΦ­[Ύxί˜Ε‹΄΄΄Ή™™™φΉΉΉ»»wο>711ΘΘΘ ƒςδΙ圜œ δσωz‚Α`OOOΟΉ­[·>X³fMG xPSSs’¨¨h˜]½zυύΉΉΉφœœœχPIIΙθ½{χNMM]ΟΛΛϋθ {bσζ͚₯lΫΆmQlη͚ηΥ‡z`π`fΎ+|±ΑRο+#*ψ °ˆl° ΐΐ$Ύ ΨlbŸ•€θώΌ-,|ΒήvpϋD؏$)30&μ;I†nI–d²τCΐ΄°iΰžπya£@―Δ…„υIΐΈά P€9ρηϋΐCΉW”ΕιΐΧ#JkΎdFΔفl)χ’₯HΙ ,Cβ"6 xωjˆC‡E5Χ‘#G΄KYyyωσiΒκκj₯ΎΎώΏίΛςςςEἍG›šš~\ZZP[[»‘­­ν·—.]zkοή½z€ΊΊΊ]~ΏΔ©S§|‡֚kΧ•ϊύώγΕΕΕ §OŸVoήΌy¬­­­TUUc”y<Gbbβ» Είp»έ―8ΞŸ9Ξ}YYY™ΒvΈ\=N§³hγƍv€εΛ—<))©ΘνvoΝΞΞΦΦ+VNJJΪ—••υM€@ πΥΤΤΤβΔΔΔ}ϋχο_Υ„£££Š’ό* Ή 066Φ‡Οh΅ΪΎn€Η_Πh4‰ηϋϊϊ&eνο‚ΑΰΖρρρKσσσ!`zxxψ=“Ι”<22 Υjϋ=ztN«Υ>ξθθx₯„ σz½ΎΕ`0|Ό}ϋφ^½^Δf³έ˜™™ΉYXX8&Ίίk³Ωvuuύ₯€€dVͺwGQ”˝•••a ”žžή¬Χλ―μΪ΅λΣΞΞΞ xς…=a±X’šΛj΅F±„„„禄ΰ QŽ ΐk@3π>,¬θ~"‚πkъ7₯―΄@Π!:©ΐm &Bΐ>c/A`ψ‘°",]ΐzaΒ.ˆψ κ•Œl⇁‰ω†ψƒ’5ŸνQ¬»ΐ‘Ω§ΐc`Έ\–ŒHUξΛ¦χ`BΦv~ΰ>\ώψyƒvIRρ΅Ο`š²ξσXΜRΆTΩtf³Ωα‡σ²eΛ”Θt:] ‹άΗd2Ω—$₯W%nIβ“Ι€<σΡ―\ΉβjkkλώgEEE@}}ύ·;;;§»»»?9vμΨJ€†††_τχχ‡›››ί;zτh@KKΛGΓΓΓαͺͺͺ]Z@χξ݁‘‘‘§eeeoœ9sfΥƒB}}}ΛΛΛK‰ͺ€ΝfΣ™ΝfWlllΨνv»„YŒF£I§Σ™RSSmrƒƒΑ––f8Υh4b΅Z=^―Wm6›έh4\.—Gtζ…ΈΈ8ΡhŒυω| QM822ςιΤΤΤΕρρρΖ-[Άœ˜œœμ Χϊϋϋ΄cǎ&€p8ά:;;{«½½ύχ{φμ0™Lυccc-ΘΞΞΉ}ϋφ΅ήήή‹ΕΕΕ7o>b–Ό―WΙδ3$CƒΌ¨2ˆ„€Dΰ Ήž”΅ιΐZ9Άsp//ΙΉΚlρΊL^"—˜GΞκ<π}aoKΰ=©Ž ;'‰!Š~*e’Γ2ΦΌ*ώ +Q#YθαtJΨ€ΚύˆΡj0βΦ%κ7qδό2!-{ τΛ^‘gύΡθ—–θDͺTgΡ–IŒ9ςέυŒQKdχ_xΏόŽ΄ΚΡ]ϊa2IENDB`‚SocNetV-3.7/src/images/size_select_24px.svg000066400000000000000000000005571524402210300206470ustar00rootroot00000000000000SocNetV-3.7/src/images/sociomatrix_48px.svg000066400000000000000000000051031524402210300206750ustar00rootroot00000000000000SocNetV-3.7/src/images/socnetv-logo.png000066400000000000000000000065741524402210300200720ustar00rootroot00000000000000‰PNG  IHDRP$’½zbKGD ½§“ pHYs  šœtIMEΰ(|–’―tEXtCommentCreated with GIMPW δIDAThήνšypTuΆΗ?χφšt:+d…†ΘvY•EF@˜Η&Ο‚§γ” lΕ˜7ΎF±œAδ‘(:β6ŠDYΩIΨΑl}λ€»Σ[z»χύΑ/ΦΑͺWυͺ §ͺλώ~χώ~§ΊΏχ{ΞχόNχνΟ^I»Α=Ϋ’τkΧ…ιžHs‘ΊkΩΈHΏdρ›Ι„·q3Ϋ"½Ξΐ†gκ8Υ‰₯ύ…h·iγζ/ϊθΒ€ι-@Ήi°²νΏ^|ΪK°@Λωj=ωωηo~.Ήqώάƒ-Εε©b0 y~'Rηu"ζ6Λ"]#˜² šQ’žΑ@«¦‡)aΧA—‡1―M"λϊ)-Δ€QȟG1ζ€žάνalXM'ΐDIΐ Πˆ†½€Δ3iΔξ4ςιaΉ_Fςr‹Κ“ 1C ŽςΣë秏ΰ; V|j Zl3q@ P‘d§WD€©¦Dj1Λ~νjšΗ­FΏAΠ%σ]ǎo`ke€d 8#Œ. €y‚•m\!.τ@γ—‰«ˆδ₯mn.΅(EY—„¦ŸŽnρ2Jι@ΠF4H¬Σ 6Žˆ5C€Ψh £;iΎyθ5κ>ΎԁzΑ–3ΧηΙ"Χω€S€ Πݚ‰‰¨‚€(T¨U¬?bΚ^€Q  ˆ<™*B6 ¨Ξ‰π7 ‘1 _V‘M-%j57Ν#€NB(B”Φ@ Θκ'P"@ν&DΔ/Βϊ0(›/N‡#Ά_)€“Ίvɍ‘m3ΰ,κ(B5(€ΚbŸ$€τ‰λ !.>‘#hΠ$Τ7ισΣ(ΔΑVο―,‹§§ΩΟΈύ! 7:Ή ”γ@Q²œα¬ή|ψa.Ψ›$$‰£ τžFšY’€^'};»Zύ?ƒ―Λ θ0»6ξή7ύQ…Χ₯»ΘJ»?©XVά{‹@ΫΖΝ†7½c δoŒαqlΨD8z…W4Ψ¬ω Κ"W&U@(“υΈ¦θΧΑ†ΊΖHΥ μμυŸΑ„ϊ*_ͺΩ€κ_›kr6TU—=52±ώ—xqm2ΙΣ­I›kŽ/)«·―˜žTRΫ€šό…θΒ£ΒΓΒ΅™²€χδψΌΎ£―σΊΌΩΟ[?™Ÿ‘½Ξ Λ γοjLS¦F&:έAyίVp^\kQςRž­“τF)Υn,8πzχŒ+“U(φh Ήdͺ’`D5cX¨σT’$Ɗ{EΨJ"τάA’ό .LΠ'SavSπjχšΌ‹‹…Ω¨Ο˞|πΘΥω;Njί€ΈφwUΊ˜'ξψΜm-M;WβŸbΧ·ώώ#€¦βΓψ¬εζ «nͺ·Ύδ―ώ‚o{όνφΟΟΠ²SUεηΆx2{Μ) ȍ–²ΘͺΚ†)ή:ΦͺΥ;η)₯€rΘγ–ύ žυ–ςΎ’χ&ΑΙΦr~΄ωύ^=Kq+8θ.Β³Ώ 8΅€‰@…’.BHd{ˆψΝΆΧ†σT½†y½+Ξ'Υ^τWθg;ϊΒξςο§½·gׁoΞ^lΊ!Ϊ»΄ΦBυίά@ǎž§χεxkrσGd ΊZ?,ΐzπ]›βͺΨtW₯ͺE‹,’bρέΙΗ(@‹4Hυ7Ύ $p¦WeA‘υ€ΙhΘ2Ιιλ ύ$ERœ%Žβ‚ %Y ΈBΪδ ‘@†4εZή³ F5q{°λ¦ΨΔΖΝX!ΔΐVθ#‚οZ=ψNνE?“Ά&k\πύγυ'€’s_Ο<―Ο\7 Ωι *¨?R Ω·WVχύ·Ρ²^φο?ώΕSϋ%I τ[θgΉ\ΩΏs¦’2ΈMεϊwŽeΧμYdχ¬—όsϋΨIOŒ5cε’μΥΝ VΏc7F-( Ω+€"U Τ­ ©ϊΥO6LςΪ,™]·6tJ ξήτtκήήKœmc’δ .gdϋΘaϋώάθRΏΝ«σώώŸ¨‡’Π₯αjΌΞm­UK‹’eU.Κς–Ÿ: Ά£€Unώ&”k±O„q@”*W…*χ'¨ϋ’Αδb! Aqίδ‹\ ΐσρ’ύΠiΗΩΔΜ'ϊν>|ΫκN©φšύWfύSVά‡ω,:§Ε=΅Ύθτ’κγ …ΆΫ»€Η,—Z?7Π,ΞL»|βκRΗιΪς3›kvmϋιao}ν €€ΡκeOΕρΌCϋ―HΚθ>ΎΟ΄/ζ’ͺ1©‚Ψ ΐ¦…Κ_ιŽžs―ͺ₯ΗΏΆFΗ΅zΛΠyιο4Άέ^&Pν±—« «ΌΑΒΥ€Ύ¬ΑύEJ§¬dΉcΠκ F·ΑYΣ8Q)Z³WΤΓΘ7±YΚE›ͺΰ<œ€Υ'€ίˆŽLP”5+}Z5«o°m›ΧΎυι杭Θισμrχ{{O·?»κσΚe σςZ…;~_›Β¦ΤnΫ²cέΜU©νbJ”„™™ύg΄·W+/:‹φΈ}•ΫΆδν_φξΉ#Χ»+¨L HƒQ.Ψ:ώ“Ή5z?2j‘ρωcu ’ϊNU;Ζ†»ΖDš*λΣβΎόΈ|ΗμUΥυa΅rx֜ς-υ‘ag%ό.₯vΧN₯nΧNΐspύ—ίΖ$F–Guš8A’δhΏΫΫέ[y!΅?‰ί{ێtH„°Ώ™ΊJ’ΐ–€έΜΆ’ˆΎ*όt ‡Έ&ηΧΟΨηUfu“pδΎdqω²ωΩΥπ˜Ύ%₯‹'όvτΰ…$Ύμ¬-±lΣjδ ΕeΝ/1Tξ%Οα λŸ”Π9觍Εϋ„Κ{―Ό}ο ΰ …BH‚9_½Ά$Μ΄²ηŒzcγΖ-σ’3»Π©+΄u:B 6ίΐvjάΡχCuΎμχs’n‰’VD(β»»Θ}Ϊγ ύϋŽN½†>Su¬K¦«.09`ωπˆ ΘΟGΉ2ΞjiΙ€%E!:0ν€Gΰΐ.Xιγsβϊ[ΡΌ’‹φρx5pV7‰ͺκϋΟ.Ϋφ7ΛγΗ‰½•»Φ)ε›&_© ζ£78ϊύζ(―Fη4ΖΖ ^r1:μv ._ΡΙfΉ}”$γVο υ}ώ3€ͺ6%gTm¨Ώthϋs₯u¦¨ω &ΠH ±ΰU$_SύξΟ‘+\–"½<ΫΰψpiDbηͺ` ()ͺz ›rKmŸvΘH6φ~ϊνɊς8Ύ;#φ3€k―\›|ΝΨΤrψC/og:π“@;(Τ8U„©Iάz‘Τ₯ΐε3Γ7ΉΨΌΌ–5Η°ΆωΩδD›άΞ<-!‘‰ΪΔUΏΎΗ珇\&œΗ¬Ηvώΰ()3|ί±@€GU5»·»Ί»ͺŽ–Υ_ΝΆZχd ΧQ£ΣF‡ͺ?rPyαxΓ*@λdΙhŽ“dS² `?9χrΞ±œη λa-PcγΧΛmηΌώ˜sΗQsϊΨK(φCξφ=κ‹™χ˜ίsΙSQ£u†0΅UŸ™ι©Σ―N ΝΆ…N8Uεβ¬*χzΦ“—](ލΑ[ΞΒ]‡‘YΞΘX£‘1 ³so€b±8U„hHδ>3Π^”7±β|xΜLτ/SO|ΤδλJ{¨†³<€Lθ0Ά‹ΪhXYμωοϊ+_WQΉζ+WMΑa{ oIАςhTΦ‹Γ_[οxvu›!TςζW(ξE9Άƒš€c’³ŒΜ?bβ,ΛνΈ8';jΨΦ>v«qš’mί= ιθP=₯—πω½ήK­31άη²wTͺ7Kι_,Nof©5A›ςx?)eξX[ή™ΞκεΙ;·ϊx3ι}Hέ]ώ¬ΪτόNΕ΅ξ¦ BΊα*ιnZέZFίΞΏ|‡Λυϋσ€{<\ΞΊqΪυ6q°9…Φϋ ψs$ԝlΊ­Ÿ%7]οΫΟΆ&‰Φ»δΠγΪnbε}DnOαΫΪ ΤŒ΅ΨΜ¬FžΎΝίft»β½ΩΔ[omlu–ϋvίξΫ}kΙφΏo\PoIENDB`‚SocNetV-3.7/src/images/socnetv.icns000066400000000000000000005523611524402210300173040ustar00rootroot00000000000000icnsΤρTOC ic08₯Œic09ωΝic075pic08₯Œ‰PNG  IHDR\r¨f pHYs  šœ@IDATxμœ•ΕυΏg+UAPŠ ]DŠ]±Š-Ά$5‰-‰1»ΖDΖhbb7E£Fύ©IΜί5vΤXΨ±"‚ JS²,lωŸwοw^οφ½Έ˜{ψ<;σN9sζΜ™yί{χξ%„Όδ=χ@ήyδ=χ@ήyδ=χ@ήyδ=χ@ήyδ=χ@ήyδ=χ@ήyδ=χΐWΜ_±ωδ§Σ ί%V‡’ςPυΞCaeάeΔΞ‘CAQ¨¬aε›Bu\—ΟυaτΌ°’ίβPΨeE(¨( aA»Pύ^—PωTΠξΕuC( w­UŽ˜ςHXά¨ςΦ8δ€5nɚfππq‘ψυ ‘bδΈπ‚6ϊζΗ½*·4-ΠΚ―Π¦/CΎξώ‰TιΊRΉΞϊΑ£αΓ½Cωe£B»uΚΓM/M GŒ―ƒδΡόΛ‚o}5~ζ€―Ζ:fΕΖγBi(›,, ϟφJ¨Ψwv(^˜ΩτY;€ υ τapΡΖ‘lBοPύΖγ‘ΣΘ]BΑkη”«ΦΨΛό°Ζ.]ύ†k£(•‡Χo}Lwz½Š/nζ[zt{Ίg¨ΎjD(xσ1)yωΚx Ώ˜_™₯όβD†μͺ―ŸB{έΕυ‹€R‘ςŸ^!\;,¬¬¨νήy"Π"‡Ά‘ΞEmĎΌ­θΑ»…’ ϊ…₯§ΌJϊ,­yίRυz5ΠυΊ‘rI»Πaތ ηŠΌ¬ι`]σςσ@»Κ0@κΕ›~:›ίξα%Δ•“CiYI8ΣeωtΝφ@ώ `Ν^Ώ¬Φ―70|rωΣ‘¨“>ΝΣΪR¦[†~eXΉh³Πgώ{αΎΦ֟׷z=X½ώΞωh£Ζ…φόZ―wyΘΙ‹tήK8ϊνP€Ο•σΙδΘΉς@Ξ]Όz¨(C{—…ͺ%9|Ά+Σ”ΚŠBα6;…’Υ;»όh­νόΠΪύυ ί3θ·}‡ξ7#Ι•,Χα²ΦŠPΉ΄>g—5Ϊω`^ΎU_0SΏ Œς™~ν·jU«^‘{πβP΅Ό:¬ΥͺŠσΚV»r'«}2γTκ3ύzα_Αομs-UCoδΰmΖ\[žΧ{ ΔήXsσΙ–_:?Ω”/ΎΩ5δτΟχ΄ρΓ;]BΡZՁ·ς²{ ¬Α‹—1έχϋ‚²Eϊ»žαŽ{ϋ‡jξΠΉ’ϊdaΉήΤη VδjŒΌήΥγό°zόœ«QΌΝYΗB½ώ/XφIxη“v‘pνΎ X Ρ΄ω«&OZυΛDr5ΙΌήάy δΞ·ΉΦΜζ―aauu(όPξΛ{Σ;ΦTΆΆθώγ°P‘[o3ΊmGk•Χ·<ΰΰY Cε‡hE°ιbρoύ UQ’§€>§mVΊ0nΨ|7=Y<Φ'/=œSX”|m€mqΪ!ςύW£ςΐjtv+ ΕF3¬ŸΧύΞ‡ >˜–•T†yχτ Υ|ιGk oώΎcX^²"7sN(¬{Ϋβ|R˜±fx Γc͘πneΌΩΌωΊ Ί"TΏ61 ΌeΓP0·“Ύέ“š ›ΆΑ‘J_.2띧Γ5ΊδΐρΨ€Ε©ŠςΦ=ΐΒεeΝπ@ν—ΉήxXNΉ―“¨χŽσίœΊmΣΦIώ€')lκTρW€w φ UϊV !U+“OzLox_3„Λš:\Ύύjφ@.^&ζ)όO ηΝΟdΙ{έ›Ξν’TΏ\ΎψƒπμΤmΓ!Υ•aεΞϊ.ΐEκΙχ6$4α+Α:θ5Ή›…κφ ―½<1τΡλώ΅3Œ΅0Β΅Ηw>©Θh» €ςΆ=ΰ †•ρ†σήxqۊε‹ΓΧ–/ zύ‘Pp€0qχρ!ό·{¨,Υφμ(x€—Ό³―?"J6<ωͺΣ―ΓύBωώ»‡0yQ/Nob€^χ{s«Eν>Ά…f\#΄ΙKφ@~ΪπβΘ4―ŠΌSςΩPq†θ$§@’<1 Ω* ©( ··οF Άό$TΤgϊ»–λkΑ₯m~ΗP=­s(š+ΎΫ9„u–„Λ_~1œ_°<쩍‹τΌ Άzg!π§œ―έ¦¦Gώg›ςA”—Άηx]Όι}Wυ΅¬w{κxožΏΥΏ\,z ω£ͺⰎ~?SΧΛΆά!|W§Βφ Ϊ‡ήΛ‹Cu\Ωie˜³Φς0ϋ©α¬%‹Βi]{†[/+υί±αG ώ³ΧΕΑŝ<›!­ωAM>$nΙΘ{ axc³™Ήsσ7χόΩ-΄„>ζ“lJ6&θ~ ΏΏlΈ·ΒF₯žτhQ©OςmΠc@θΠwhhί₯_’WΕ‘ΈKοΠΉΛΊ‘«ŸΥ5:8<ΦΞ°žΝεϊoEA―mΐ&lk'°»±|h)›—Ός¨ΛΩ6?‰Moώl@w΅αΣylΠΙραΰM:MeΤ#Ε:Š€|r] q ’MΜυr±Ώΰΰπ!ΐA€ξψ΄£ύΨδC[±9><7η%π‰οόήόΎλ{“ΕŽ<σ/‚y»@(οΠcuΝΊ―`γϊΞμρ(c<6/O τqΓeԝ²Ψ&μΝvψI€Ήζ%2¨k󳉛߇εlΊβ‘o W ΏiC‘μYα λ͟m\?ml€φΌ™8@Δ‰u«Έφθ™icΫH±]ΰ—ρ?䘼δ=mΦuηχcs±)υ_u„{oΈύFψqάŸ”>Ϋ 6?O ά齕­ύmeΤ±YΩ΄Ψ@ώδ7­Ο‡’ͺΒA;ή`|ۘ>ΠιΓGEyΙ{ΰΣl~6ž7„ θ;Ώ7š7²7iρ”`σ+8(7+yt\+xMΔ°¦δσ_1R‡ τ/xΩΐAc½ΆΑψͺ ―μ#|ΨfτΈ­ψΚ? ΘA_–¬ΡΞ>6荬κδ.¦φ^_Š©/Εν°Z# υeωΆqcŸ“α @H]žθ (ο-ξ£oΤ=)Ψΰι>*J^ŸΟV:Qμ#hg]€ˆϋ‘ϊ@"Ε‡ϊ€ΒKbΧΜuΊ/oς”pΨWžΙΗ_χ‘}ϊZE΅b›j ΪBfψ.‘H`Uπς€/~ ΪγBΡr}Γ돱-Ψ^Ÿ œΔkœŒ:Ό:1”ž₯ϊΊWrΧΩ@QΓaΛ”ΞUx°ΣϋΦχΧ“y.Tκ#¬™Ώ^[έσš †87Ί7©λZνΧlώbgρ†ˆ7Ώϋ¨8y§ώH₯lΖ― 6j,qΫΈάyžF.' }Υhς”‘€V° ›h·ŸΈ^ά(~!xΉΑӃېcΖ‡€―U\[OώK•"=«<n:L}*,Σ ŒΤ…°Ηπ]Γ¦Ÿ”†‘ΊΡtЍgΕZazqyx«]A˜ ƒ96΄Χb±k„4mjοJήx,¬½S8uFηπλže‘cΏ%z^¦ηΞ•‘ΊΌPύ¦Ξ™zž₯Π΅W„kqκiཡ{†’₯ τt Ώ”[Ν“Š}η½ω1ΗyκΙ#δm+)l ύΕ†b`“!±iΟ£χΡXœΥU3ψL™ +oJΦ)ί~ybΈ΅FcΫύY;ιΆjβΘq‘΄ύaε’ΩaώφsB‡“^ ψώ‹ͺ†ŒΧAθ£Ί[Α%›„.ΛΓ·?ξŸυNν·Ω’KΨT§ή`ŒIžrC™Ε›„ΗύI‚Gm~ηŽΝΤΉ)zΨhcΔΓ‚»6/ψ5!‡›έύH§ŠΝ…οRθ@ΠƒΨFήΰΏβ$APΣ?*Nϊι:μ)ώO<+Ζ μΆΈ―ηηk§΄#o‰σ.kiκω’‡|2ΖZCρΰΝΒ Ÿu^ώL¨κΏ$ςGS|ΣJc„?¦ͺTϋΆOž{yBςFjcΊ~)mb'|)Τ7¨ξόΕ…•‘΄Ό$,ύΩK‘b«y‘˜;}S¦O©BνΘ±κ[N™5#\ΏΰΓ°’Ίj• «Ο”–ΤΩ\l(\‰Ϋ9ά؜;ϋ =d&―Γ©u‘ƒk6Τ¦βΥLϊˆCw|λ%εΑ‰‡₯X:©{Il–I+ε· DŒ…Δ›Ψs Ό½ΨFπˆrqˆρiEΖΆq_—Η©šΦŠϋΤ41ƒmΦΫ‰ώC• »~IΏ!α'ϊ^΅³ώ€N<™%žecξϊqϊζaΡλ„zs΅II;’Ν9bΧP8υ±P5h·PύΫηC΅^ƒ°‰½‚M5”―Ιξ¨ώίΪE·Βςpΰ‡ο…‰ g'OV‰/œwΪΤahoŸZŸ7Έλ|M}Ϊxl6Ύβο‚Χπ{ 6#O΄ρ¦OλγξΛζž 8 ΊˆτFSQ2ΆΗβΪ‚MΦIΚ›έ/#_χΆπŽ@θnX,8Ο‹¦·xY [ΦτσΨΦα2§j’ˆ―Ρ‰pέ\±Žt‚.½τ·ƒΒ˜κφαφ;―y YWγtηlΧτm'KΟΨ*,›ΉvΈωΥΗΓ1ΩΪ}Ωe-™cΞm:>ΌsψΫaƒ]f…vlώ– gœθίΨ]o>œΌ”σkΣ8¨β^>Πg¦`γvnOZͺͺΥKΎ%βΉ§ΣΟκfσΟΗjjΜ—¦4dπέ ϋŽΣΙX†--Σ§>^ϋ¦mCέWK= Ϋζd€ήAέn‡°έΊeaΠο·Ξζg’,*O?IοζŽ /­ΣGΑ¨7Uε@wžζδΖ·G'°ΑάίeΎξ€ΊˆΰGΨ$ά-ΨόO‹‚Χπρ{OΊ¬Υ‰.Δ:ιŠΰ©α8‘>:έ^UIŸd2ΧQd›ΘσHΟ wq sbLχuJ_Ποh[xŠΐ<yΔνGe&Ύ¦MSΔ:œ’ ±Nύή(vνΦήjΗpίQoΚΙςVkl~aςs4Λ ¦„κ9νΓΛmmσc£BΎΝΘkϊυ‰~½ςτΉΥ;―9°pλΉ‘pYIΩ±}¨οz½AψΑ£:pšκ›ΈyτΕϊ)CΚΔoΔJ.$lsΔ…βίb'?Vσ˜~ PQν&"ζxRψŽxNπ eFΩU€TWG ΫγJΪΗΒ5ΙδL!ΆΩoξ$™fIϊ²αiΗK‚™‚χ3x ˆϋ‡vi©(ελϊt[_Ηvσ),ό8|¨xΫ{όμšD}J›ZΗ“λ0½|Υ›‰₯›Ž £GθEMΥ‘Λφ8ΊM ΪbL₯ί§†~ΛυζL:[ΓZ…ΒυΆT―ήẎk%oh98HγΌGs™―6d]¬Ο}b]τgƒ“r\&Έ{]|S°™Χί£<›'-ΤΒ†f³Ÿ§€΄Έ-)υ6΄wΉ²‰Pf>SώVAΌΠΎ―X(ϊ₯ηλBȌχ5°ρ–θ#8ΠhΔz|ϋΠν’ΖϊαqœΊ<Zoœ&z;¬Ϊm²c8sΜGzm‚•9O΄εuΈ¬ΤoΎ£§€<ΓΫܐqΠψM> +y6ΒmvΗω‘za‡0zρ'Ι]8²tβΐs°Ή,Ύv_R6ύ—[Ÿϋ±AΈCή,ŽdR^7#n‡7φΌ†ŽΕvΈμWΚ°ΡΠλΎqκvu₯nλz9€φθδΰΙαQA0ΗscΎ<₯P†ΔΊΘ―#6ό6ΰ}ρίΜuμ'ςu¨‹Η$·χ΅SΧΡΧύ)Kuƒ)ΤcΙΎίz_cτȁ΄“Χ™JΆK^ξε`„ζ«δτms2―}Ψyά¬PΐΧQηBXύΞϊΌΉν“?ra£ςZ!ΐ-δijI3ε‚ qξv/Šm–Ύ΄u{Rλc="h{ΊΈApηw[ekϋvΛδ±Λ„uΡn' ΓkK6$6ΔϊtΉŠΨ~Ί­ΣΔeͺ΄N~΅Ηΐ†F˜σα%Ν41NPf±RϋcΈς‰ŏ2yΗ#νθΟ\°Τώ"Εε^«x,Ϊqmβλ$ΏlI(XΠ!τν±¬v(ouΡ τα  6%vx›2J_G½ΕP}ά’ίίηRϊλψ΅nΤ><"g‰}δΰ#u[ΪqM ΖA8CΧ·‰Xάί:ΡΡKl,>»‹…u£Σω8EcYŸ²Ιψl*6Ξ5Βuƒ•Ÿ)8”κ·ΝVλzήΨγAκΏ/A=wυλύ²ιΥ 7)§N+W)}A *N„χβΓλI]―%Άlό»ΕΎβ^a=ΚΦ e±pm;λx\—1fΆΉ³fCΔjΩ ……‘Š?[m+²Z&έΤΙU…ζ‡ΞΌƒš“7ek0‡X5X’‚Υτƒ;^oΑ¦'ίR™(ƒΔ†-UԈώΌpΈ˜)N“ESd”Ο|–Ÿ)ƒ^63qΙϋ KεCΠ›ŸΤΠ&Žco―tJ;˜U€°}(μTv[QΆΧΛBwZ₯Q+\πθ‘Ο„LI‘VΠΨ:*bΗ΅ŽΖVΠ’yτωaπ6‡"GA+¨]E…nYΥzS†;Βέ·1kOΠŒ?q?ϊ[GœΎrργ§χyc­Ώ`˜θE-βPa}N9Θ“Φ]Κd„ΧβcΔοwgΖ²>e½€cyIA=?βyxlσϊΉ›σΐ\Πi½;γ—ΤSΖόyzΑτΑΥβ#qX_μ*Πk‘―Εcq—»}Ω„ςΈoθ%κoDή°S΄!ΟP99‘ϊ³Ub G#5Mm:šΦ;­7ί9κΝ’ OχΠGs •|"πSmέώŸ…ΫwN>ΤΒ]_¬΄M‰kŠΚΫ 0}γ<ΧΆ~Κ³!ΈζΐΰΡ™ΐf£ΞŒν £l²ωZ—8θ9ΔΞΌ¬8CΔ›?Ϋ|8Œz‰ΖˆΗ±§ŒΑ|Ώ!ψΓ@aažΐMo€°Xs/Έ&οΉσxώ/Ξ±βyΑ΄Φ‡OμcR_;Ο΅Ϋ*ϋIΗuΑνΒεαΫϋkA)ɁπGiVv[žό…dFhΎJœΦ¦DψS­η°‡_λŠ;9^ϋ_―9wώ¬P΄’,όDCμ)Φάn iμi*η|βšδIΚyϋΤΑθ§^ΟuwΗτ”Η3χSšυQΈν{b6…o¨ΨšŸ―)qΫΈ<ηβnμqHcέ<ΟΗ…η­l­/8μwψϊkΟ†χžξ©GtV2]υψϊ‘T/ioζ›…r0D³Uβ°6%―?ͺ'O KΚ‹Βέχτ +ΚZΩ]D =iύ°|φ»α-}K―=οW‰£Δζ’ƒ @–XΈ‚ίω)€<–yp[vˆ˜.|GcΣrΠΤ%ρΈΨCΰ"ΦYsUσσ%Lk°`όΦ6/ώH cYοη‚OυqχΌ•Mž ζ‘ΙH<'ŠΧn‡_ή<₯¬-Π»Hΰ3λ穃ÁΆφΉ}c½ΆΣΎγš†χ]φˆ#Ε₯ϊΆ¨Χ~š~e3(T·φ―žΉϋ?Ϋ=T}Τ1|ψβ€0UXߺ˜Υ+8΄Ν‰ώ ΈPŸΗ>κz}™G/ΆZ+J…Α…›ι5–„?x~—]ό‰ν₯‚»ΞΥβ―βχbœ(Ό`,RCκΉΆ/]ηt[Υ½ xάΎθ(κχ₯ΏϋPξk>8γ;ε±PŽ­lVžHr£lνδ[*±^ΖεšτZΑΨ― M$žG:ΟΌ‘ŽΓ6nγq8ψθΖΗK‡mρaOΑΛ(χ%M elzα-Εήβ qŠΈUpx>§›ΐΨο„‚{ϋΙ‘ΕϊΦ¨lšΤ°9²,έ¦zY»ΞLΑ›Έ Fnίsˆ6iο°ΙŠvα™{Ρ ς6_:Σ\aόΜ?Ϊ.¬Π›ΝΎ21ωζͺΛY?μlΣ’Ώ xr£…a‹_Ώ:.“΅Ν5˜΅œΦ5Tώ|λPτήΓ‘ cΧPR¦οwΣΧƒy™IΉ£8sΕ/Δy’—ΰΡ}’ψ‡xCΌ/΄‚€TΈ¬’‡`c’οƒL=΅θς&P6‘oθη}‚Ίϊ„ΝΕ|Ο­―Q¦Ξwdla“’ςRŸqœ!˜ BκCqhZ<—*˜'¨§ œ'e ωg ˜νΕtAΏε’“ΨXπ„°΅Ψ's­$Yσ•ς&$6Σ>«Έ$τ:t]'|§’$\}σ ͺšζΌ$ΰ ‚―cσλ?_½οΥ ΙΑ£‘ڞΰ΄6/ztzTΌΣž ₯ό.΅ΤKίΛω4αΪZŒ›…Ϋ0”.^€Ο‘Κτc+ͺjΎˆ‚Ψ\σ&ΦCΔqβdp7Ÿ"ΤΏ+: oςŸ*ΟFΈVπ˜ιΗ\EΪOl ώ#²Ν{ΐ›<ύ4›δ‰δJ9ŒΎ™ΉF‡υ+›3ΩMšΆ ϋΕ‘J拝ΨcΜTAΉlp·γΪΠ&c™_ƒΔ·ΔDα~ˆ#Εϊb¨/vΨςˆψ£ΈG°ι9LlƒΗͺgέ>‘]οώaΟ%Γ?Ο}!Tl1?/Σ σ'Βό©9ι»Γ Ϋ%qzΕK“jγF5mO˜ψ!ϊ Αsη·ηθ;—ο7+΄_ E!ς²=¦ρI«b}m‰ͺ/ τΖίσ/=ΆιΤMO υΕ UI°^Ζ~ΐ'1άρ$6ψ!βΗb'τO‰»ΕŸΕήβbq΄ΈTœ'ΑA«ͺDγSθρΙ3MχεΰyMŒΨη1²ιRu«Κξφ¨ΐ&6o² Jϋ 6θαP=ΨF›Δ•Β‡λ=·tͺ¦I?R„ϊεβ 1Zœ(<τ”ν"‰Eβ|Α0>0Ovi °’|ΟAaνn}ΓZUΕαΙA‹C―#ίΥΫ- «7&ΝΤ’ΙΡCΒwOΎ¦γ΁‘όαΎ‘Έ{YΨϋΕ'’Ώu¨iΠFfΜo£ΦEf ί*—.…ε}ΒΏV…=χϊ Tκ›‚ͺυ΅ΰΕ|υΟ°D«¬\T=Χ#Tόkh(£ί³Ν §ΏύjΈFΏ^€i₯VŽ €d •"δνRΦ™GgT'k”;Αr8UPφ‰ΈQ ‹O0‡ŠΑ'ΒΪβqˆ@,φΊ^gώHό@π<Ž^(xτ|G°‘ ~Ζs_eWΚ©/Έ‹ΞHάήmhw™ψΰΞζ'6Ηζβѐ0Ώ_ˆσσoŠ`$›%J™#wαϋΔχΕ‹bΆ@μγ8uy σƒzt»yœ_Šί σ{‹― ZBμψ±ΈAπ„ΐϋ2ψœΓzόΦIj‰η‘ΞΣ¦Z±sbη9}†κ ας‚~‘jiiΎ²" ―^:|Ί λŽZ΄<άΚ”πѐёύ΄η’ργq<^>mψς.»¨½ΎΔ£χ†ΙB[]O­?Σk­§_œ|+ε§‹Ύ‚ΧκlR6A[”Κ‘uEGARϊuΞ@)‚qx̝œΙΟSϊ€ψΆ*‡φθ²>λ€½/pψpν–I{dRΚ¨c¬Ηm9ˆ<ΖA'Α ­!OJ ~GΌI˜ cβCϋ“ίŽ0‡OΕJaϋ™ƒη„νnǜ±μe“MΛ5ΊΩόΓσΊR<'˜;\+.ΚδRŠ0ΎυZ'ώAφBCJ=Ζ žΓ‚>^ μμ.N·ˆκnλ‡uΊŸΌΉHί£ΕzZ¨>ΙPΤ₯g(UΎHŸ)hMίkˆΌΨqπ•π8― ¬³?@)έw Κ;+ΟB"‹ΔΗβ@A°QΏˆ7α€ O@€ƒΣŠN˜υΏ«2’»ˆώN,ΤMΧ žz „‹υ‡².‚Ν μ'θΗf:YpG‹œ 6ΜΓAθTEMϊΖΒ5ΊΑcαKζ€νlnμ;Npphq€Rψ›Ή₯Οώ Ά§ˆ;>„gΕ‘›– Š?žzμW·ΑNΦ»ΡoΈ¦œzڏu΅Ž”ίI,—*ζJu΄9QπR»tΖΎO σ?ZΟ OoΞ‹Ζ‚τ,AA”» κŽ ‹G{+ ˆ= Ζ£}XΊLή@7ϊΠKlt€)›|ΡΗύJiA;XΈOl/6zσ³ΦΛ΄%Ο&BΗ'Β62l"v~„ϊ†ΔνzN€ΰqμζ`_±Ρ{ lœ'°»9ĘscM˜/Ύ΄•Mς”(³zxYu‘°0Vμcμΰem?ˆυ{ ϊ€Χ8žεΆƒΉΔόXΧ— t3l·ίOTž'κϋέ~«)mγ?qδWAΈXX ŒΧŏδx±«`‘8Ρ +ή(x|νΕά\εϋκ‘2Α―“‚~Ύ Œ1›qώ/Š―‰ίˆˆ Aυœψ»ΨY°Qtϊ'-‹U@έv½ƒb[³Ων:§Μ{α9Υ•’—±άvΞ“f»fƒŸ!° όAxΓ([+θρšcρSq³ΈM#X7όŽέη Ϋ[<:πΫν‚υY_|*–69}m―²Ι<πkoŸY/©…Ύψš5ύ¦ΰG―ηΗψq{]ζ%ΧπB²p,ο@•ί@°(Ώœθœό†MΚ]…@ω–@O|Κӎ;@χwŠΖ’?c}|d ʌνUQςξτ•J­ηε/ƒD/ΑXl ©?σO܍°gKΡW` c67>c3l,Θ;ψγνrμ}UΰGϊ Γϊ|·΄―π#ΆϊξNΚaΠOπ2ΰΜ5εr€.Ϊμ nsΎ˜*ΨόΆ1~]‚΄‰ηΈ•ΡΓ!Αά₯©g ±»I܎ό£Λδ¬PŠΎΑ‚yΊ=zN κlΕ‡^oΚΪΌΰΐ―‚xβΉPlό‡‹s¬ΨM°X̝~Y&Οu iAw‚—t© ž ₯μ*βrRίρθχΆ8UY σρΈQ$\κι‹8€™ƒΗ€ό=±XΠϋγΤy—OΟΤ+IΪQo(£Ύ%ΈΫΉŸυ¨(χα°Α>ΪZΈζ βωuiΟ:τ;‰Ÿ‰ΛΔaβ;β^Α!6B\/hOθS—0ΰ‹Λ6Ό"† ϊΏ+ώ)X3ζC,\{ŽΚ&Βυ‚±±ρ#.τ3^,Φ—­Qy&ϋU/ˆΚ)φJ±ΰ.ΖβSΗ¦#(}z§ƒAUI ’—»ξρ‰ ΰlAQSσşŒg.ϊ’w3Α‹ΝtΎΨU\'Έ^,6ΨΛέ;%βq9XΈFΠη9μNΙSOŠžlΈ-ΆΠ.†:@ΒΈγsb³vξ·™λŸ*&Ύ+˜‡ριΩ_ ο(1G θEgcΔνŽWcleόbC?=…νf,ϊL΄w9©σŒ?\pHl 8Œ9|=–²΅ωΈŒς5N˜τWUX™΄ƒ8O°G ’;Γ΅‚@!0¨¨v‘Ή#Ύ'ζ -ΟHsΐύΉΫ³,δwˆΓΕ“βiΑME{ΫκGκ2Rty­Ν5}Ωψ€i¨χΖwλPΥ*ϊΡ‰Oi繐RŽ-άυWŠΕ9βq΅ΨS\(xiΓaό¨` ϊ±υ ν|X¦Ϋy\l`|6>ηϋβ]1X`+퀃Βγβ—SƁΡ[pp!O tfΟ=[έQΖ„Ώ β…`!-, IyKο; 6‹Ο‰O›φΒβ@βz7ρ¬xUπϊr§ΰξFΕ‚ώΊ„1,S―W_Η tŸ,6ΘQβ6ΑΊiο1μ%ύθΛϊvx\}°ayJr?·uJΉ…±ΨDRδ©["x<@όLpX±a©η f_A»Kvγ+Κ©‡†„5|MΌ₯‘ϋ[ΎΒε_Dc„}…ρΌœgN[‹ˆν6§οώ*JζAΊΖ ͺ‰—ΝΏ§π¦δŽuͺΰ>Rpψ€…?D,ˆι`εοχ‹M‚~Χδ??ι»›φu uΖmΈFzΩPΧ‰Ξ‚ Ό]0§ΗΔ3βfΑγζjA ~μρa†έ>€Ψ$Ϋ–9ψ ½‘­η7JωsƒΕ/Δ‚yθΜ3Ϋζ§nϋ¨Ζ*ΈΫ_/ήθ_(π7v!ψšΆ>€<'6νθΓ!Γ}@Π°ΐΠΝoxϊ{Ν^Κ(cύ9(Xί)‚φ¬?v?Ψy— .Ž#ςθΘrΒ—!υ~L ±!X,CΏ©‚#κ― •΄§ŒvO ΰΑ݁€¦β@―ΉjΩOΟΕΖ›Ε6o.υl¨%bžΰΰ" >τGΈΖ.`£‘α`cΞΜeΉxPμ-ΠΟAηΓ€yeWdΚ©£>)8Dθ‚GΔΦρFΖ&c;γ9­―ϊ 1Q0/ »ι‡='e›-ŠzΪΗ1Ά²ΦψςlΑϊs˜16ε?Μΐ‡M6»Χ„”kΪ’ο$ρoA{„yp˜1nkΜEjς{ΐ Š“ 2 βΰ|εY`ΚX,`α 8ξj;AΟΒM{ χMAPμ$ΨLτσ‚*›Όv$m©Τ56ϋ Βž½Ά°YΌΉ˜/s₯ƒ›9r άε6σΉ‚ΉΒBΑ#ξA?Ϊα|‡ο† Ζδ.8_Πo8^0Βζΐ&t`WlyζΐfdΆρZε™O± ]τ皴9›Ζ‡³Ί'Ο₯θƒ8^°…92oζƒ=ŸˆΕΔΗβ#tΞ€­Ξ;eξχ όƒ0ΗKsζ’(Ι¨Ϋ8•” bQXxβ|Ap²)χβ±θ@Oύ―Δ2±›`σσXΈ•`#l6/¨²ΙݐԒ>—“ΦWΟƒΝB0˜Ά»m±R ‹kΪ’Ϊ6e“<>λˆυ0η^+nψ ^?ŒΗc0›ΰRρ‚p›λ”!Ζ™)Γγbl›mδ₯ά›ŽΗ{ž.ž3)²΅ΐ/ΠXAοF©Ζ¬—Εϊ/“΅'8τΎ/˜kΉ8"“ίN)u΄3ψˆ<ώ$eŽ'‹{ύ‘όPγ‡œύdAΩ ^P d£~O°Ο εŒ=³ŸΨdτe°ω ,t§€8ΈTΖΩ„ •Maθgœ80±ϋΉCaΣΟe΄Ϊ»Χή„Ψ lΜψπF%εnŒ.δtΑγ/cόeς\Ώ,ެ—”@χxŒoΫφΡΦ6±έŒAžzϊΣfoOΐ>RΆ^‘οζυ΄@8^°±›ψ`^δΏ)°ΙΎ`]¨w 9O{χCΐ=‚~Θyΰπ5]˜ΓΰΜ$Xπ8NΉΝΛΕb±D°pί³O±Έe•qE&Ώ«RΪ€εœtAζš`y_πθI?l%@I=‡'hλ99₯-ύάGΩDŒτ’“’šš§‰2ε9ž·‰™‚»τξαPΰύ/~ζμΊioaόΨ¦8O{Ϋηv§¨Œy(¨§=2Q8OΫΊ$›Ήp Τ'ρψq;ΖφψΜσ[™Jτs &Ϋβ4m#>Ά oOh3ΚγόχtΝz²ΰ0p?ekσ-wͺlΒ<Ν<ε/lφ‘βaρ„8[<#vŒ±‡xG\/f‹{ΓτeŽ΄Ao]γΖv²9ΚβΉ½ kt³^Ύc*›4΄‹ΫRž–sRΨΣ\Α‡ΔO‚?Μ(Α_Š>[ϊr·ίBά!žΧ e9 ΠΗ“ zžϋ ϊώD ŸŠσΊΨΨΎ[z,Υ :y4―ΰ`ζ±νψ<ΑΖλ›)χόhηu™νΜδJ<žSτY°›υ"8ΰπχΆ‚9ή*ή―κ,‹”±ΦI}ο|\GY›—Ψ1mή،8±³Φ”Φl°ΈΜ‹DwTΠƒΔΏΕ:bk±T qςΝ‘X~ζ:NΙ;¨ΘO$—‰UΆ±™ΟgβΑFBΨό΄]!8Τx­z–Έ[<'Ύ!xOΠΧ ΖCθCRΫτε‰Q‚ςTMSΔ`ΑZ.°{μcRΚ·ΆΝ~ eœgc½*Ž”Η~ΰ:χgΣ·)b݌gιύ‡ˆ~bΏ. ޏ`ζ}°ΐfdζ…ΔφΕ:νϚVωŸ­ξ/&w‘v‚ΗhŽW°ί=εά΅:e (i3C l6τ —Eώ@|M Τ‡γ±ΈρΒλ²N‘Πnt°1Ρ‰έάΑ»ΰe l)Ψ8έυ̏9ОyΠΈΛΪFζ`μ©όΏσ6ΩΉ‚z;ΫOΚό-l†Χ„uγλα™JlΓ&ΐ>ϋΫ™ƒηκy‘2|>R0Ο“Ύ±ίρΫTSROΪΫοΜύ-mΆ n.Ύ-ζ‰ϊΠ†9χ ζz–@ΌNΌd ?‡β‚6γΰ_ΖEW^ZΩήLήH,&CPq’³—ȁFξ;‚ϊη‚šE#θΆΤ+υŒ‹Ζ ‹ ε†rΑΰ±lt"ΨΝlzμΉ3} <7μ'Ψ°‘Ύήτδ=sΏ\ΜΜx}Ϋ_sUχOΖϊiTνω8΅νθ=Wx,Ζe“0Ψη،ν\{Ύτe ξͺo 6VΆCίΣw†ΐήXΆGEYΕσΕο€€]”.ŽΜc€ ά‡ό`q“ ώΑ\Ό^ΨuͺΈCP0ύXlΜK+z‡²°8'Œ,ˆ7‘€‹UΦ[h‹ύΑ"έ#τLθbΑ½π΄αNΑK˜1θ?ZpχcσΡΧ‹»Ÿςˆm#…ρ’―@/θ"ψx2™ z b=±Ύ¨ΤcΏ–ώŒΟ5‚]ΌTxQ`7ά)φν±΅)‚νΨ›M<7RζƒοΖ»MΨ†G”ί[ΰ_ΪΰGRζΒZω.Κ|―”γκxR σdΆ‡΅τBly‹σe™ϊϋΰΕgθ… ΔbψH θulŒ?TόA0Ώϋ:°O· κΪγΫi[¨ΛK <ΰΕf€,AΓ‚τ,ΔΕ‚€ bα'(ΏA θb‘½Π,ΊHŽ˜ΕfQ½‘Ό°Ύ>MuHZ6!θuπtΨΛFΐί ±|Ÿr>o~Ζ§―KεoŸζEπώLXάΊ„ΰl©`ϋ¦’χz(›΄3•b\!8˜ζΛό™'σ/x*γ`‘ίΡ†Cƒy3|ˆν^'ϋΒ~'Ε_³Nνι‡Φ2>Πυχv΄uRΖΐ—τ(~+8œŸ΄e}N· t ΄ΕVΫ‰μΚK <^XœzgFAC`q7‰ŸΈ{²π7 η" +&^hΪν'‹A‚Œ•€ Έ|XŠΛ¨gρ°<τxσΰΨL°{σcOΑΨ§ oΖF?u§‰©‚6p•ΰ‰Αy°‘F™zo ΊšUsDΌ>€Μa^ΏΆυmεΏ/hƒ­Μw#ΑΌ& ΪρdD?ζξC€MΖAΰ5°_ρqz-T”ˆ7.υŒEϊ³†ΊγΕ ‚±bϋŒΧ•±X;bκLQ!¦ „5ΉM ƒώ؊—1Πa½Κζ₯©πbΰD‚’Εp±|8ρΐB<*8±"θο…°NΥ―u£\±X|]x“¦ƒŽ‘/ ο s_lρΟφb;6y‚ώ"±\`§vGεοKAφ„ΐ.Δsr>)¬ησή§žzWΡn_Α|κ;-φ+)ώτ±‡ςΨΨδ·›jw±Lœ#πύίώ&ΠCžΝ‹OΣkψληb#Α5ΠΟk@_λ`-φ» ž>H™v[Θγ_ΚYcτσTu²ΐήYβGβvΑΌh‹nΖr¬ΰλŽυ«8/ y‡₯‚ΝΕb°˜k ξ wdΑβ%/„²«ˆυSΟBΣ–ΝΘ‚^'ΠΛ’2γR^`μ‰q©ƒŽώΨJpϋξ^μ6΄eΜ ‚»ϊω‚—”qΗω½`|„ΰNύ#Μ±1r`cΥΡ&^7ςψΗφž₯ό\ΑΌˆ_ |πͺ Μώ Ε_› όη5ΐOιuπZ°ΡœΗ΄σ°~όPόG0&ύΨμΆ[ΩΪ<ε΄·nΦξ8ΑΝ…ώ·gRڱΎl‘Μ±λWq^κσ€ŸΒ’7Αbψΰ‘’7uXί-Vžπ"(›ΌiF`‘ρ8,0νyJ,ccxa—ΐr²Θ€]Ɓηΰ³­>Π7V`?v0?+„ƒ‰yόGμ%ϋ‘ζ*χ? Ϊ–Š7ΎmG§ύ»•ςΜžtcΔΊ‚?α|oγSπΰ»Ψοδέ†:|ΦΑŒΔΖgβ8`£eN&ClΈόρLΊΡΑMβαC€907Φ”ρh‡-”9ώβƒFΕy©Λή”θG:ή‹ΚB(HoAή΅›ς,ΊΐΞWQVρΐffρ t](›…t2>ΑρfwPŸφΑŸ•g=ρ;w[o^ϋŸκYIέΖ}μw|Oήγέ€<γ½*8h(·}Κ&yΫμ:ΦΒρΗ!ΕAr’ψT k° ά6ΠΦ‡saήθΒθn3‚AmEμ/†ε4Ηι‹Εdρ‘˜&‹Δλ’’ ν§5W5 ±ƒ.Ž,uσa†X$Ž{ Φ‹‡-Ξ/Λ䕬4,ώύΞΨτA7wώβ%Αc>ΩF0Χ Ο·\y§±BΏΖ›ε›ΒsnLŸtζtkΊ0sΝέ|FTG[„x―γBOn}ΕΟr¬˜-žϋ ξΤ΄ƒXΧ‡ζΟ›r―‘σ\ΓAΜμ+n£λŒ^|§Ί¬M=π΄ΖαΠE_‘^oU­^Α/Sp€€-δΣΞ!(xάFp6ds}  w©@hkIλΆ^τbS†°ΈŒΑυα’‡`³/iq[Ϊ{άΕx}ψ˜X ΞSΔ8N6t…`ό;v?.8l8 Β’kράΣγLWΑKΆΒ5ώJƒ ψ 0gR6Ϋ ‚ wαž‚vύD7Σy½Η£νhσ ύƒΐΖΚϊώT|"Π93“[@IDATHι±HχGσ"­onΨF°φΙ΄%|WPΆQ¬©}nVμnHX§Υ)Ž Ηλ„*Ψό£…»Y#|‹ ׎!βA ‡ς;Εσ‚υ„+ΔP?Žβ<)ϊˆbˆΨ ±Ψr’ .Ρ‡=τΓVμ‘ΠΑšOp@»΄C§Η’O<7Φ6>t™;a \ ‹OŽΌ―mm—‰ηodύRΰlΪΕ‹χ₯Βbΰ@_SFΎJ,{ž*ΈζŽqM&₯l;ρŒΰξσ;q‡ΨHτ[ ξ*#w λg±m?eΩ H±«LΜάe>Ψ€PΧω­ϋnΣΨ~‡©αόcϋw…7aΝ[­šωցMσΆ ¬ηxΡ_ΨΏΚ’ηl*όJ»­ΕΑ|NΙ\³Q9ά ή‹Δ1‚±"τdνΈ£ eˆΗt—sΨγχ‹#ΧΔ Ά ΄΅Ξtήϊ’†kβOˆEdΒl†v‚S/ήΜ,:8ψ¦+Ο†9Aΰ,pϊωdζTζΞΦ[°pEw?Δ§-Ίθ0ζ_ HgΕw‚½ŒI{ƒΟbn(θΤsςάΈΆ­”}3SΧO)c욹ŽΥ$δ—ΔΑο§|‰οO=ώf­μoόκΌ}ΝϊO°Α?ϊΑkΏ…ς¬=kΔώ!ΉK;&‰%ξόΔ0&7%⍾ΨC[ .7ŽΚ‰Ubλ#AŸ‘‚8΅Νξγωc«}Γ>Κ‰0@£!ž„σ€c|ς8ρ31[τGˆλ„Ϋ‘fΛΟW9Eά†PL<|_\!8DΆw ,Ϊ€€\ƒ)mHm)‹8X¬"‰ψŽ~dΈθ!θΧXaΜ—„νhl?ΪœΉlΛ&ιωyώ΄%lΰΏMά π™ϋzΎŒAΛ•Md/ύdc±^<½±©Έϋ’›»=ύ_'ϊ+Ψπw ڜ%؜¬“2£l­P† Πe|­’ΔvΖΏOœ*ήΚ0B)1‚XG:΅>5Bl0ar,&››€γtda89‰ ΞFX΅»xD τIχc±8‘aΨ@°W‰A‚rŸq8½6Ψ Β ω„ςϋ[ ¦Ψvm?D}§ ΖσΙmϋΛyΧ‘2>ύι{Ά Μc)[+τΕW«C˜ AΟΌs%fia~ŒβΓώv¬p}½ΐoΔώΕwφ1)ςž`c͎οΛ]™ŒΧ˜ΤλΞϊ Δ1δƒŸzΈ°M=•ΗbωDρG}τ%ž³Ω‰­ΨG=ωΓΔs‚Ύ<PŽ!τaγ2|­Ω|§βΆ#±SγΝΓ„b'°9§αP&Οbαψ=Ž@άΗΞΑξΗ#α‰g²xύ εΝΔi>OPΟ‰~²°#½π*ZEσ`!˜Γ’\μ-°;;EΧ”a30ΖούΊΣ6Υ΄ΚύΟu›9˜–Ώ¦ hΓζ`iaCΕBΫ]…ƒuΓgψŽrn · —Ωη€>άνobƒ§Έoό½—@6›UœΔ~χz{³Q7VLΔ ά)F t—'‰+uΔρΗφΗρΛ5vb#!A_ ζG|ϋ‘/νπ >‰γfuNJ†oXμD υζg8‡Ι31„X8ςλ ξl~ξό; ;Š4ηe;dtΰΘλ²ŸxTPγ¦A’>GΖσa<τ=,βΕκ―kζδtP’gR¦Ξ ©ΛVt(κ›—΅|έΨt@ͺ!cqΰ¦Εp\N[:lήWπŽΪ7μχ όGάΨ·qΪΧθ?BΌ*θΓ΅QΆNqRό‚ŒƒG‰:IΉ©+.”Ρ6ΫΪ§γ;™m‰§o‹Ώ tpˆβ³xnψέψΔ”νUQې‚vCaI‡P\\J ‹c1œ 0i&Ε€Ωψ†Ν?@0ω2±•ΐa^P/jΪ‰8}ΦΣGyƒž'2)ωΧΔ‰ΑΩ8°©Ž£}”Œσ3±Xl#˜“mα)ΐAΰ@ΕίΎϋoͺ|cl`C] οΛζž–cUΐbiξabΜ@ž@ϊŠ8HπžΛΒqDjπ΅ύ=Pωo βι‡IΫZSΪπO―;-fšo§τ.A|Αϋ™TIν―YΏqάΖqνψ%VˆCΕΥ}χζNl3?ΪΧO‰_ŠΫ…’Ž]Ba·>͞›TΆ‚ έ19™bM, F:p :wOξL–GvΓζίZ0ιΉb3/dzσsN;α—Η½ ž"Πw³X_ ΄o‰80I9@˜cΐ-‚dn€MΞ{ρ°‘Ά2sΈ@ «ΉA©«UnΜ2>Θ…ΰβ‡uf md€xOΰGξ„Ž₯΄―Yλο‹Ϋmy @šβkΖ=MŒΨΑZ1&ΒZΏ¬9r©˜-*γύF\%n΄£8n‰Η91ΒhΗ“ΐ…=ΟόΠ]ψ θΤ₯gr­’ΊeΔ.IŒΦέ ΅j6S³Α·Ω10l—pΧ€έΓβΎ{†κΎ{„κ vWͺΌΚΚ†Ž ΆΪ)Y”PZš<ξwΣr°ωχLφ³qΞ;‹2œc‡Ώ€Όe27 tΐΔLz‰ zX8τ NœZŸ€…….θη δ@žKΔΞ‚…ςΑFꃀ,*E?α N£ͺ…ρ›#ŒEpaSC’mŒ ujεzμΕί>p9ΈζΞSΤώ‚΅e>i_sύM±X\+ΖψΊ6”γΫBGŒOέI‚MOό‘Θδg(ε)aΝ½ΙΧτ'Ά|POέ!β§}ο&}ΧΣΎYWΧa۝ΓαΪOi_-υ^cΏeφΪβa»†{Fuπώ€_«Κπq5'Μ–cΒΉ½φ ΥnͺΨ+”O, •ΪΙUΛx˜.ž* UΧτ+έ4¬μω΅P½ΕφΙοLCQIςYm&ω΄`ƒΨ!€€3ΌρνtδTρΆ |qŽΐ‘ eνiO‹BΔA‰ξ^‚Ν‚ΡŽΰγ ΰδwž~Ψτ™ΐ>λIΫ’ͺεΊ[΄ΌΑ°,*² Yš΅ZΎ‰\όΖϊξ*ζ6Ω@q˜π!€Ι³ω/ψœ'@όέ_ί­v±ΠΗΔΆ°φΔ'rŒψ`<κ=Δύ‚2ψ—ψš@ˆ βΫ:Θϋΐ~τ(Ž+tΜSZ²ωα φΟw6 Wχ εO–†*φΧΗήΥΎ{BeVέΑ›…ΚžΪ—[οœό…gو'‚Ζ8Hvθ΄q‘€Ό:t_^ήΩω£PzΒλz­_ *€%…š•1σ*]ιGρηΒŠ ₯ ώ£tKΓCΥΥΙλσΉjΒFJΊ(EΘ›q‘ΨAΰl^+!,wϊ§‹ΠžΆη‹"ΦλWU"˜K9BΠ±q?.K¦₯λ* .£Νϋ‚E%xp?e“—7Σ”ς„CPL]q;]Ά ang‰_ ΟχΛ2Μ>φAΐ56-. ΦΪλKΊT&ώ ¨λ#華ςw΅!caL±=ΎfΘ*ΔΞ¦©š†Φ=Υ|•K―‹νuΚψΨΒ\fŠ#φ$ŠΏ Ϊή*ŠΟT[Τ΅wθ<`XΨ·C―πσ{Πγ­fζ»ΎΪ4J„§ο.ε‘π.νΧ%ΕαΎw‹Βξ›ξ\σς=­딍w…3τλ½₯αΩ&*ZΥΊ΄.ΧΦ©εσ QKΗM’aΓϋλιQ§―ZόVΰ[α'ζrΎψ(“ητGhGΞΗΪ7^(C(‡\ˆΗ°nξ6cΌ Έ›€t)³ΟρC,Τeƒ›}‹³Do1VΌ’ΦΏκ=$ΌψΫηBυ i+iΑζ—ΎDЁšŸΎ*–‡Ι―?φΕÏΰΝ*zτ/ήlL³^YΉΫΗ‘„₯΅ΓFΙjΛ Ϋ,ΉΛ―­—?—~œιΰΙ6tΆ“|5L/FΆΎΝ)Co¬›~H\ F ©)Ϋ_°Πcσ Μ‡˜²uΚΧU³ΎˆΗ¨³qͺ‚C'ν:ΧVm±©Ή+zϊΪFτ°! IΆ5₯ZWυSΗ&ηέq|ϊ`ϊ{?]‡]¬;βyPWŸί“Ζϊ ταP™²ήΐπMΆG •ΖQ CA‰΅©²₯ΒΎ;7χ\ϊή)μ9B/ιcuϋϊ„P±’(œqδ΄P± νΦXC3σ 5ς)―†ΚΕ=Γ‘¨Πo˜v εm"©σ΄YGτθ΅€šςΈcθLJ>RΚΗ²T™τF£mŽŒq“ έ›‚:K0:―l£…ΧΧΝ·ΩρaΕΣ O1M‘OΥ]±€―γΊ\δρ[μwn<p8ž)XΟσ2ΧJjύΜό{Ρ{γυ§ύb†°6¨*„˜AX{|F{ΚΌζδM\W¨Κ·Ϋjλ°Χ.³kήP»œ†^€ΧJΥϊœAυηO±6|•AεΒ₯%aϋ‹CΩJOa•­sΑο7Gκ„)¨ £Š8wk―ν(§XπOΑ»ΏHμ@ς`q―§+σ’πBR~ ?R2)Ί¦ν,ΑλN^RX’8P<.νy x_tίk νθηφ”“―™1Wu Aή\‰ηŒόΐΑΤXΑζ9"φAcϋΆ΄ώ±ΏœΪ‡ΔςΣβΨΜ άu΅}’5v[RΪ7fσ«Yς'β€ϊΦ%ρnC™Λ)sμΖ)λ_'y}2Ά]uϋ0~Δ§‘šί²εJ>“ξΑ‹e@u!Ckc/λΜDΏ,^k%/Νs'|Špε‘ZFt«\™8‘±Ι©σiηQŸC)‹…MΰSΪεW:₯c’|:λq\žΎΖE0]p§Ί^ΜvννgΫΗ±YTlVΉ3K©}’₯ͺΑ"!έΈ‡ .I¦‘TuN.cϋβ<ƒαSΚΆ~c½L™ύB}Œ.”ΛlρΕ^wjώ‹¦Ι Ψ₯"Tθ3λ80 C²Š>ζW NΥΉ4ŠO–'Ÿ(,SΆΖ)5‹Μ]ΗNΛΆω)㉀>ξ§lΗe\ΗsεiξΝφΤhωό§Η―"υyτδ%DΊ½]Iʝ©ΏhHΠ•ž4š#ψƒΧ³Ά%Φ1W§Ε-ΜΗ/Ÿ«Κv‘:χΕ~Φo€8Aπ>/σ6ψ€Ή©*Φ qZsΥπOΫ’ΆΛρε˜sκv€”9λ΅» UΊVλ7β:6£ω-x@Ώ~/ΤgqΚd„νΣΥ*Γ[³:…NXœ+)•3t/Τmf†²8‰dA'6“‡·IΧZ―—)›H|MΎ₯b}qŠNœ<ώ_,x%ϊ 6)s±€ϋηќ;μ{κGΰg“†^2`ΗC»[KΠ™^ΦξΜhΪπ„±[TFžΖ|'§Švφ y@μkςάρ9y§ώA,ά'vk χ#vœW6tσ”ζ1jJ³τΨqj}€ΦOoςΎvή©ΫΞύΙ#•+Be»εaΚϋC17Δ\I'Eη,½Ρ'€ι¦v$Œό’„Šgž χΎέUŸΆ¨mϊΕf--!Z_Τ2–­ Λ—%F±0Ÿ‰q‚ eηΕΞ%pΊˆM„ŽUœκφqJή(Ϋ$‘β΄ζͺζ'eάyφG‰‰β#}|˜…MŽΔcΗωl―OΣ©FΓͺ?κ2~S,εސŽ]βMΜg‹ΚxΏ$Φ€(β 㑨Œ6'ŠO2eψ†υŸ.Θ[β<}Ζ‹σμmβf±±xRθmDβ>™’Ϊr§ΊκiΧ‘‰γΆή”Ϊ»ϋ–ΌΕεΛ> εsŸ ŸΤ[§—΅ΉU+¦l˜Χ΅ŸυkΖ—εdό˜†~A¦>–<ϊWΟmfΟiͺX•\ˆ>ξXύžΆπλ“Γ΄ς₯΅wslβέΨa o§‘G˜SμhΚ–~mδΆq½ΛœΎ1βφ€ΦG?–λCΑ ›™ƒ€ΗΣγΕ1\Έ―²΅v‘―KφP]ŸΤ&Ό!V»ΈY”ΤΧ7KσUŠ˜K6Ϋ8pxš‰…‘}Sύƒ„ύFΚݞ'=lgmΉγσλ{b¦˜(¨?[Δc¬ƒ<βω;­)ύόgΊ―94ƒυw ΰglAάΞiMiέ?ΡΑS\IUEΨκ½κ0NΈ³tn©^ ‘•…ΙN—η>Σχ₯'Β‡ϊ,@ν–Ɛ¬²ιΨPάwIΈτϊ Cek~2©v0Mτ}₯Qϋι‘ZΏΈCεΏ„ž ’\םΜ6Η©σ?Vίwn€,[JYŒ.³JΊ ΧΎ«»ώΟβMΑk^Ζό›θ 4΄σžΦ«’ZyGΉζΌvΆ‚τFtyœ6F?-› ?}Θψ!-ž{Ί<Ϋ΅}BΖφ5ώάFŒŒ»«ψn&«R>ˆ5CΌ,N΄ρΊ£ƒMJZŸx,§τgƒδIυώyςŸΐςτEΒ@1^ “§ΧϊζΝψ‹—‰γΔAβ(} φϊ#Nχυ UΉψoΆίέ?,ןŸώΦ Ο*/O Sž —<ή'”θsΔΙ_%em،ΒΔCϊqν} ύ­pXeeςŸ7ώTͺ&‹ί‹#EO}άXYμ` –•"βΕsΪy‘”­νλ°.—ρ*ΕyξaαC€ςa9Bpχ!ΰ¨ΏZŒz°KΖ$€NH£’U„Γ‹§˜l‚- I|¬«νκͺˆΚ9HΣ±ίχΩ^ΊΔk©irΏΤޝ2ω)JŸό~΅#n΄cΎ/Š ρqŒ ŒυΗ_7ˆΨηφ;iΗσ{G°ή.[[ωα’»°3•Ÿ$Κw*ό‚μn/ΆΌ\b .ιWmΧ}8-όίF²[=[σ)Š₯σύ “ΒωS_υύ£τ"«ωη2j—Πaƒ₯a―“ΆΧWΘݞΥη-šžCο+œ©s½Ϋ’π«΅ΦMœΨGΕƒΑA,ά}‚Γ€„σ‹EZ˜£…v\3 ΰψηΑΑ\ΰˆOxΚhKŠψšEίGpW‘έ»‹Ža£Ύ$8υ_cӞΝΞ]β$A9cίv)»Jžk$ώš’Ο4-Ζ¨Jxm]Ÿ-PΡ€&φK:E ~„=Ε^‚»ώυΒλεCzšΚ.Ή8Ω]Τΰ,°Ω>ΰ‚ χΓU γΦΘMe;¬DjYΰάά¬ nf―Ε“ύ9πΔφΪφΊό^PΧ–;Ζ[`Y΄‹ΝomQŽ‚ξ ρŽc_…―AάΉνX>κJίn¨p1˜‹λ’δέE5hΖϊπ…iŽCx­ΝY_8ΌΛŽΛΒί'oμθ{7‘λΥΊ€:ΌŽ5Œ”’’ύ-€WήΖuŽv¦J€uWuvi›b*ήŒ·­@{ΏΎό9 …€‡Η~π(t§ημήN=rϊΩ€ͺτςΩ€šn‘™K"NΪoβϊρFιc>ΤwίH—ŽWΜsuε“Z 2.žΎ'νσzχ4α„-<οή~¨`qΕΧ! ?–ζk4ε±qΕζ¨*ιΡ^ΣS[ηί{ƒε§Αzp1ά :Σ“S'»ΰ F,6§νΤ%κw!<α½v6ΛΆŒφ4DΏH)*‚ο2™pM隀^lϋxXy@ΈΰΡ?O)^ΰSƒ^7&κΒδL*=H%ΞkQ²(Ώ.ͺβΦ듐<οϊΉ>ƒ 'θΟνΰ—`μX/©ρ£ΟΎ9~Ά—υΧΞΖ™©m’ΩϊΌeΦ•‹γ:¦βΝDŸ­ {ΒπψΈώƒ“OΊ‚λ€ύφSβΣκžΟέ—Ž{lω4ύŒχ‹ϋfqΕύٝΎ?Ϊ8Νyf₯τξ“γθΖtΠ΄iςΤ?fouKψβφΌλ’jΊ0€-Μ>λό(qgΪΞξ˜jΏΒ»―sšϋτ]Εησ7§Zη;qZΤ;’θΖ΅Τ y.¬Cΰfψ \WʟDκΨlHΤεMΫ©Χ1ccΪΖ24ΚLmSήίrΠήCΙೝ}²Εϋ–Ÿ¦JΨ©eΪΏn©ΞλΖΔ»ΤΒδ‡Tz -+ Ώ5e|ƒ_ΙύPž―kQwSΨ„‹οΑ,˜yΜΨΟ»ξέΰΑ  퉡‹ΤqΕΊΌ>lvκΜΕυ‹5t#k² e_†οΐ5ΰš³‚Ί€·ΐX±:”ΠλΌ|κυFtτ„ /—9`;&Φ9ΥΊΧζaYΉQ΄­λάkΆΦ…/μΝ=ΣΝΌζχPjTœp“ειqι(Ύcl«―LŸ΄yͺ}a₯T½ ϊ[ίΈΘρέΜ•αΝRυ©§š·η#ν^/œπƒž ΐώ Bœ‹vν\*ΠažόoΓρ`ύ‘ΰιiΠί €ΛΉh—/–υφUΤsΌuI\[Ÿ‹Άώη[A`Ǎ±g“,£@JΨ`ͺξ'ΰ%°_Cνͺ+/kLGή)wχ°5ο·°ΌσΨ 6ύT.§— bn¦Žiš—Ω¬'θcΣ=@ΙηιzψTσox|Ÿθ&ΠΪεΊΈ±6‚8hcλΥΧdλΗ7―XxmL‰φφεΈ€ `Mπ`Ώ\Ρο1·Πgͺύ±N Ώμτάi$μtθ¨Τξ”MωuyΟTν~β“| μ3χœeξΓ‰+¦κοo–jή6Νβ¦;jό=Ε«B]γ5ή’¬fΨ˜Τ‰Ο Μέxt:€'€c&wNΫoΔ«wώp¨†?νε§™>μ”ΊΌΡ#uxjenγΥιa>η| ―A~;h³Τuβ£Ει­o€½ΰWΰk‘«ΐMΦ»ΨήιΛƒΡzΫM|0|\θwΰPηdπΠθ–iι²HΤiΉiŽ•IˆvΉ˜οÝπEπnδβϊdςG0Ψr.πk ­Ϊθ8κ ΌΘ~JœΏϊ%1‡E΅[T½z\ οhΪ•‹u§ΓY0=―XΜό–΄τ­ΈFΓ?KΧ$…ϋήp* ―c=΄E,ΣΗΛΓκp8ύΐ'ύ¬ψDι‘ /sͺ£‘yR\ˆγΉnΎΩλSho£Jt&½.οώΎΰ‘ηεΆR΄ΐX\nxvP³CI7„ 0Ÿ―πκΒ·ψΜήxT:”ΜΓορ·ήˆ¨ξ?#Νο9§˜Kš9u}½Gjο^γ;ογ{7/βέώ+ύJπgΚήρGη§ΔΙ/‘π7ΕUύ­»ϋ‘6ΖΐΝyLι‹'ύF“I|‹π#ά_,rςΫ…ό‚‘Ž]R;Ύ,Σ/†―Ααfψ+θPΝΗz7—bې°ΩΤ6Їmt$(δ.~ή κœƒ›7‚l!ΉΎΘ»pζƒίΎ€§½Αφ1xΈΈΨϋΐ΅ΰ ¨Γ~ϋΒe°%ΌΆu^κ2xmύ”μNΙ“ΰαf»ΖΔΗΛζz ° ΊτεΒΖk̎¦”;oη~φΪuwγθsΛ]/ΛΌI\ 7„°)Φ&tΈIυλώpΈφG€εΡΗ1ΝdλλΜ+ΡΦ~οŠΠ†ΓΦπ…υ,q°+θ{cΛFQ‡φ)yΊ-3n΄u hοΠΨβ…Hκ„›n7]Ϋ§m·M›σ™ώ-Έωφςš―ι{―Kuzδώ‹οτT±ρ‹ΆΦ/JΒΈE΅[h½œiΆGΩ[?γωΟ6lˆcŠΞoΑ/ΰnΈ b©*$Χ‘ΓΤπ$uA1ˆΔ²₯τdRh ΠfΫ)¦ήi"ψ-[RY0(Φ’=Γ@»—₯ΔΊ;χάW±ϊH1.>†…άΏαg}οΪή΅sΝ\›ΊΏγΓ§BΧΤ΅υΪφŽe_}"Α‘°%ƒΐυώ Š}―λ= Ύ Ή:%ζΧ¦φ5Ύ…½ΐΓ_;ηςJ Ρρ:ncΠ™apΑtV82œ7’2Ρ:1―σμϋ.t,†ύ•sα °Œ…CΑΆ‘zΔλΥΑ ‰ΝουF`ΏΑΕT΄Ιΰq|Η‹Νο΅Δ’‡}>‘ΨΗςθg0»yυC.α›ΌliσŽ»¨{YI¬»svξϊM_„―bMGSvθs7Ά„ΓΏφs}ν@?ς‡ΑΓ`ί>ΠΠ`ΗΤολwψ»ΰQ°Ÿwβs (κwνΟƒλ8lkά”Η«νΔ1Δρls<μ>εόγf™HyΠ}–Fθ8Εt< ύa xβο :0‚Υv>:yΒkwl(Š ±]<©Χ…4ΨNƒΓφg€ŠΏ‡Wαw0 μkŸoAŒγlO™ςmˆυ:κ΅)ˆrSuΪζ:pα•Όyλλ•Α°hϋΒΔ~Mƒξ6Π¦%•₯ ֘§iψ!ς1Χo-πipL«ΐrϋ(ε~³―σςŽͺ―Ογe °.κυ§wήuaΈ!Ο†` ΨGpž?„JyγHύŽ[Jψ1χIŒ•Χ›χΠϊ*L†Ώΐ$X΄Gέg%¦cu€'₯Ž5Xuؚ`™§―©'²''²wkqCΪηX–οήat°ΊMc,οŠAp1ΨW^„ΒhP§wΙ±ΖƒΑ nΗνρΪΆκ4 ,›mO§’_©TgΓy{WΡNm\ιMγί€σkY%Ϋ4’HΫφn€±βά.ϋΗZΈ6‘ώ ?ŚEΩwΑΓX_ιg}Ύ4|œΗƒν]³#ΰep]]uΫ~π`y^λέτΗƒk h—Άj»v:Ά:b<ΛΥ,ΨίρΌ»GL8ΫFŸΎδm{4xx<ŠΎX¦²Μ `φ:P1υ„χΤΦω¦€ΑΗΈ°ΥΣΫzQ,|yΊm§ΆωΩ`ΏWΰ°ίA0Ξ€[JlKj0ΈΰŽφΈau‡~ϋΗΨΦE>lσϊ9˜ί;―3”λ’j‘ς>΅―ώj™ˆ’Qδ|·†˜_4Σ?‡ƒλ”‹εύJ1/S˝o€‘·Ξ1FΓΙπ1ΜΧ΍¨Dγ ΔϊXσΖΡ88”1°.ό|ΊΌ Ά€±° x \αCΗΜΕ1C΄5ΏΆΌό:lq~ήDφνy6ϋhλ2'ΣD‡)¦nN7™§¨pΈ€ύ Ϊ‘-‹ΑΧΦ7$±0‘(baΈ`—ƒ~=άŸHN‡ŽάŠ {\Τΐ2ηβ]Ε2οΓJΧ»”Rν5H’iΘ’ζ횚κΟsšΠX”Ο-οvJυnœΛΰέΌ!yΛυ™s‰ωθk}sΞηo›N₯:ύεΣ†ν’oδ}oζ ΠΞ°ΥΝ$^{ΐz0NΧΰχp'|μϋŸ c`B)οS@θ"Ϋ „Ν‘F£ό:tXΦ v„ΥΰlΈ F‚νκ*? „##,œ₯‹Ά9ψ8θ㜏\nVΤΣU‡ώ †ƒmΞ_?Έ†eopcΏ§mτO¬Iψ[]’Ου›iψ|uς£αxΤγΣΒΰŠϊςΨ( ³ασ°Y;C[΄ΩX9<@Τkk½6jΣΊp8όls!΄:qaZ“θ¨ΐMο"yŠ> ηΑn°)Ψ&—Έvα\4η•/bήΆ±|θp³+‰w‡_w²a\οΐE0΄3Ζ2―¨Λ 1ΈΥ“Λpa°ΨΧωi«Έ™ΕqL£άT‰1κκό‹V”†yšΟō΄Έ‰lcΖόtΠw}aπU,‹υ1ΥoϊHqm\³΅α`p£ίί…g@ywκw¬x![ˆcηφZuωšDYΨ}΅CΦƒ-aG8N†―CEΓ:Ω…pC+·‚μ ž΄+w7™u7ΐΖΰ)m )ηΧ%υ YΊ,Υ-”ipΨΟ»‰8ΎzŸ‚ΨΘCΘ_P*·ξyψ¨γ^Πnu„žΈkΈιΓ^ϋMm·Μ:υ‹σƒ_ΪiΑGΆΑΧγ–—Kl€ςςόΪqώΉώΌ~ayϋδ„½κtγ8~άEŸ>ρptξ>ŸaOΈτ>qυ‰kαΪΔϊ¨C±―εƒύή„“@swΈ¬σf’ϋWΫβ "[ˆύΤ»*D\hΓpXVν8ΎκuΪκυh_ΌAŽ«Mϋ‚z/mΝΧp4ΧJψQ»·ŸBτΏm΅y;X ΄Wύ>m\Ž―ύŽ·|ΛGAΔ.ي,‰\Ρ…QN{-œn0±Μ`sp“<.Š‹εFq!Τ }yō…Ά­’>eL-ru?΄!τμA~·R:>ZjΗGπkX ‚|XΫlηSwšoAνΑe[λ΅ΕΰΣ6mΗ—πIΜΗλ€μ§δ«Ÿ*iΈ@ ‰ε;Β₯Κ+π‹©Ύν–X}>p}bσχ"/›ΑŸ@ΏΌn4ϋ8oύΰ&ہY`»Wΰ q<ΧΗρνη΅~άΖ‚}uKC›ίM%Ξω(ψΕώˆn€s!6–>=VΕ1bΎ¦a[n‹σxpαSΛ΅+ζ©s±N݁λ‘-φΪψhΟ°ΌSΊφι!|CΆ"KλXΨ27Β€Ξv/εo"υp‘\X7WΎΨφ"@ΤεBΫΞ@Ulσ8LΡkSƒ«\ςΰ3oΰˆb€ΠΪ9†ΰx‰ε?‡> Νq„ύ ΨG΄{½Rήq΄Ν`3ΰ›Ό6Ώ(qξ Iθ1 έ¦ω˜Ž½hKψR;•|σ;Ÿ|9_qSι‹“ΐ—EύαPΈͺA?έ ϋ‚’ώΨ`εσ [΅Q_i“νOƒ§@]ŠsΆN»mνΝΗ\lc •ΫnωαΰΝA}*3KωΠ[nΥYθP=ΗΰΪ \€ΧK©A²%x7uΣDΊyςΕΞΩΰ°ή…³ ή Τ{>D°5e3i£ΨVμ«Έ©Ο\½q7O~.8AlλF1θʟ΄Sœ—ΊυE`ΗΖΤOaGΨDQ£‡Α―©Σή\’_ΜEΠFΡώ܏Ϊύ6ΔΣLl ησtσ{νZΣα#Έ |΄ΧO^λ7MΕukŠhsΨ‡ ΚυξΪ¬/s?F\Δ\lγ\œGn»±rœκσΙΕΤώφΥwmBΚ½΅νωŠy7θJl0έ¦.FqF\ηu±h3hψΈx¨ΓΕU"­»ϊτOλs|Δw,u~ ΚmαP6ƒζHp<ϋΪF {"οΖ:Λc^^Η\"2SŸ.ά°QGΆθ―ŽœŸp:£­υζσ4t[yΫΨ7Ϊ’-ΦΑGtοήΉLαΒ9ΪΦ:ΧΛ ζλ‘ζα{< CΐΊ“ΰ1pΜyИ―CΔG΄‹υ˜Hύ'ΑίAΖܜKN”ηs’I!Φ9_ΧΝΓ[ΡΗ–«#ΚΘV€9= sϋΓ ΰ"{zB{GρnβB{9,[ Ό“D—mνη©o>'ž,σ€W—O±¨ Υ‹ϋ‹z ΗR΄λ»ΰKΗ‹ƒΗ9PΞΙ;§›Β»˜eΉsœKψ"ξfρ€cͺݎΑνuδΓ6SυFy΄±L”xΚPgάMßaiψ56΄σtΦ½PͺH: n€χΑωΛα’έΪ¦DZw΅θŸΆy¨G»΅k0~k8h[Ψ›Ο!βAίΗΣ‹±kςuςΗCΨm{υθ/Η]\{ιR‘Ey@§€^,ͺ‹$ΓΣΩκ.²mΔE²―i”ΗbGz0unΆΑ`ΰ8¦‹Ί΄βΛΗ7”6ψhΉ xχχΐ‰9:š*¦β£€r#ψhχkΨ (9Δr±-•Yž/Ϊ ΰMPoŒAφSbƒͺ\άΜες(Ή>ηαKΡΰΩΤηJnˆί‡™φ ΕziH,ΧVŸ(―]ί ζ׎ωH£O΄υqcX―Dj;ύڐ„ΏΒΫ엁ώύ!8φnΰft }8΄=χ—υβxGΐ* ξΕνΟοΐ5°.τ/υ Ξ[qž«Α6ΰx­Νϋ‡Α―ΰ8*ςy NƒΑSέΕs“»p.π-0άΐ.άΙΰζρρΝ@Otϋ»–›Šu.°z NοrΆsρν±ΟWΐvΉ4Φ/ζ`}ά•œƒD{½Kz@)gΒΉ`ΰiΣϋπS0ˆΗΞοΔϊA†Έ«EYž:žύ<τ…ω†τδ}ΚσnZ7²ΆDσΆσΪω … αΠ~΅€>W΄πΐώ (ϊZ5$–;ncυyŸΠc[cΓF»΄ι 8|Ήx;Xηϊλ3S}’mβuΔΘρδ―ηρ'π³?87ΧΜωΗΑΩi^‘Ερ€NuaurΎ :λ\ˆχ`Gps=ήa ²a‹k λβΖ\³ΐ€u1 Η7Ν­`›\Όε‡ΔΒΐ:uͺ_;#p΄0˜ F₯{]RΌ ώ{ςΆ‘q°(Ά±½6κ m1΅¬1 nέϊφΖΪ†~SQ·φ―n*±LρωxΒΦΛɏ%ζfϋΥα―`;uθ‹…ωκEΦG›X§r_[―ν_‚kΑ±½σϋT‘νΖBϊΓkεcΑ'2ŸNLuΨ&Φ1Š*œp1: |Ε…ψ; Ρ…φΪςΦrΥΕφ5]`lΆ= FQGω榨^Β¦ϊ‚&d’Ί= VƒηhπRά βάάn6·sΞΏΈΦq–§Ά‘ό PŸzΥ―-φρ Φώ­α˜Ϊτ ό?PμcΫ°+ϊBΩ ΈτΕΪΠ\rIQ~θcνvND>ψ’γ$ˆ8ΘηοΛ€CαZp^ _%ΏO©Όrΰˆ––|γδOωpFl.²εΦΰβύ6ΦwΣΆ}>„Ύ` Έ9uΠd‰$3ζ‘m” Α £MΪaΰΊΙ Ξk]8œΗ‘ _,Χ_ƒα`x œγξ`ίΠ‘ {υ>)ΈN–;ζβΜ‡ζiŠbAur,ͺΑv:XηBΔ€w±Ψ<.ŒψšΝ6Γ0Θ]tƒΣΝ36ΔΎq8^s.¨ΊώJ>ΗτΪ;ϊq ΪfyC€›LάPΞ7τ}›ό›ΰ\?€3‘άq8oqΓK\Gj;±NϊΑyπ¨χίΰέ_ΫΫΈ&’MϊN›wΫXο2uώ‡A ¬Ϊ]ΎiτÊγ¨W›΄G;τSnΪrΌΞΙ:ηΌΑςM@ۍ‘˜‡φyμ ΆΡΏ•'΄€Δ‚κό|QcLΒ ί \ΜΨψ¦.Ύj;°έ+0άꍇυa\ >ŽVΓY° θ“ σα#ϋ4¦Ϋ;gΉhΫuε…₯kυΈ‘ΚΕ>»‚6l'n λ~ dΨΆ™†NŸ άhow87ΐ!Πυœ nς)`½e2|œ½¦Α°œ _}αΠηaΨv†MκΥ§Ϊn2|Μξ;BΜ‹l½ΈNSλ―?γX’}α—ΠREf/pΉρ`Ϋ·αvpΓβIHhsC>³.ζΧP»JY3y@'»  wš8\;ΐSά ΰ©-ΦŒήά<`°½ l: J~y°­ύΌ³:ŽcŸJωbkΣ―@›νmLbNω|S›>‚Έk9Q΄Ος˜›vz-Ϊ-JτuΞήΥ•~π3PΏΌ Ηƒ}Ύ/BΤ9ί! ¬ΪϊΥνΈΆDYΨ‘½Ž­½aϋϊδοΗP—Ύ)ί€5κKλr)χmψΣ6$JψΙρ΄cπ₯sr½Ώ_ΜΫΤ5·½sΆ船9ξW`w°½oΪ¦±ωPΥ:Eηή$ί ζ½#Ž…ΩΰbΣΑƒ ‚δtςn8O{ΫΨ/‚ΙΤδdp£Žl!ήiNο( IŒΡP]y™Ί Ί‘`ΊΒΧΚkΗΛΛb -S΄2p~Άυz&όήYΟΫ:†σύxηΏά_λOƒχ@;ζ€SˆcΞΣΐ>±’ΜT±οM0 |Τώ_Π¦Ζ€1_–·Ώ0+p|qL7°ΆΈ)£œl‘„ΤyλΣiπ „ΨήCk#1²Ÿ}Ϋ¦ea“k‹sρrqœγh0Λ 0ƒΪ;˜ς'Ψbƒ[©ν#©νλWBGž†ŽGΙhχ­ΰΖ »,Λα²u„˜? >,θbΏ8δ<°n†‘ΰυsxzΒνπC°ν㉄lα;ύ灑~Ϋ8^ΎρσΌuŽ«ΏοΆ¦'–R’%u―ΤΫ| =B’NΫ½!h·› +A?(k€7‹'ΐΓΒΆIΤ©ΏMŠ‹σy’XˆΥ±&dΠοuHθσ:ƈΊ6“κ€Ο“ΈΞI"`Ν»XΉψΨχ7˜ ƒ`œ ?7 ύ"ΈΜG™ω¨‹”’…ŠR }‘ΪtΝκ,sƒ\ \Χ@ŒMΆϋ+nς ‹ά'nΥω»QΟŸPnοπG€mί±<$LυΛ·Αϊ]ΰ-Έτί ?ΈI΄ΡΓΣrΫjŸ©(Φ›7|ςΪuŒϋζυΡ.tx(ߍ‰νB‡ω/ς‘Ϊίvφϋ΄7 ~–ςk’ώ ΎŠφ(κ ΙσQVI?#θ|Ε@5άTn7β…­Ό@ ^Ε;œΑ*nˆεa °½§ΏΊ `ε0@~+ƒmν·μUΚϋHμΨφΡ–²…DΰεΑςϋR]Ψo?ϋΗΑ NνΤ>Ηr£ϊzίΧΝލœη*%V"΅>l;”|ΜΝΉ(–=ΞQ.€Aq,νhλm·’ΞSαmPΧ›πMpβ“ΐ νΪ¦ί΄Υ§ ι »ƒ‡ŒŒr8¨_τƒ¨Λ±ΒGΪ`>όhDy΄΅ŸΎT_Δ„ώΜ‰CVߜ ΞεVP§Ά†ΐ§ƒγΑφϊ5t˜΄υΛ°¨§8εaي4§\¨ςΕvΒ`Έ‰7Q,Z€κΉ¦ΓΊΰBGπMu]΄:Δΐ@qLΝ>ΌύΙT¦ώ ‚aυR[$ϊ˜ζ«}ŽγΖvSΉ‰ Ύyπ#πκζ’8lo[e\ Ϊ.ƒAͺh‡Aο˜aΩFΕ6φ7—ύ”Νΰzˆ1ΌC„ΰ&ϋςΐ9|j`}p~ϊΥr}ώt Ηύ›)|v‡mΦ›·Ÿ~,χ%Eυ›ΧqΤγ!ψ? ύΏΕρE]‡Βs`½λ«7¦Ώ©6κΫΑΆύAa³Ί*ΜΘήptΊ‹«Έγ «!ά0Ά½Τ£Ž<ψΗΝb››a5P‡ŠQΈΪap)Έ‘ͺί|Œ‘««ν‘Σ1άЎγ»ιxςή•bsYξΥqεπ.h―›μP·βΈŠsŠ|QΠ„φν7uŽŽr$™Wΐq=€~Ϊνœϊ‚φj§›δ °v9GηšϋQΫΔϊ˜—Ύ 1h~TΗΟ`U]αKΫ:ž©φh!πg°όΏ@QγΉ©΅kpcλΗ]ΑΓΚqΚ±ŸO c@}ύAϋ΅M?9^EšΩ„:Ω…uαtΊ ¬Έγΐ;’θ‚±€.τρ0Ά…aCPWXϊ7(Sη}ΰέΧ`°ΏA©c—Α‘£n1Έ£­ύ$V}κΧ^Sν«ΑΰΫzƒ²+ά Ϊ'—‚υŠsuΚ’zΦ.4}rX¦―΄UιηAΨςωc@Ρ^7ΤΨœ[ψ‘l‘'|k™ϋh#Ϊ8WΗ Ÿ†?Mυ›νcMΌ} ˜7^†›@ΏŠε’ ΧΗλcα%Πfύ§ˆ£H΅Εyνκ\ ΄ΓώϊgiύŽŠŠ”{@§κ\l@±ψd‹…GκΰΖ‰`‹Es!Υα‚=ή‘ϊ€Α£ΞΠc{€νŸƒ9‚7-RuΔ¦Φ¦rbσG›ϊœYJ΅ιEˆ`VΏώŠsiΡή9C_ψ(_νυ°Rφm .&»F%IDAT―οΌ^œWΜogς£ χ…ώ _κCηεfΛύ™ϋPΕ†ΟSu8ŽqβF½΄α Pςyx}ΨVέ{ΒχΑφλAΔ’ciΏ©6ω°Ψn-ΠΖΚ€ZJςEΛ^q!ξ…νΐΗd+ˆ@ٟ2ο¦.²β¦‰Ν―ώ| Λ­χ .uΏΑΐΚƒΑΌΈa΅%θfS₯‘zλ (_n(Ά ]νΙο p hϋE0种ϊΓΆ{@SΔ»φαMiHuη’ϋΘ± |7ŽvϋΪ_ί;§ŸΐdΠwΣΰd?ΨΖφφ΅laδ>3/A¬Cψ?(W·›τYΠ†νAiΰ΅λlۘ‹©Ε˜J¬―λ>χ` κκPWŒAΆυ‹ΖΆuqB\X%8m3€Γ½ΰB{guc)‘Η4ςΦy§=Όƒ= ύAQ· œ@^n‰r}¬ž;`„Md‹qΛφ_μcpƒό΄ο―`ϋ=§Β$pΣψ˜š‹c4Eή ΡeMiH›κR;m,ν’ν±½sχ ϊ(WΑιΰάτύ₯|ψ0txθ7ρΪ±#όύΌΆνήπ Zβ~°·<Υζ(s ηΒ₯0ƒϊBb―s»£ΎM₯:Έ­‹ b>ζ”—»€€”§Β΅ NΆ~!cA- qΣ^n8}€qp8žϊoσ ©hƒΧήαwο”1F€Φ+P‡€w¬Wΐ;Μΰ˜#Α»ζ`pz0(ω½φ ‘%ΔΉμ ¦ΉΔψ‘:'ύ✢ƒαVψΨΖωι+ύi۟A/ΠΗnΌΠηxϊΞλπ§iŽuΊ}Ψ .ΎΠ^…Ζ|FU},„ύRφψδςΠ―1Ω"oΪζEGΆeqQr‰EŠςH§θπξΈ’hu^nΞDο„νΑΊhσF)οu¨ωεJϋˆ‡‘οκB°μ|x `;ΟƒΟΓηfp“L„84Τ_E‹-Ή uΦΆ oySλSq}v8Έω΄έƒwΔΞaδΟ‹°/ψ„ΰSDψZŸ­ JψΤΉ›WgΎ1ΥΏτDJU± άτο `ώaΨΎ ΆυN{hc%6^έUέΟ°αξΌ°‘ΌmΗCCzς.Φ?1Gϋε’_›?7τdpΎβft^3ΰ{πuΨނ߁γόΓtpΞnΔπi€Ž‘o.mR§ο?Œ+@±‡‚γšΟα²Έ6Ν%όΠ…ΒΐƒΔΓΫ νε:’½εiaθdΐΕφdwq»‚Rq1Ό3½ΐ»ŒΆ½ά΄€‹k™zD‘ΧkO{λ•7κ’ϊ7ΉKΚpL7²›1Ε@”•ΑΆ¦Φ+>Φk£ύδ,θJάνN$οψa“φΔ\Ι‡€FΞίωXo;ϋD0v&ίά’ξKC|BΡ~SνΠ'nΌ5JΧa—ύΛΒ~²…^ϋ½ ϊf œϊOΏ―ϊ5όά›Όu^o ί€θη‘’ξΠ―ΝJ¬o¬}¬”;'ύi?ηq$<κu-)ў―ΐH°n0Ψ/τΕPT‘ζς€NΝ7…Αgΐy‡P\ˆΑπWg±XˆuƒΤ`p‘]ΰXψH-ΐvAΗϋΊπn<ƒΑ>οD€m H1Pϋ€².ό΄AξΕ>Ž›Β²˜«γhsΜwyοŒφΙάωˆύ”HλΉ8Αj[7›φˆ>†άGqύ:8WEϋ΅ΕΎεX.1ΗXΛα”]α³±δ₯/ΈΎαλ­Θϋ4α:ΌŠua“6κ;Χ*ΦΧ4Φί±Ν[–ΟΙ5ήΎ>ύ} Œ%uΗΰA£kƒγδsε²υKSƒ€υΟδ σ`sq†Ωp)xm*y0F šΖ&²ήΰV’yuΤ€ΑΊ=ά›Γ9`;§Γπ"<οy Ύ]ΰF0X΄ΛΰR§δeκŒ:Η= Τm!ŸkήΞΌm›"‘Ϋ^˜„~ύ©n―ƒθηυΰ]œcΜ£‘”κzQ§›ΨΝϊκ; V„ΛA§€‡λ°>ΈI ž…!ΰ­ϋ*ϊH;Ό[›ζkΧΡ–κBΌφεί}°:<WƒλͺDϋπ]\ΧΥV~Άˆtr,¦›Τ@‰;²ΊΑΞΰγα*νΪ§²εΟ€ι]Α@·―›(°Lά ύΐΝwοόγ‰υb°©λ2˜‚ΏcΚ0 ”Πι˜qŠ \4(ύΘƒ)ζμ|ν§¦nυk―vš†ήΖ‚š& •S«= ‰vˆΊc,mΙ}dή9>ΪV ΪΤΠ).$[υκ}zυ³waŸ¦¬ο€›[ύΑΥ₯ΌͺcΕϊ˜ΖšEΆjΏ8NŒ©eΦΩΗΉ8ώ>πŸΔR-lKή5Wφ†-πJmU§βC]©ωδkX4n­?[πΦjoaŽnίΎCjί}ΕTΥ±Ka―§φϋ%f΄kŸΎL[7ζΙδ- e@εDp›zxψ˜g}ωBxβ΄ήΤ€τξ£/χƒαϋ`ΰ0>SθΚ7„e9\ϊcμθcyˆuk‚l5ˆ6‘7τQU<.—΅±¬1‰ƒ+―Οu…~ΗΝΛc|Σ]`8œ n}RήΗλλA†nΫF;˝ŸώuCN…SΐΝͺ?†½@±φ0bΪ?—ΓTτcψΉΌΈξΤ­8”ξ‘έ{+χNSΧX']»Ξ˜tΓ 1驁£/Φ™Ξ[m-ž‡›΅J©έ }RϋZΠFDg·a·―­IνϊM]ίy.ΝΨd›τε9UiΟ·»§—―J«tc;VσώΛΚκ?ίνεΤϋΓΙiφGS:+τN=¦½_<ΒεΑ‘ϋΑΕΟΫδmmη]b2μ‡ΐ όΌγΖΓVΰ]Ιώ€(Κbœ<}ˆϊ-ΐ±r΄+Υφ³ΰ%Ψά ‘Γ±ΔkΕpτ`\”Ές kϋΠ‘1ΆΊΜ{§TΜG»KΙο±Ήr;’ Υυ}l§δϊΥ'ϊΙε.°ήώΎΌ Gƒγœ“ΰλ0<,~ ―Ή}쫎\r{ςςh7wπi³N]i3{€Ά~“Χ4¦šA3SM·κԎ‰΅›Φ%ΥΌά5U=Λ(rTυ(]2y^ϊkSΝKΆδz+ω₯υΐ€M‹Ν—6™ΞZc—T»χ&©φ’>i.Ϝσy±]ϋB vEν}SυYύRΝθ©Ά/m7Ω²’TΥ9υδ©ΐΗ8οΚ9εeρ8ι££€°ΜΕ·ι<β0PΌ3y§ς`°ξ-°oOp³ά>ζΪFŽΫ.ŸSθŒωδ:“ν΅=ΧιoNV‚XΫν7ώύXζά•αpxˆY?π«Ίl6‡ήϊkβb…=‹'ΐͺυG₯[7Ψ>ΥώΊOšglc΅·O΅Κψ'y˞€ΞΨϋρΐ4o­‰΅QΕΏK덩_O†m§]λ΄ΜͺF₯­¦vNάσ4ηΠRηn,σTB©#Η±ζͺ+Nj>?ͺaΒαΆΓΩ¦τZτΑσ―₯‘έ¦₯φ3§€9΅΅υ]μέΝ{ηrygυr8ψψ9”ο4Sΐΰw#:¬bΏ5απWkuκq©[q<ρΊΌŒ’ΔώNΡT”Iΰ¦yv‚Π:c#xΗv,JΌγͺΫΝ’γΫΞ±ά¬a‹σ? ~ ½AΏY¦/rΏrYHθς"ςκ2―˜F?S–Α1Ά―ΒubϋM‘\ _‚aKPΧμqπ EuJ؟ΨόνΊυL{—–οΌ\zαΛΣάΓ&¦N“Ρά™–6^˜¨h?–Γ3g Os]5MyαΤgΨ©Γ„±Ÿz²Z˜ͺΟ΄ήŸι¨K0ΨΖ£O¦wJ§ώΟ}μ8Β‘ύ’V$£†Yvgaξ]5Νϋ馩cΧιiϋ)ΣΛΣήKs(ώΙ 0 ¨oΑ όώΌ“+ή΅r _šΊΡ½;½nΣTb¬<ΕΊϊ³l^²E†nm3―ΑΉπmπ pΌΠ™λ{™ςώ:Θ6(_¦τvψ0«΅Oΰ˜1v€Žg~0< n|_κΜ†ϋaxψD;²υϊ΄W„¨3U,·Ÿ‰›tπ.Ώ+ά Ξ1ڐ-Ζ4υ.―­Σa5ψQΚ»Ώ…ΕCLRΧεSη•ΦIέkWI/^pͺYσc^Λ3Ί,ŽhP':έή7ΝΉp!=rλNK΅Οί]Θ/ŽΊo«½­^6•NœΩ!{ύΨΤ~*KλΒ,‰Μ£o;ξϊϋνœΪΝ›”†Οx5MώψC§ΆΨ°Œώ8|Ό\ήζTπnfΠΊ φ-`&(αGΣΐC@SΑ@[|"πŽ305σή=7[ΐC ƒZQΏ6xν!ΰέρbψI)―žΠ«nmτ%Hl² Šϊ’o4ˆΉ˜Z/nθΠeΉω#ΰlθΞΡςόS―}-Ρ毁>Uς:―νγAβζχik-ΰ9ώsύΦ+φ‹Όc䒟¬ΣGΫ‚Oκscα"ψ θχξ”ζ]|oͺι>/΅― T,‰TcΙΈΎiξ₯CŒο Ή$ͺZ΄OΉΣ[t°ΕU>ŒΧPη§uguHΟάx7ю΅Kk°OΣ;¦Ϊ£FIwA©Ycΰ›π^ 7`<ψZΡ 0ο]ΙMUnŽΧQf0Ϊgoψ¬ [Αk›#ΧiήrƒΥΓ&κLsζΥm[w9ΤΎ–υ Q΄Q”Λ|θ4Ώ0ΙΗ³Iqά5α/J‹τNπΐŒ»oŒ©ϊB§©‡`Ž₯qνΤλz~ŽƒAxsωa#ΣΘ#_NƒF½“Ϊ‡γ¬[ρ)υ̍ΣΗΟχLΏ|ζžβ%δ¨k‘ΎαœQήJπ¦Κ%χέ,έ’ήωΛνpan%œξ›&=ύhZ‰ί,―€w±ΛΑ0˜#ΨΘ6pΦε>5uΓψΪΏΤοζΩž‚rέt΅γΗ΅i“ΛΒ67ΕΏΑ;θΫπ%y_σψdλυ›/mτPQςρœCΜE[N‡S  νΘ <θ%ζQwUχ3tx:cœhηu_ “ΰ πIΚ;΄~τ0S_WσΙΟ\wδMΫώβΓxνέ.έӎkožVΉ|,οξΣ’)ΚιΫ$Ye»)šv«jŸf?{g³ͺo’ k€3Z₯¬=&΅ίptϊΪV紐»²Uškσ;Yy·x’X%υ"{5A°Εƒΰ:pCIω₯h±Δΐ5–Τε]§<7ΑΞΠ B LŠλ’Sf?Ί‘Τοc²ω7ˆ:B›GXΤ:»ωοmΙυδϊΞ‘›?δL2Ξρq°/K’½©ck‡Dή4oΓe!Ξ‡•)ή‡ΈŠΤLΏΈω}ia½4EςΆΡG}Opo,Χ#»ήΖιS±ή7ρ’aShJ›wρΤNo§Y]j1΅όϊ°)}>Λ6‹ ŒΟ–Ζz‰7MxTΏψΨgΣr\²fϋϋ€ωŒLλuνQάmΌγ»™-ƒ<ί+l―Žα%=^»1{‚wΆ‡αbΨB·π\πΠΙ7—EΠ8yπΈωγ€²|¨ΓC,Δr7ͺw'κΚSΫm™ϋvUκΤ5ΒΉV=F@ΙXƒΚΰs‹σίPπΠδSP»Khε£kΎ ΜλsοrgΒ·Α@?W=‘Λ4/‹Ί (χqΫλ8XΤ»3ά„sρZτyJ“EŠ:7 xΤγΈCΐΧύΧ€cωθΧΆy»ΑgΑ>’ψ~ΖΡπψl ʌΊ€ώη¬ϊ\3df~ˆa]AΌžjˆΉπe3h^P…Ξθ;3ΥΎΫ5­Εϋo/X»l―Zεΐ'ζφϋBφΥ—SΝώ:faξυywλΙό}nτηι3κ?w1π› kgπ+±"ο΅›ιKΰζςnοκŠcΗ΅νw…;ΚΪ9†ΗφuΣ< !ΦΩ?RοΞ‘Ο6–Η8^+–I§Rj{cΕ;ςtPβ‰a#ς!tζσ΄]ˆυφUηςν΄YYάΰΦ½ρ~DCΆQ½€¨³)σ)ΪςΜ_;mJjΧsΞb}¬€)γ,Πf.žδ‰Ά¦Ά[Z₯ͺΊήO ΄YV­ςΠ|’oΰΚ,ŒQΨRβ-5BŽΟ{cΫp?hpqΚ_7fŽνά ILΕΤGv‡ŸΒnΰζχ₯!ΪΊ!ά8±ΉΘ_mfΫΫZοΣ†2^‚υJ)I!‘#Ώ;[±:Ό a».ΖtNa‹iŒεAΰέ\ϋnηcΏΠm)*$t¨ΧΆŠ±·2Œήp<‘#ζCQΛ@ώ˜μ0ŒΘacΛ VZ[~œ΅θh‹Vήj~U7u&ΦEΤ.z*‹ίΒ΅˜Ζ†§ƒξΊ»Ψˆ‹―€yzl‰}·€u ΦEuwΓΨnpK­νμcλΝ{˜F{S ‘ƒ’BB—©mBg\Χ΅ͺ+?Œ‹―‚‡‡‡„yππŽ}rvGYO…p|μv9~sKŒ]θνΉZκΉsΪ’— ‚Οˆ΅ŒψΙΐΉ™Υ¦Ισ;|6MSgNnκ؍ΆγO.;.WžšΈBšο―μZJάUΈίVύ;=Ϋmω"ΰ­₯Ρηb°»A5Γ;ίP>Β»‘β½ [7έΕGύ Κl£ΎΰSΔΰ†ΊΕqb,ΗvŽ‘ζσ Ϋ"₯Yύ¦tˆϊΎJ8ŽυΣ’€ξρ ±,p>2―”zpœΎό9 <8μγaψ€ΠάψΔRΐ―~g-ΏZšŽS.}z%ώΌΌ₯v?ϊZ‰Χνxͺ}'΄ΰH ΄˜βB·:αΔ¬ν5'½8~ΕΤ΅³aΡBbDΎΓΓμmO§™ό& GZ`Žc˜Fή  |ύλo#¬7PݴʖА}εΑ4vί7ι‘АDΣΘ7ΤΞ2‡–*£½Άψ2eˆώŽη{εb½š©φ«Ο;ω°| Όϋ{0D²…xέά¨ΈΠιί€ΌφhšχΨ?u·τ«; ‹Q[ΰ‡˜Δqώψ½ι΅g[Ω―[εPS›ζOγWh―r?[©‘°o†E2 ήμžjzΝ*6^hlξ€kHŸcY˜z—t–q‡t“(n Ε»όοΐ;|H΄‰4ΖΉˆΎ―π.όΤݐ¬Fαž₯Šά–ςΆΪu δm|ΤΧ¦‚㢍<ΩBΌƒ?φw~ύΰ/°μΏ?ΕώJΜ₯ξͺyΖ(Ρ.lwσ;žO Ύζχε‡mΔΊhGΆυΘ³ws@ΞOο°ρW?aDjk4o9,5nb‚‹²Φ―‘3Ζ~±AšsΡzι#βΆέrΣόΦϊm@ΞΗhΥ’σ†ŽIUϏM]n[3=ύυiήlŽuvSΫuaQ^η3{α―d:¦SŸψGρ'²­uή±AL_œgΰπŽ1jΈΆΚMπίΊ£uζσΑΗφ(³άCapΌώΰ!’δ:κJκτΫξ«ΰ'ϋ^_RDœΕψ΅>yξξ4gω9i2ο ΅»v­τΟGρ+ŒURu/¬φ~$βΞΨ3οα©ΆmΖ‘ζωŸΓ^9]AάΌ//ωΓΓV+.b›ΎπΔ)ΣΟw~+ΝγΛA«†ΟHνό΄_ξ‚(x½" B’[1ΝγΙ‘γΛΛ§Ιόiζνη§ρOή[Ό6-ΪΆβ±6¦β&ς‘ώ8ώ n4ήΗίΐd‹Ά¦Š}Ξ…ƒΑ;Ά›1ξΒx¨Hm+ŠiŒλυ«ΰ~}A·*φ‹Ύ‘Wχ7ΐκYπuE=ΩΆ#Δڎs;€ζΣB›νςVJON³VŸ™:v©ζBώ!^Γ―’ηρ:ΏΫέxfΥYι:^¦~­]§ταγw~nυ“ kυ††Γ·OUγοIΥ›o—Nΰ΅Ϊ)|‚«ΧP~Y6„‡SώΊo Σnr§Tυ\O>θΑ[`Lπ±Ug¦SΊ?έΔ―ϋͺžkε'rΜ³”ζλΤ;ςΞΰφ~ ω!o\σφϋ~ Ώ7°U‰4ϊDϋόZύ…½ΑρBbSΗ! ή“αψŒυ(Ρ¦ξͺόΜοΰ[n“Ÿ5m9§CڊτϊE2ήΩίβ εϋΩψχίχ@ρM"Ζ:cϊ’MH,P›067’ο μ<GΆΑ»€Ξ}¦ΏfΞ_]­L΄Ww¨IοΩO<ψ`έίΒϋ{~enήΏεcά̊Oή]‚—amθΆΛα²ΨδnH?ΰAα#Ό?ί”Ήώ<οxO@?X¬Λ7~θ0=Όϋ φƒΠm(j»2tϋΤω½iξ‡·,ΰ·΄>oς©Βvώk[œ],R[΄½°™SΊ]ΈσWΉς£†Sz>Ύ˜?α¬ΝNτ“ •½™Ο«0VλυC@Ά80ή =n…ς€’’ŸiτS‡Ζ}p\nζυψβ—pψδ[ ΕΆ©x βfς€ˍιΛι ~6 6%ΩβNν―έxT +‘φ‚Ιΐ ₯"o½wτΫη}lγέάΗYNJ>2Ύ—ΰΣ„eΎxh¬JcλoŸΑρΰAΰ†ΆΏ‰βr ‡½ΰˆΧΐ•͏3ڊ4mΕώΛvΖkθ¦tΎƒaΌ 3Α m;οθΑκ0Ÿ?NiΟΆο |;Nβ=”Ϊω|uΥάΩ©CMuρήΒ‡΄Š€›[]OΒ ΎΑXΩό8‘-JO[΄½bσ'O£Έ9݌OΓϊ°;ΈY}°Ξ;ψ|ΖγlφΝωVάώk­–ΦνΪ5 ζθΑ§3ζΜM―=RΔ!ΰοςWιΐ7Ωp(ψ+/7Ώο7L_ Χΐ:₯rη―σC›ϊY9ΪΤr5hl¬aρ$p7­w€cΰ:¨α ;π] ίY}νtb§iŸ‘θ²ΞTnε3ΨΩΥ©†ίq·ηcιΉ•91ΨΞ]怫ίŸŽhjρO9ϊ Γ§ _fxΠψ£²ρqBE*X–ˆ»Ώ›_όœ€rΜζŽ†Γ·IΏο³Gͺύφ€T{7πς&›—λΥ>Δ·γ>Ψ‘.}†λ·αώ\ϊτ5Σμώ_H΅lWόκp>zb³ϋFb8ͺHυ@ά=Ϊ¨ω³3Δ†ŒΓ X[6ν9<Ζ³ζθΤeΧ7œο>Ÿ:ΏΟ‹_,μ{πό¬»τεEΓΕ<-\±Qκ0΄OZηρΫ›“^KsωŠJΕEεGΫσ@$mΟμŠΕxΐυ Ϊ/Χ3Uυ˜ϊΤ–&žσ`ͺ0­¨k€kΓΕ*σo,fσ ΓCGρΛκ΄+ʝγ嗀@Γ+₯mΒή*ςωρ€wd)~eχρΎCa₯4ρΒ{ψ½ίτΕίόΊEe>)tβ­Ύkξβ θn7Ώψ«@«+Ζ=P9Ϊψ6`Ύ{6 αoΪΧί:Νϊργ|ίt¬{do yӊόM*ώέ}ŁΏ ¨Θηΐ•ΰs°ˆ L‘Ά]Χthoώέ >Τ\Zέ—όΆ νόfšΟί`ψ ΑŠ΄qΈ¦ωœy`½νS~P}Υ=|)_ ¬π < μ>†?$˜“ͺεΫ›?gξϋ?5ΚΐηpΉkjΚ|αιόiœ-!“‰šο§κ‹@ΤCTt~F¨Ÿ‘£?ΛaΪU₯λΏω\jΏ΄―ϋ³Ω©pΘ‹Ε]ΩX›JyΫπ@εhλ΄XVς‘ή-MM| ε€7Ÿτ·l_|€ εͺhnQT€uοg―|Ν1©³}/?€ΫΒβΠΎ¦ψδa TQίR¨-εΩe€Χ―RόC–φ~ρΑώΎ %4Zr έx r|ΞΒ`όέώUoέΏδiΙ©yΐ8Ώ\ΚO΄€•έ‹ς@εX”‡Ϊ`=ߏ˜ψΛΏŠέΏxz\ύŸ·θxε-γΚΠ2~]¦Zω―Κ?y°7_ŠΪ‚ηot/ώφΛt’•Α—Ϊ-"Km[EΑRx Ξ©ϊ―wρΟιΝ,ώγΛγF¦jώiΖ@ώ₯Φ›Ν¬Ύ’ξ3τ@ε ΰ3tφg9όtζ›~oo H7ž.ͺψ”!_P‘ΆμΚΠ–Wo!Άσ/ΧΑ–i›΅9ΕGΖ£·IsΈϋοσκ]•7›Σ·ΛBWεX^ Ζδ«Ώή©ͺI7œΌ Ώ²™•τΧ©fVUz{N»tγg0Κ-μΚ{-μΰe­~ΨφιΕ1ο€G½ͺψΧiK,ώοΎ>©ζ·CS{ώ‹ξRhZb*[ΐ•'€pjkR9αž4d\Ÿτάι₯YΛσ$ΰΏΈ^ρ7 +ΣοκΑiφ₯λ¦Yn~Ύ#°r,Ž[qΫΕ ‡V<“Šiz`όΈ4ό…žιέvβvχLΥέK/ όίφε;ΩkΏLό³_Ύ%xώ^£SΊ}4–€Ω}/€vOίΫΏ[hΤόJE z |ύ[p¨ŠκeιυwζowͺSοY έη₯Ν·{/Υμψvͺ]zjΟ§ωŠ· ?αχJΧ4lίT{Χκ©κ½nι½Uf§]ŸWόe9…ΚΨ-ΰΚΠNm­*‡νΪs―Ωxϋ4 cu:„Ν½σŒŽiDοYl|ΎΦ£šο˜Μ·ώσgΔVŸ™nŸΧ>ύ‰ύͺθΗ9Q‘Š*hσXoLjΏα¨…½Π&£RΎφ«…ΎR€Ν»°2Š>_2ϊSo|Ύ&X™MΕTΫςy+ͺ>nDŠˆ¨#Δ @¬hjτ@w<0iΊ Σ |…Ζσ·ΆiΩί§έΪ΄Zγi+-7νi³£4θk|Aγ½T―χέ¦{ό‡mΆΖχίgΉ,/=ΰ—¬π{hπwιa@Γ΅pΡcf«[’gZ5/˜6bœMk-ΨΟΦ-,+oϋύΊaZ%I“–ώμ|π^ΛkG M―½}ΚL;­’ ΡΡ5ၸP‡!=Π΅ŽΡ·ύ5˜ž―όόž·j¦ύTΎΰΧuξύΫG“οLΤW‚Ov(n.Ά›~=ΐ:ζΗoμŸkHy ѡζέσoZxƒnσνΛλu'ύ-σKψ驏ϋμ­φnPοςΟ™υk³’&-ΣrΝ6ΑΚ¬ύς’Βήjf,7z n='u{θ’α‹ζι‡}ττΕΊ·~Τ€΅Φ1m…ΎΎWŸπβΐΝκaήρ ετ,@‡~‚ψη3=ϋƒεδν¬iΔ ±¦O4ξ`χΐ‘η–VΛΪυ²Vό­―zT?σ«χΨλ…&¬7{Ξ*+Κώ‘zΰ’γ-€z9tΡΞάqp€ΰΨΌϊφΐβλτvΎ²3X―Φ5΅N+θ:j―&φΦΟŸΆΒu ƒ!|W™M8 ='τፍ;< "έCo:~•5 f­3Βdύ±υκΠ[›4eΊYgMˆζFΰηΎ#EDΤ Κ_ύkΡ½τ“ΨφΧΟϊΪΆ΄sg&ρΦΒh*Κ/νgCœͺ<‹w–/ΖGDμ]Δ€½λίXzτΐn{@»η\ŸCΕOhŸΙϊΊ~{^±[Ԑ3τπ"?U,zτvCΆλ#EΤΏβXΗ0Άΰυ@ƒ–ΚΊχ˜~Ω―aΚͺϊn$;όX‘~²˜gN‘5Ε|œΤχQΦΧ»β ή`΄€τ€~μ'―ŸΡmΣhy"ŸΦ Υ-ΥN@=;GlN>D&Oi9=ϋŸz<˜ΡζΖρ<`elȁ䁼_™E›Βΰ9N―΅±#P§ΔoπζβΘMVμΘΫЍƒ“†Τq‹κτ@D³£x7¨’==°Ώ=ΠΆ΅όώΞσK΄όg­η“Ν‹ϊ* νΆDŸ2¨ηΖμο$Φ=Πˆ€pb,"z §=°msi¬ΧΧσ†π½>§[ΣίύοNϋ™τQ;hΟ†!6€‘#™Σt'kL==°<'{Α©±Θθ=τ@NΛd_·sΣό@Y-3ψCϊ@Pǁ¦R‹βίθϊσ@œΤί1‹ΨH†ΘB2μ'[ώkΈHυ-}ΝΊ&Ά‘܎U‹m΅žs¨ƒ_4¨k—Gγ£Ίτ@œtιž=°O=ΰ«~kέRΎ`ΆŠ~Χ©η9ΝΊfΛӞQ“υ,@±›³OOŒXYτΐήπ@œμ ―Ζ2£vέ>ψΓs[ΦY±©·5jˆΌ·YOΝ=<ΠL‹ηΊ&^c|ͺo2‘Y¦g˜απ<`€θθύδ8ΨOŽΥFπΑίUΉkτ€όhkκ(Ψόςίβώ₯ €ίCχ„υΒiΓR΅AΏ͟?Ητ|cά¨—γν<0='ζq­ͺ„ƒ²ϊΗτ’~A―U―Άo΅ΥΝΆqή›o•žχιλ‘zΙθΫG˜΅h ·Ρ†|“©•‘’’φ—β`y>Φ=μ‚'nπΏίΊΑ kžΆUσο%Υ}C΄ P§€ΩΫ+κ‘[„άƒŽ;‰7βŸθύδ8ΨOŽΥτWώ8# 'ςζ΅V\½Τ6hΜό{£ώΏj¦gλŽxθoξύ―k1=χg+Ϊ;lAˆ\§6Χ]»’ΑΡυξ8¨χ#ν―Gψ`wΠ—αyn’‹šΆm³Ÿˆ·/dm+κp@'s‡Vό &3w,ΊΙ–K•+―½ΝREŠˆΨ—ˆ€}ινXWτΐφ•>δ`ΘK1š4ΆXΣ£·ΩZ:/YήΧrστ,@i^ΰIj›σ±ŸΪώ?$Ήίί ‡ΏXμ>@ΚA’(FDμ-Δ ΐήςl,7z`G€Ή0Œμaxrmv΄&ΊΎz π-z€ρΏ&XΫό@P­žώΏkΈΩ’Φ¨[,ΊΡf;x$ l«7ΕuŽ~ŸΝίͺ;Ϊ+V§ύΰή~G==°<wφ‚Sc‘Ρe„™Λ!OΛ„]η“sΒy šΕ΅Λmι16&—·γ°ΒΩOYŽŸ φ„΅βuζ%|Άψ&½΅π‡Ρϊ|AΞΪ[7Ω«žYbkτ‰cΘۘ‚p¨eOyτ@τ@z ΦϊŽlZ,*z`Ώz ΐ\†wοi]ξ΅πF{›ΖΦ• Yα{ǘυ׺֞`ΫΩήf_Ÿlm…Ό5··ΫeάiiβΒ‚Γϋo[Θ½pˆΈHΡΡ{Ι~1ξ₯βc±Ρ₯Λeη8ΩΓi9Œχ4蠜žθ»ϊ1;΅w»5ΞevυQšΤΠχτXωoj2{ΟιϊfAQί-j·/λ΅ΏŸφhύ‚Ÿ8φ~'έvo―σ€ΝIΛγŸθθχ@ΌΠγ.δΘΌ|  9nςp({ώΜA²m«6―·ΦΎ#μφή φκωƒ­Mο6œ¨ίΨ’«™ξμbΥΟΰΏFΟχμd+΄ζυc³Ή ηΪ› ³ύΆΣo[hb¨λŽζrτ@τΐx Nφΐy1kτ@ΚY:ΧwGfΰο2Zσ„=άοP»½₯Ρ^ϋΰk[άΟf=mΖΗvφυ$€Α?ωΑ’f>Ε ΪΘ vλ‚ΉvŽΪ _ώ §%ξ‹Πu‘Ξeηž.v}δΡΡ»ι8ΨMΗΕlΡ)„”Λ!GN‡)’»zO›π&} hΥγΆ€©ΩžξΧΟΞͺŸε6Τμ9«ΜjιέR½22μ bTηMκ™«ΫqBςc?Ήφ’]§•ω΅Ϋ6V~π'mNWasξζ§ΓχΫaŸY4Χ.λΥΟϊmZό€aV•ψr^ u{œσ¬4‹°R΅κ!C»_ΟπυΑεύ¬ms£5υΦ3ύΊ ρΓ-μκΗώn(ι ' Ε}HΈJΐFΒακέ吻¬€IZΓς΄L8Rτ@τ@7<ΰT7’Ζ$ΡΡe„ΧM8»>­ υQ-Ζ•«κ4Q˜%₯>c&0¨2V/ΈPϊƞΛ†F­ψ5Pke~ΜΠΡφq=ŒχRέ“O bUΟΧ‘α[ΝΖlΠB« ™olΦ€ΏQ›χOi₯ΏTyx«€Χϋ’7 Δυ“ήηιSKνck—Ψ&ύZaCϋΆd€ή¬"oNxεO9νcΒίx˜Š©ΛπP&AZ—οyœ‡#ˆΨ‰Ό#ΪI²==Pφ@xΝ {8δΥdŠγΚEVtYaΆ>ΒYΒ‰Βϋt ͺ·  Lt!βŠωkΡmimφ]…‡5υ²†1Ο±οΥbGΛ€)ΒȎΌ^ΐ}‚1Ψ3θ7ς ‘يMl Ύ=Π[οςητΠαEJς›ΎmˆΎπWάΆ©²ΕΟvJαgΒ+έpHlΌRό »n£Δ{ΧaΏΛΞC]({|’A<ΞΓ‘GDtαςeίEŠ==€Χ aΧ…Όš–αiΠAι°λXE3Π?O`πNΑΣς -;N €€4#„· ο {„η Gœ`ƒ4!θΧΨ`½•xpCή(ΗpΙ ΚΈIΓτΪφ{¦­ΝZu_σ“ μZυƒBDΧ54ΩΛ5± κ n.ΨW…·Ϊ[°fα‡ΒeB/Αν σfΙƒ§ε0¬θJ|Z&)z z ŠΈ`#EDtνπ:q9δ‘LI„³tΙΣ†:9mΔΫ aΊπ/·μyΊώOΒσ}z's@e₯}¦πuα(‚σ„Ώjΰ/θsΒ9mέηΈUSΝZΕ··‚ξιη›-ίΠKhΠΦΎΎ1Ψ· Άn΄αŠϋ«ςλ%Γ„NίΕ«ό΄νLZτ]BϋŒπAvζ'ΒΫς@>ˆ—BΓ.ΟσxΩ‘GDdx€‹5Rτ@τ@u„œΛ!OΛ„C%{8-ΙΣ1€ιΡ»δήώ‹Εί(0p2ψ_-\,ŸL}ΰcbπ α…; „Ϊ νΦ±uƒ7λΣΌϊ™αΒζ΅VhΣ=|έ.HFZ ϊΆmC’ο`{_q}y­ύI‚>υ“΄…zώ €ονK•LHxπ‡Χ  zΪ Ω}8^ό&2ήV‰ …a—ΣΌ;iIγω<}δΡΡˆJ†S’*z μπϊp9δΥd²‡qξPΧy8‹³ZgεΆπ:α=ƒν—„χ άO‡|ΐe[έ7 L  L( σ•4νΕN·{ΨQ',(Ÿ­ό Ÿ„HL(,›[μTόFπΫ“L›xhΡoHL(Μ‹ΒΓ!GN‡³†:δHΡΡ)pqGŠˆΨΡ>γrΘ«ΙYιw,=[Γ€8X8_x­ΐ@‰ŽϋSΒ{Vא‚‘Μ@Ν½‘“έmΒwΏΦΓ|Rο@§pv Ώό‘Ο k©ϊ&*f < qϋΰ: μu?Jμ$‡αtΓC9Μ“– GŠˆ<ΰB ŠbτΐAοTp„Λ!OΛ„³:σ0 ¦A:ϊ‘ΒιΒ›Vο¬ΆΉFYΝB`rΐφ9ωςαŠ`ΥΝκœAχg[ψ„=-ΌΥ)alλ'|ˆ€ˆ‰ΕΕΒ!qaY Vˆέκύ«pZYΛΚ8aΆ0V ,·KbΕίȐϋ-ΛΏ‘ΞΣΊ.ΙδχpδΡΡeΔ @<’:{ΐ΄.‡ΪΙ-6‘+?z|W‘, yŽ2H1ψ,œ#|D&0!`Πgk|•–ʊځ({Έΐ`ϊ&˜Ÿoφσπρι2άEUΘΣΐ]φHΧ9g°ξ%Μ°IΛχVς]QX―οlόLF§ LdόVΐ-’)ΟΣIμd—Ϋ’₯G…νpΩy:>ΙDLˆ€ƒιhΗΆΊͺ θAΈš eς{ސ‡2iBςAΞΗ@=]ψ¨ΐjšΌ Qί—ΈC=θͺΡŠ Ό‘eΌ[ΈKΰϊ¦^‡ΔͺΆ!-vP«wlΎL`"\/<#x^‰;%r›γ„ΎiΨaΰŸ+x½;Ι„C λΝ’³tδυayQŽ8ΰ='ό!Ž <@gο~Θ‘} 'y:‡³8髃%DΩ l¬ό_*Ό_`uΞυw“pvYφ‡ΰ¨Η)”]ηœr)ƒUσ²:΅ΐΐJyέ%―'δΘa˜²¨Ϊ(ŒΈ@]Ο ¬Ϊ=½Δn~aς;{όΉgBσΏ» LžΌl·+V’JdΘΣΈL˜γβe„ϊ0-ϊH=ΰΙηXnΨ8ˏgΉα`¬Β,wˆ¦­Ο<ΦΔ"vΫρ„ίmΧυlΖ)ΊHτ‹kϊS*7ωyVu΅ O:«ž­μΰ*-λw]u‘ŒΧα,O–`εΈ‘‡-ωs„σ„w ’θ(Ό@`…ε{Έ»γ_VΙγ„Ο L O WΤΑ  ₯m uΘa=.Γ€ρί9eOψζ?“ΪΘV>d§tέNΗS.qL^ώ³,S?Οp;£ŸΠΥ„ΖΛMs|δD\ο2ρ‘¦w9ςnz`κtΛςϊ‘)?EŽ ~Sbών;ϊχθσυγSmϊυiy^‰‹σfwΊέΣΝΪb²=ρ3οHϋΠ“g%'}ξώ›:Ÿμ--·e5ι·ΨYνπϋμm½’Ž”R'šrεΧ?iΕeσvΌ¨:%vΜ”6XαΎ*?·kb°9·ΝVσϊΔtQŸ”Ξ%ΐςŠhBʟWšβ‚’cθκΘ{ΨŒHϋΐSfZSC‡ξΏ±΄*Σ,y|GΞf¨=NՏׁͺSϋ©ά_εΈ΄κο6υFλ„₯κHwΛό9v«8”›pš5oXmm+ŠIΙ%;όMŸίaΩΓY²ΗQ¨ΛΞΣ…ƒ†Λ€eP{žπαUΒVΑρ*α_Ž5iH됸ΣϊHΓΰ8^x“πrψ&qJVδΆΈ=iž$HύΑ(δn—λP!Β¬τΩΑΰ>m£žkθ_–Ε2λw[ˆ‡<μu1‘a·δΟ«~Ϊσ pfYζAΗjδeeρP—%S¦λ½όtΨυ/η(Ι+Î΄άΠαΦ΄θŽ S¦ΫΙΠ/i(Ϊ‰ΪΙ«3δ°6-€™[²Pφ;@gSAΧ@Ξ–I\¬…ΟmJώνgΫ2’NšaΝ ζtΪQ*—YOx€CiyΰθσμΘ†φδƒ/oΤΉΟJ†^TbΉΗ•€{TŸΣ·Υ;μ“[ΦΫM+–X‡~Ϊ•$aVŠ]·Ϋ‘¨ΪD~Vϊ' Ώ† μlN– L’Β2½N©+ϊj6€υž‡2ΒΙE˜Ž4%iTΞq―^«ώ†g[λΈΣmhο^φOκ’>Ί₯Ρϊ+ͺ]ƒ}ρ¨uΦxάjΛ½V_¦ ΟΙ@Ωs8(orαφΦk%ƒΜξb…GZϋcύ¬IύdN}άΓzžΰΣν½ν'‹~o“fY^“?>ΚiO<ΐqˆΤΓΠ}σ·vmϋΏKέΠΤKΊQ]Υ­xΑ2νω―²ά]€“ί/ŽtOΓΑ‘‡υ^vƒ―ΧΠ²LwmΕΫ‡[†’mT—Ϋdo˜w§m<ʚΦ,?¨/’τyναwGφ3ΓΣzΞαrrΞjυ\α]•ΊΑdBπJρ_ Ύšυ2αY²Τ=rHΤ3Yψ©0Zΰ4š)ά)p:yI:·Mb'™p…ΆοφΉžΣς0ƒ0―πέ(Œ Άοο°Γλy(+IB#ΰeΓ?Ζ s„QΔ„κα !¬CΑ …εΉ w0-»yΎRhΗ°λ$ξΎO·©8r’5?ΉΐΆsΆ]֐·)αατGΣ—[ρEK-w˜žd¨ώˆΒI¨[œ Ċ.tnTΆ­V«ΜΗuι—γ¬xPυ•švδu 4gošƒέ:yΊ5ϟo TΉBΦqΩƒβbV<0i¦M”c’-­1œΫFmΆ¦Χ>¬'΅ViU―.ŒdP―€nΈŽδ~­·²g4”\s΄Ω}C­ΐ–š.’|‘Γ.~ςϋυϊεΦήΠdωΊΟikή±€ΡT;C}–ŒΞυ]Ι;k¦ϋNΏΗκϋωΒ[…S§J˜Sx‚ΥlγΚΩ*ΜΰχJσ―ŠyΒΩ+f?βΤΦ…ŒΐoHΜ€Π.Έ\ο6Έž:―Ψα€˜θ\*¨+OΖ±ͺƒmh#ι²{5$$>œ(ξ“ͺi’Ωq`βΆIL(,Χexwd πtIaaΧθ<—ΧτRύJaΤT0pΈ]£ωKΤ―΅Ϋ`οΎ_[3:λθ8ο1q+κ£3υM-Ώ1Υlu‹΅©θ&Uςν°λ©<Ύhz Θƒ³ˆ gXξ‘[u?l¦}P·|fΊΎ)«­πβΗ-?}eiΙΒEΒNO&*k€° Ÿ^–kΕΉ‡YGŸŽδٚ_l^o—?φ7[™Λ+L—ίΉC MP …Ί²ͺζ˜ΫšeXΪ~Β€<.“Α+Τy>ηιxς8yύ!'[ξΪΐLVώtN R Š \˜xΎΠ©+§„λέ– ŠΣM d υΌΤu‚𠁝θCΒκ#§u™ςΗ ηΧΙ"M<$―Χui{Β°Λμ>ΠVΚΎC@Ο€dœ€ύΨλφHL& πP—Ά“2ΠΑ!8g1“€_ όLv¨‡^ΤUΤ%–ηυ²QzΥt€‘―Ÿ°§ Λ γIyΩ₯PνύuϋΣ–y[Bξiнϊ[ΓΦ Άuβ6­©Ε~ΥΪ`#΄oΕbkœρ΄ϊ5εbΐί+uNΧ¬£aΏcφ³ρΦ*'7+|oGƒΏπ:[9y†ευ`4Η,nxίFΪCπ„£ξΝ·oΆ?θα—σu‘ΨλΠεzΜ[έlΈΆ‡Uefgϋ¬]Gr‰¦ωγ­ΨΦ`9Υω¬Vg¬ZfOyB― wM΅ήaύΞΞΧtΌ‡C^MΛχ4Υ<†―ά_t>#„ιΒΒαΔ“μ'  L*œΒ²‘Δ‡q όΘ‘ΐ’,Βί¨[―¦5Rς>ŸΌΈmΝ€}³ψΧμeA΄Ή-!wΩΫCX3ΨTxΉ1P#σ¬Bθ―j²’U|Šμδu:gΠ‰pΎ'SYΗ$ {HΆ]Α„\WΝ†jρžίΉ§σp½sχm§vτκ§χωυ ί‘§ΨσμOμ>N\gφ^­ϊϋkΚGŸ³―ˆ7τμ“}ςΔmPΥ»IΥOΡۏο+ΔzόB>ΫΆΟΪ$'6kπΏEΔωΊ'_ό¨6%uO,YŠονΑŸFzGκ‰Ϋ―έj9=`ˆnPC£=4h”MΡΰߝ.Χ4B₯γj1Ϊ‹ά•œΫΔ§y¨KΛ]•G\H”Λ`3TΈPψΑ­L]Uς*ιœΒ2ΒΊˆχ8Χλ(&ŸΫ}BœU6Δͺύ•?ε²β°¬,‹%–ε:_#Œt¦dήCυΊΣω]―l;‘ά]8]``ώœ΅ °+ΰrX¦—r%Ν<žθCb*ϋαe%™ο οx@Π}–½3ߐΖy(‡:τΠΞΚ«·ψΪD±u£m{’]˜o΄?ιΙύΦYΛυΕ©»ΥΩι,ά—ƒ?ΖQߝUί½9ΩYε–›3£ςφ Ι"ν’ό„ήΕl1Ή{ΰ„iΦ»΅ΩξΠ€{웬γύχZΓ!κ’ΆͺΫΨΫb^oχeI“„oO2›3ΚΪυžmcΫ6;σ‘[’{₯dσcξΙ³Šͺ'·ΗmφpΘi+9„>Œs]Θέ7žŽ8Χ!C r£… xŽΑn‘πbαq{ΰPXŽΛπjr’IV Ψν«hδiΒΟμ‘όΓv˜ s;KUΡS6Ί/ˆ ‰<‘MΔyΨeΒ^·:~#°"ΏB`~­€O / ψ†έ ·'δ.ϋ$0λT°Bggχβ*α­‚Φ£Ι.ΔΧΔί)ΰ&Yδufρ΄.΄ΛΛς4†»ξ6Ί.L·7d―/,;΄}Vš0}:Mbϋ„3mz―fϋΓΆk}υ#ΦόͺΗτύgyžwχχ± ‘οΨW¦θύΠ‘Ϊam·υšŽΧΕ†―οrGk™\ΣυvηΔ¨ιμ/γτe«†|AΩ5Ψο΄ή Wή©PgγΎΊς»j{‹Œψ’.’ΫIR΅ι[G­yΚΦιA.§]1uWzω=ΑwvކρY2:ΧοŒc―§qΫΓv»¬£œ¬ng‰_)0GήΛ…ο M‚§—˜ρ^~ΘC™„N2鏗έ_2; ―*G0x½ΐ‹Mi½^ηIΔnόq;²8:ΐ.„ΧΙΔŸό·€ό¨pžΐnF5ϋB]vl yύ\†s.γw KΤω αbΑ'M+–›–=ά]^)T‚η u΅&»ίͺΩUŠΧίΓ¬Ήh:h=€…Mξ:Š ώάο―ΒΩτo_›¬EŽ&zN`υόΩ6τx½"x_|Ep—L€]τΐΤs¬aήλΠΰ₯€9O³Πβθρ'ρšι 6i¦ώ~=~J-•¬±Ο`»EƒV=ΡΛ1ηRή^ΩYϊ½Ώ;vxοΜ6κπ4YυΉŽςXi.X:έ-ανΒ­‚Ό_!/Χ9.§ΉΗU2—Κc€γ^4ΑVΨ}„τšŒr‘°ό’¦{=_˜:]&qžΏ@Ψu‹π €Mγžƒ€< yΒγβ2iˆcŸηΌlx5]αδΝ&”Γ±y…πkΙAxμT¦—_Mορ!w[]G^ΘΓi^Šέ»ΣuV »Υβ}S³5¬ΖΆhΧm•χžχ”Ω?>VZω{ϋ›c(;o™o¦ο π‘SfΩογΰΏλG&}μz aŽ•‹­¨ξ΅κνκU°φOήm μΠϋqrΦqϋa‹Ί«Σ5X8؊«zΩΐaclΚ3K’ΥYsΩΖtg€:­σpWqžfoςtηK]‘ΞεΈΗ₯σ¦ν¦­i" ƒŠΦφΒ9+`ΆΑ_)θ₯ΎkΎ}ΰ!–Ϊκ‘«ΕQύk„σRΧWΪπΤ#ς²K‘νaΧwΕΣyε…yΣυ3€³#Ψu†ΐEΏrX,!ΚpBƏzΔΜξπ3“ό’ηqNωμŠ\'<)ΌHΰvΗθαΫυb#y+²—ςt:Oο<6Τ§σzxστΉ•c――?fΊ]]l°ιzΰ―ψΎϋυ%JΙCx΅D<ψ =GίT™=J_,ΪΔ!γlސѢ`ΥR\ό»spDΪE}ŽΆ‹φYݏjΗ‡­ρpΊ<WP- 7Ο·|‹v)šrφρg”5N[{²Υαf{ΨΉλαΛβaΊž’Συ„εz:—»ΓΓτΘή­‘7€0μiX]2°°½ό>Α•_IΎ΄ξ+Ξ@’—ε<ηzηaΌΛlŸSχtWˆ³ΝΞ½Θσ:/iKΡν.Όœ¬r=.ΝyπΏ„g|6Jx‘ΐͺΚ²=m/Œ²nŸH]!?&(ΘΗNΐ„K5›„β ‡Ϊ€> {[Σ:Βτ—a-|Vπ§ΔNδu‘Dφp5ΩΣΑ°#t„p­pœ}Hψ‚ΐΔ{ †Η-”“Αl  <Ηΐ`Ιnh΅<‘νJ–ΩοpI‹‡  ψ•ω6ΑΣI¬ΨμυΒΙO›\'±“LΨν et”Ν‰‰έŸΚav$؍8KxVσ*ΈeΥ뺐»L.;χBΣaΧο)οͺ Yq‘n™_ίiκe ‡N΄ρύ‡ΪΪ5,^;Ηrε―‹ξ©­{=?ύΫΚήf859—›υΪσΰE7ΩΪ½^ρPAx1ΝΩ{MΠΆR~ήlkΥ6ΣΧό/ZbΕδ‰πrΪ{ΥοQΙlα1Sf@3ϊΦ†fϋ’ lΧύψӊ°%ι0υgιΠCaή’¦gώ†εV“½ώΠ>OλάΣΈU ͺ x θo JS! 11ψD -ιπΩg„O ΥE%ΔΪ€ΓΞ“„ήޗ•μIΒΑΧV΅>ΐ8—*‘tΨυ!g`|k΄3mO˜ΦεΚ%ۘPΐW<AΉ΄!ϋ9'±Baέ]ΥαHγιBN;¨λ>α ‘|sΞHΎK˜ °#Φ§`' γ\Ξβ 3gιΒψž»ͺ#GΨu‘Œ½ΆΝZ·ΨͺΎCμͺκΫ.yXύ„ŽΎ~ywC6JGϊδgŒ7  ϊώ Χk€nx λbμFΆƒ/‰Ά• £ghΛ΅h— l΅ΦσžHΣδg/λΑ\Μ΄₯wό*­ΞrΦrτYφύΊ€w iN³\61KηiΓt{K¦~ΘνΓOsOƒbΠΰ>α\a΅‘ƒΰ€kƒ7«όσVΆο>,τp²ˆϊΨ'—KΊ΄a8LΚΤΓm&€Ιƒιτ-p‹²_/°€C }ʞΖΛΓθθ)Ÿ°sŽέαΉΒί&;ύ…ΩΒLAWBζDDκ„B;\NsfιJ%μΫΏΨαΆPsε0ΞυΉφVk?τh;J;g ίj­Η?[rfX k™Έ0ΞzΪrκηΐ…G?ΟzOšήΙ'΅lώ~³-NvΑυrφ.z™cW[£.”Ί™!ΣD.fޝ=uE©γkl°—IΥ«όZ I πš―&—RξίΏ‘m‘έwή••  T‡ ƒΘ‚πUαt-Χλ„ο ¬(ΓΨσΓ!Χ«Άž7Υ3O―$ y9°A••μρDYΏH烝Δ$rdΘΛΛβ΄ι a™€}YiΠ…ΤUl{·ΐ€CμΏGΈU€“ΰ“NtΧA|Wδu‡iΠω1σr<ΎcΧAΓY2@―βδ#J|Gη<­Δ¨+{ˆσψ4ί‘  …ΧςŒd{¬rΫ((Λf―`λΐCμ΅x‰™ΥΛκίЦ֝‘«Moe1˜ΤhSςϊZ ΗGžνθ lΏdjυΰί δ°ΒsŸΥΣ±J^]™jLΩ*ƒΟ~Ίd·μŸ2zͺmlξΤ1`qΨ¬jr:]w[vx‘\-?iΊ"μ mΜJ›.Γλυ΄ούiΒ7v K 7&>θH܁(‡τχ Φ£#(¬£€©ώ—:Ψζ?S.ΎC`P λ§L‡ΔŠάΊΈώwΦ„eSΎSX>φ°ΒΎRΈT`βC<χγΏ+PςrΜΒό VμFNS΅΄‡‡ ?“0nPί‰Βί?W•όJέ .εt^+*!Οηάυ]qΚΙΛLλ=M_-§ yhrΣΉΖητΙ_¨QoMC8A}[˜]=γ@Ÿύ€ͺP…Ooήj=Έ]ΝΩg.ίΩΕΏΟ ©εŠτπ_^­{Mγ5 ȝ€5ΛΏz#.ašΖkTχψ†΄τ΅#΅ν—η! Qx‘tGN2ͺuZθAςN’Η;ο™ °j§ΣήςrΣ— sΞΰNGψ-abYΏQόα—BΏ²Žτ!ες|Α7…ΧΎέLό ‡BžΞOٜbγ…— L(ΈV?'Πφju£O—ΥSa―“ςΌžPGΫ– |‰γΦΒ ΒέΫπ·OΦ$Rg”Uϋ S¦χQή―; »ΞνaΒΥWΰ νLΫ8ΖΔ_ψΐd…:H9/…J³ta|–^Cιό„ΣH—Ζ‡qYe…ρ.‡υ‡r§x~2όΠ 6R£ώxΏzϋlΥΦΗvΎT8M &κ§_r~U_kέΥΎ’šΫc6ϊΕΥcˆιŠγό­§MΊΥr#Υ5ϋoVΧ[{Ω=c₯.]$-6cΫfkߝσ έeΉ’Zτ!y«εσtΝ.”Λ ‚™bΊ0μƒΕ ααpA‚βYΒmƒΊ,ΫΌΗωΛΒ;κx―ΐκπς%f’ΧΦί•μ…x>wΕ=-6{;Π…aς{œσPηιC](σ)°’^klξ£_χm°ζΙuΊϊ§QVύΙΒlτλx²Ÿυρ2ύ^˚NηH)aό[ρ'|€.< χJrΜ±ΒΤv’~Σ&h†©Α²rεu‘΅&£˜έί’ύXu}ϊ’Ίi†SιψΌΣπ”Ž%”‰[ΊΔ{](w'LˆσΒσ{‡w™Α„U56p υ{Zt/Kb"‡i‰s ?WψͺΰΫԏI~™ΐ†ΙΑ} 1“Ό^"‘³ΒθΒv¦ΣΈ½θ‘_(ŒπΛcΒSΒ‘§D:|ΒκUge²Š Λ”ͺΫD>A'κοͺ¬taΧΑ½Γs&ƒο2:}…Oξ‰Ιΰ|²8yXΝ‘ΦΫ¦“:“¨;«γ_ ”ΗΔ‚2™hL># Η―ι:Ό<ΧΣ&Bs^M—$Nύ! NŽ5δ~+…v gιΣy8iηKΈ"s›ΧZ~Λ:›Φ[{U¬žέΑΦIΐ{Έϊ·ejΟΠ΅6υΑ9Ι+‘u‚}o&'I€.< WεόΌΝW±Fi Zλ_Ηκ’9Ι,™ψCΆXq]οd½xΫ¦deΔj($Ϊ]­Cσ8ηa>—ΓΌθaOrΚƒΰ.'Š Œž‡γ^θ$v’t]^–λCNYŸθ€ιΤ™\|Q8MΣ)Ψ‰ΌLηια4'ήu.{ωvΞ 8SπαnΙg n£Δ„°χTαRα}“ς:Q>yΊCn 62°΅VΙDΌ·Γ9I]9ezxžd|Λδ ίΞ(‡iδ·6J‘’οοTLάΎ0ήεjœ“0ŸΫ‚OV L˜θAl…E ]˜OΑ„Πc§—“•&­σ°σRIΫRVŸr0LγulOΉ£T- νc²LyΥ©=+κ5ΐcŠΚΡ_=€Œ«ͺMίΆR7§oΆ ««χ₯q°Ώk•μΠ ξϋχ+­œ+ιN²Χ\΄7¦W»ε6”Žώ‡tƒ&μ|²μΊ§€²ςμŽnge„,[ΊͺΛWuYiό>±””ΝJςzάm%mIίU™ž.‹§Λ Σd]τΙ•¦u™Ό]εŠζΨJϋiӿՈψΏ ; ΰ "}9OOΛ©oΣςΏž'DN0@[±#Nvv"gu8;ΛsPΕ{Ο F`ΠW IOzϋΪ©~‘θ}Ζ#xΐaΘΙ=(’SΦΐετ{ L/ΧyXπv0!ΘJ¦GξNštΒΎBοΞ)„M>1Ι* vΤΛuμ>ΫY›ͺ΅uoθCίq`~w[½NvDΨ½8p¨|Ζk—x©_Υ«γΦωAσ΄5©ar© „'Ιb”<°‰cθ'LΏ”Uχ€ό(ςJ£~ ΰ½ ¬Peά•ΆΡaξΩτaΗΪΨΖa_Κ;ζθZC^ξ‘Σ`+Ϋ­Η ƒ§{%<λ2/wI(”QP&ΟTλ`ˆΟ²™3gŒ€άΣO9ΈOΩ\Ÿί*sχsΪEW%lX+μJžͺ…λƒ2Σν Γ ¦ήflxZxΐ½}ˆΫ?ΨM‘νι6¦ΓJ©-ΥΪ…ήΫΪƒή'‡ƒ±Β{n ζv8― .Ž.ώ \/„“·΄ aΨeηΚΪ‰Πόυ‡”‡„e{Ϋπefε“ΊDΊξsό‹†ι›νφώΝ: ]fπŒ5Μ±Ώό »΄«kΨԚ0-NΊΦρ”ιF]$ƒͺέν~Yϋ-₯χz °ΠњtdǜχNkόσ—ͺfϋΝΦ½P1χηξ›ϋϋ}ΙojθΏΎ\6„kσ·Β[Λαzf ¦·o˜lύBΠ•”<ƒrΈή­© d~ŠJ΅ΟiΆjdηΩμ{»p‘πSασχΥ9oγdh³ΛΞ•ΌΆP&ήΛρ›ψ4yτ‘œφΌ€ ΛτΊB›ψšYI―λ5ΐΪ5eX£‡3/¬ήψϊ&žoNΞH]x NΊpQ:‘ό’yΊQ ­Ηh­R―οΚ &2ΟθΧΏτa#¦2ωΫ~’ΌϊΆ»ΣχOθΙj:Λ¬8τN.;GΚa˜•ΰ<φr«ρ‘JσM!\]3ψ€ζW°R2uSTΝτO†½3Nη'­vO^-Py>(τ@έoŸT;Cϊ}Aa[ΒΆR··ΧωXι†Ίz’Ÿη½SόLαpφώ@€Β6z›ΰi6Œ' ΉΩνΓv!ή'pΌ9Άψ4΄ωoΒ‰Β\a”0VΈO˜.,(ΗΗH/_ͺ„ΒΊQxΨy¨K2i< O—ΫU\WiΓ|™rG›{υ³ρeϊt=¬½f=υJ8šΎm©~ hαl[pμ kΦ[\»Ϋ·Υ«+Ίm·_ έΞp°%ΤT²0yFrQήΓ‡2Ρλ%\$αU]O>αY§;™}%‹ΥXƒnΠ±νMςŽ'¨‘ιΈBP·‡]†CήΙy|ž¦xΆβΗ–Σωa;¨οαjΑŸ0>?`Prϋ<T ž)|Z@¦nΈSZN‡=σ0ήupoΣK$Έ.―– ›t>Β!άηδυS12>„CΞ]Η-˜GΒ›…kˆπΗnωψΐ*±yY”ε€ΗΑ]φt£ˆ7)¨‹―βSˆ4n£ηgΥx†ΐν §[%œ.lόXyœsτaœΛ‘ήεt\Zοe†άσ„:d·;­Γξ‹4·BΑ ννΆΆ±`[–υ ³ΤŸΜ²Ÿ.Χ€F€dϋΏnjϋΘύq°GλW9―ςϊσP³ΊŠyΊsΜ(V―DO^›@IDATζ« -κu±ά―vδ΅  ‘ξ(Ί gΉ"μΠ8ΗdΏ'ά"Πω¦γIƒξη€ΛžVQ;χΡΏ °š ;uOH›Xeώ·pΎΐΰCΉ―X Ά >PΊΤ‹ξ ΒΫ„ΠοŒΘο¦OΧCΫΗ ”3`χξ‹· YyvV¦ηΑΆ!Χυ/•Έ½Μtω΄Αu¬ΐΌNτ‡ ―˜ΘA€sͺ&3Yp†iΘηυ„ύl‰„O©Ϋσ†i±uΉpžΐΐ―©rr~ύAόubβςΌ;v9Βs8”‰O‡=O5ή©’rΐmνeΪθνt½λ^Π΅ίΊΩV(r)ƒε-pΨι¬GβΩ¬΄ΏC;΄ΠΉ©άΪ©Š8 #νΔzš΄(,Υ,y΅>0Q\#―Υ£γΈ n‘IŒ.½ώσΗΎƒ­Q߼ᝅst»K”ΑͺkπΧοθ$ξ ‡ξυt!'ε.θ°‰GΑΑαΒ©ε0v&§/…Jaeθ5‚6Gr]9Ψ%s[ˆ₯z}‘ήλFΗ9yΊ0.”±qπ"α/‚oϊ]#ω΅灗%±rώ’K£«ψjqθw—ΌήFΚIλ*αΦ­ΦρΔΌδαΜ…$ΌO‹?ιΧ Ρ ϊΆ›Wo¦π―§ž£MΞBeaP/MΩ§v†ν>­Έž*ΣΙTά0Τ–θυ’Η8ΡΠέΜzœ%Σ313Knn·Φ•‹­Φ6F·|%μ³eš œ<κ<+ΦΙκλϋβΤ…“Oη<Œ#­λ‘!Βœ΄‘]ΘtΠΗ %0¨ϊΐΝ.ΐοlΫͺ`BήNΓιv•άΖςQv .)'Δ6l„e€ΫYJ•ύΧ}3KΡμ|ΰžήΌ’·)ΛwsδIz|JŸh―¦€ΙOχ2‰ςέpŽΓ‘Β}“:Θγݎ’6ϋo˜Ζε΄mηRawθbακΑίή!—Υ’Ξ!±"{Zsξη:i<ής68χ<„ΓσεΒ·κ`δCΦ―KnΡΠ7ψΥΠz#Lζ€όMg’v7Ϋ΅c{―ϊ5[8·r~Φ[“φ‰½~Rξ“Κκ΅’Ί °μgϊ=€œΝf{ιΆCκσ€gνF͐yOvλ6[»φik.΄'Ώπ|›sΤ’“σNΒ; Β]Aѝˆλ@pς2 `’ΖqώyΚ€ρ°ΗKΥ)―—vpcpψ•ΐ ·E`EχΥƒOΦj³ˆ +}VZκοΉ/=νιΞ¨‡ΙΚ«(νtιΌθ²ˆtδΏE8EX$€X©v™8^!ΈΟœS˜ΫηάuΞΡγ§Ηvg ŽΙ%”|ˆo|2ΆV2ωv•B[ΓΌ‘ήeκΫ$Ό^ψΐΊΎ&p­ψ€ »ΣΟaE'η!6ϋ9 ‡Θηi½Œ$’ηrȱс>”9ξ~ξη2e“ޱ?rή½@Έπρ{μE ΄ΒΣΊJύ]zΕΥ5«U7¦ΟWj·VΆί5Ά=PΰεζH]zΐOΐ.ΕΘ’τkS_ΣE’›?ΘΪVιΞ`½]$΄‚-2hυΣ6B³οκ=`žŽž ŒΞώA8Bΰάΰ’Γ /€PVT—D‡γ”– WCWyˆσ²œ»ŽŽξ,αηΒα(EΨK{θ| ‡ωμΤ>Β»C”™UvXρΐ‰< oΠΣ)L cΖV(§€νώ_&@<λΡ*„uwΏ„Ξ)Ω­xπ²ΞκΚ¦.χ I½nΈHΏ‘Μν!t/F ήn‰ˆ²Θ·L˜,p<™œ9Ώ7ˆrΉή+|I`2‰— μ0ρœ†Shr\Si‡Σe w etξKΓi™0φΚΡ&ς|ΓΛΕΗ-ϊϊz3;uν ΫΊYoicΊέYz—N΅N4°M-“έΙ9υ˜WΆπϊόTλMΩηφq2Gꆦ̲ΌvΦgκecΖh]0Ά|ι§―Ξn·Ο“°sρ€Φ/5>ωeΓάκΧΐ2d€0C8Iπ{έtΒvΈΎXm±:EOsΡ9yσ£w9‹{'˜ζ€u²ηEy8Ν=ŽΥν‹…† ΨΈBΰΐΣm­F^f΅xΧ‡ι\¦\ΰΎAο Ÿ§C =6ž*Ό[`w„ΑšυQ!‹BΏgΕWΣΡωχ±"ώ±π'{ΣΆ8oηψΞ?όŽώΊ²ό-‡aΟ‹.Τ‡²§qξqpμtΞ oΒ L„ˆγ:Ή]ΐ~vs<―ΔδάτsΎPΈOπ ™§Νβi²u’΄_Γ0ΧXα„sn‡a?vΌQψžπNέ&ξš~¨qυΆ¬Y_όqϊJ₯Τ8±°ι'o~c²υ\V³v4“'ΟμtjΌϋΟ |=HΰΊ«aΧΧ―f§λα.7Iζ8έ#0xBW i»― ΊΏCُQ¨C†<ΞyI[ϊ»³²Β΄.“‡σŠΙΧΔ/&ΗqΎ@{8ι:½½Lή.pνψuγiέ&ηJR•Ό<η”E½„΅Nϋ‹ΕΟΈžΡaγ„ο L΄ΈΉ†)ηΗэœbgζΪmΣƒƒ­γ–aΚμ5(²©ΏZώ‹±Ι»Ή†‚=΄`Άύ`ς,k.@[^ήμΞΙV–֐ǜc“›:μnύ,pγGο±ΖIkjw'€¬gμ]šηoRχ€«ύ­‹ο²«5SΞ΅nJ:*IˆUθtΑμuDη@§=&ΠYί,|φ^½φ|Υ mΦώωΫ­‘―κρ΄Z#π°ŽΖ§žkΨ¨ώxΊ&ή–Z3·&νΑ‡‘vΓ“fj₯’³ l΅ΒηnΣχ΄upΦΉ9ߜ’/šbν²ο~͌ΉH§ηΡ‘Ι舣Cy›πaA{•GbBλ/χ= Δ €χ O δ‡όάJOˆλ#qNΪ΄Œ+ mΓ&‰<Νο6’ΧeO†]G^dVΩ?Ψƒ*Ÿ#p-xZ‰ΘΫƒ2m¦,OλzηΔυάBš‘ΐ?nqδÞEΒ»&0εTŽΗΰ‘Φ°ζIΫ¦-τ›΅γyζΔ΅ϊmμ»ε ₯ΰΗΠj…Ψϊo“M:ŊΟτΆœϊΆοΞ›moϋκΕ?πυboMΨ9e†5j¦ωaƒ7―֏i|ω8ύœv ]8‰ϋώ-²IΫώvγH+θiοΨZy½,ΛZttΒη…ΑΒωΒ άΏt:RΒG„ο/X±Νž/Π™{ΗΣ&ςr]¦ΓeπcΑ―₯ν$μψ”dV@Π„— lw2ΐBΤλu‡rYώ“Žχp˜Ωυ^NV8Τy~O†‘_,θθ$νDόz‘EH·YͺL.νΩε―73α.(}~²d&­ΦΧ;'3}ηΦΧ“dvsΆΟε#§‘d, γPQ'ηη+a&cYuIPX ΒΨ ΏYx΅°¬fGν~α8a«@]{JΤƒ½”Νu3VΈ@`geOG o-sv*œτΏ"ŒΨeϊ“@{)ΈίMƒΗα“­’…vVύOςϋWικfSI€ ϋ“θkω<λ•'XQΏΝΒΦ_ό™^9†ϋΣΌΊͺ›ƒi7<0U“½gΪ’οι/Π»§£Ÿϋ¬n°i Μ¬tOΈPϋθΟhxωΑΡVΠ'2σ›ΎπF»qΐkZΏ²2@vuMΣ©p~°x–π+α«Β,aΊ1ΐΣ‘0a Ό]˜+¬(ϋ {£t^Ν‚Ÿop‡ΫΰqŠκΤΧx<œΊ°;^#Π!Rξ§Λ >LʊͺΔ1ιΐφt"|^ ώ°ƒτ:₯ށˆƒΰ‘캐#C΄‡‰vbσϋ&1ψ ›,\*ΰKo«ΔŠΝn;Ί}E]ω`wlΐΗ΄Γύ–Ε¬Nώ p<9VΣ†d(τ…Ÿgξ38υpn@ž'έΒNn‡Ϋ†ήγ»ΓΓ4ΘΨΔ€Œγ{Άΐν)ξ _† \7!yέpc7lVχγ£Ÿ*'ΰ“!ην…– Χ ?ζ»ΨγΧ:§Πnta9Aοώ–ί²ΑŠγO³c{χΆίk0ό°ΝVψ—–Ÿͺ'λufονΕNa»ΏZrλ=΅xŒ67Z^ƒ-ZωΏlώuΙ€΄!nxΐόndYπ€žΘΝ›cE=9;RΧΓlέ8ͺ_›ε.[`ΉΣ4ΤmΡ%ΚW΅φ&1;gπ_£ζσΪΠ|ΌŸ΅λΒlΠ{½/Zt³ύΉWkΨΊ‘2Θ%ΧTΩd§PvŸΞΡ#;ΘCΗM§uˆ0Kψ”p„ΰυΘ-IΖ ƒp£@§ΔJX›ŠIηE~ΚτœNΣ½FzV¦§ήρ1ρψ“8₯ͺ a›©:ZxJXC Eaς|„]ΉΛa|˜— ƒω―…ΉΒVΙ=“!ΚpŸΉ Ξ΅OΘmΨΣΚ8^Ψώuω½Η‘cJΥΐιJU3Ηωz၉%δώΘβœg›Ϊ;„<υ₯)Τ…2ιΒ°ΛΞΓxΧ₯ω0%ϊ’πB6cΫΧ„w Γˆ|ΨΘ΅ΐ€=I+ΰΞβΞ¦ \_œλδ…C· Ÿ.s&ΧuAδυφ'ŠΰOΪ^Β‘ΞΓΉ|ƒΎ¬§W‡™h#†n·j ώυgπΈ5Όfqια@Κέ ϊ5Κεdϊζd=1{˜΅ͺŸkΦΰσ vρ²Ώθ§Ϊ΅‹―όqvό ο^ξ˜«β‰η&οΨ7λJΎA'ι™ΥݝΏTu_’ιΊζοτfœΠ=νpΎΰ±YWΘmκ>¦4Pσ”W?t‹έ¦ώδυΕ?:‚ΨνadˆpšBs]†§e::x:':ό„³„Αδ7ςu;ƒ ƒί#‚ΌUy/ώ’ι)Ξτƒ66‘Ύ\˜-@;kGΨ.—±59$I ₯?¨*’·…ΛΥx%SJΐ/Ψώ]αMε8&5άΞ ­Δy[άηŠͺ+ΒΏΠ-¿ Z΄Ηύζ\(|_$hiΣ…G…Π.§9e,~'ΌQπ:%VΘλwEeβ» ‡q.;'/φNx₯ΰηώ$ΏEΐ?ΨΞD—©©AΞv?Έf˜ψxZv]š›~:ΌiΣ[{ΤφόζfϋˆΎŠvΖFύΰΞδ5VŸΦ5μοιαΑδH2q%d‘"=Š™1X§ασwcΜζh¨dυ―vΠ>ΡΆΩ^φΘIηΣC<9=ΔCV 3ς0ςp)΄½*{{» w™š!ΧeιιΌθ¬τώQψ°@§‡ή;h:±Η…θΰθ(~,¨+Hh“ώž!,)+nρΆ”Υ;°έ/yu{[)ΨΫηνυΚ\οaη^7ρƒχ Ÿ ? LŒˆ£3σ΄πPV°Ι}Oƒ†αgOγη«ζο”γ8OΈηέKΰ²qΚς:?Χ)“°sΟηάλτpοNςe₯sηϊ;…> `·Pΰό~Žΐy «99οΕ*ΧΖ³’?-όL`‹›Β›„#…εy{ik5Ÿ(*!·‰€Λp‡λ=œζίΡ{ υ9Ιf4χюVNΗ§¨·τΣΒ#υq±™OκKIš YΝΑK SInœΒ䀃žμ ψ6{”υju‘_υS|ƒβξo/ΪλVΩΌωχ'Ε‘=Ry€©η<ΐΐk}‡ZnΨ8kyμ.Ϋ2qΊMmΜΫguOώ }©j·†m΅ΘTMτP Ÿη΅ήΊ”yVΐΏΈΕ\(½mU—Έ©QO‘5[ρAuƒχ΅βΌΑΪYPΪζV+nΪlm›ΦۏŸ^hΏq€έΈn…5jΥ_ΉξΤ΄δϊKqZμz—α_§αΉαrΘι|˜°“}RUttμjEiDό΅Β…IΒ’<¬~*Ό\&@¬^&ά"†>#$·9Τν‰ŒΖ/~)<#xϋ$&”φ8ΈΫΗ^‚?<Θ]"όD =ή‰;'—-=A”η6υDyέ-ƒz!oOΘΡγ›P‡F <+1E ž‰ΣΧ|H<νπΆTγJRIƒΌ·Ϋ΅ξM&(ΘN7l %<_x»@ˆγš Ÿ†ΎΚΉ/1ΩβηΩ{v ΈΖϊ΄›Ό3„Ÿ δχs#νχ‰’d’Ϋrdc7δ<ŒKhΠ¨/‰nλh·S=Κ^Χ‹§77ΨαΉf°΅ΙŠ›υ₯ΡCΤΊΓ7Zα0ρ![-ΧGW8ύ; -§U³εΪΗτ“>ΎP΄T;™χjα³ ηΨ­žkΚZ|μο•φ&‰βŸ=χ€π=/)–€Bζ4 ηW.N.n›8Ν&74Ω ϋ—jε’fΈόv΅ι’hΣCƒ!―Ο έbS·B›ΆΧτΔk“MšΩ&γžYυ„΅­[i#7―KVWθ^ έλΏQY— ta§@ηΉΞΉλBŽ’·Ι9ε]&|K ƒ"ΞγΣ²’vˆΫ,«ήc„Yeωΐ(±Bnηεq;€[˜ ΙͺaNJv4Υ’6υm·λ·|’ΖήΡΦjχ?r[²Γa‡kkŸΆŽMΟ*φΧ—KεΔΏ{μ?${\P, ρ@θO—“νψΖζ-v]Ύ}'XοΡ‡Ω‹rMφrmsΖ…TΎRyΠ₯”i;mΙ žΖύξΑύβ‰μ1E±šψB9Ιρk…UΒυ‚_΄te…u.‡ς6ΈLX“qϋ«p‚pžp·@}žΚ V%aeŽΎ/œ) σ2‘9«BηυχA— NžήΓΞ«ιwΟ DέtΊιφ‘7,7”‰s›α Y`57M€>!|J`ςδiBΞq{πU·?φ„|@>-`3α½IΤαΎ ΉλαψrNϋ9ΏΎ-L-‡'ΞvωEΒCωάώq%­€E†$Χg•Ξ‰cΔρΓ–7 \‹£… Βa—³HˆrΙΟωp0V8D€8w_$0‰`‡ƒkΥ퐘‡αέ‘Ιδι’ΚΌ YƒδiΉ\Dr}qΌ˜Ψ`λVa†p™`ZΠΌ³οϋ‘Vω κ£ W'ρ\—η œχσ…ΉBυq농±q›Χκ9ΐΫ²e½&άΤΊύω₯° ‘¬D‘φΤ\h‘φ‘Z7λα–ΝPt‚λβhΞλ>˜.–|λ+>rgςpΫ³)k$?-΄5΅ΨΞό’Ί͘;t‘@Δ_+\N@Dςsa¬pŸ³οΰvVΥΎώyξUP©{B€ "₯X°€ˆ‚HGΐcCΕ‚**ΦƒλΔ‚¨Hο(k½ "zξήϋ΅~‹‘/ίΪΩIvBΨΎ +ΐpΨΧ3Α΅;’ΨΎ*ι§ΆͺWŸͺ7σρkΖ«έ3ΔŽsŸύίμλΓόZω«§νœ3³Ž/ϋ…m!}2¨w„―{xRg<= ΜίνAGώ™Υ#ΰ&Κμ?ρΜΏύχ3<ώή[τΟn¦΄ΖΉι΄όTφω§ƒGžΗΖqλΘΒp(~Yn ζY ”Μ­TέΓΩƒΊ·υ¦Ψπϊ΄‰φš–ΈaΚJzΒΆr΄₯έΡ ½όνƒ—Ώ‡Šς+π 7m2|ΌτΧΗ§@YV‚½Α‡ΟžŸˆ_ξψΨ&λυ`IΏQ§Π’Ϊ'υΔ£'4ΏcμXΗ]ϋšπJΠ׋㇐―<Ԏ4ΛΥ¨ΝωOyΪ"΅ήΨFΦ1 Ώ~]O^xzNΖ#mν«6υ*Ζ›ύΤv\T?ˆξΕμ8+΅υΠIl€ΗΦ λ˜¨‹ϋιc0±wύˆσ1Ά‡­Α‹~!x όή«‚—ΏβZυbχ‘`Ύγΰ.pœ{ΒM ψΨ8ΆƒμΤ©Δ>V©}ŽŸΔ›ώΖ“ίψ4γ±'Μ­ŽΑ~8ξΝωΑ9τΡv'tΛαΌβ˞?Xz9sωcQ¦U_Χkψw–Œ@sΣΝ’J†…ΞΤdΓ%LaΩRGcΘeμαα†τΐρΛζRάτμ¦ΤΞζMΌ†#₯ι§$μƞύ›6*†ΆΫΧώίασπ9°}ΪΎφAέ ΕΖΐ¬KY|(œφΩƒΏ9¦΅ξͺ[‡ρ¦€ξ€%_lΝ<3―uDΟC¦–4mΡk¨ήD_Η$ηQΖΟπp\ύΚVΎ;ΐ’FzRϋlΩΙo²ρšέ°κϊFFy%L/wηΧω[vχΧ| 8Ά[ŸΟΒ·α!πB΄nΣ¬ΗŒqΕxΪuΊΏ||όΊ>Ύ>Bά΅/Ι_Γ~bOI}Υ-φV]Δ“7mΆ=γzΖΏϊ(rο)?…{Α―ύΚ>9ηπfώK5/§†κ5n‡j\έCkSΨΏ„› Zσ 0ώ(Ό V†ΰVxΌˆ -[?Λ8ܘΖS?jGŒ·ab|…ϊX£υΤ―’Sˆν: vOΧΰQΛίΆΙ IMχ’?Ž„+a",Ї’‡θ–πZ°LmΗΙ6Ϊλχ`O™ 1u$ρ„Υ•ZΆ“_‚e·m{‚‡Ί‡=ΰAoυ/eβ2EωΖ•šžx›-ŒN¦™όcωΝ:Ϊκ~M½ιŸφιέ1Ιά<ξΊ0ν)π}ΈΖΖΑ/@{mWΥλψF7ŒŽΪΧcw-Όχ“ιE`axΌ|„, ^‚Κ_α:8v‚sΑΉΆŠmJ΅}U·χƒϋφΕ°Έ†ΆΧζΕΰX΄IΚ©aΥΝc|I)¬iΆΥ1Yμ—ηΣ'Α13ν­`ίΛ*φΗύ8œKH»ƒ6ϋψ%°Ν|˜†2+GΐΙʜ19(jkr8΅₯ι§έ/cΐKΞMθ‹ό*˜“ eΈΉ| z hΟΑ $T7MIXυjΣξ_ώ?„}ΐΆ()Σ/$L‘xΨΧί»α[ΰ‘κaIΎΔ›‘υZ†uύΊΗ}„Γc°(/‚=ΑΓΩΥ5p.L+wΒ_ΰ…Π즁τ5Ών²ν^φΙΎ{Iδςoζ#©#ι³ύ‰n˜xΥ»9Ϊ:.Jςuc3χ·ΩfγΆ'φ„ΦΤOŸΔ-gΈώ φ/CEŸ«α<Ψ œο=ΰsΰbή¦˜'cPΣτ5Ν†sεt~\»+Β²ΰ#ΐΌλ€Ώ¬ ρE팩λη88ΕvYΎϋqzΔυςΏΑ½sΈχ¨Gΐ °Μm}²?ιj_Χζό+κUΜ£ΤΌΥΦM}φoΚ·­Αr·—Β„^όw„χ€Τ›zΊ©SΆ'υ&mΞΖpΡe΁l˜΄(ρl(ν±ΕΗληΰΕ·¬γΐWφ‚ΰ₯›όnΰζ†KIS₯iSšyΊΦgz€Y;ΰB0nΉ’z°ω•ώ2ΘεΏ;ϊ! j?ϊ΄ΔrνSQCμ'Α6{° ΌPτυΒX τ±­—Α‰ΰεγƒΑƒέΠφ›ί<#υ½™žΪώmˁ2 Ξ/NσE,_’·YoWέό)G»bκΞωi`“†:έ’Ό†’ςSPμ5ήΤk¦%Oμڜ[Α ΧGš—b<£ξΧΥ¦0μ>σ+jG2ΝΈvq^ΏζOχŽλΑǚkΗ‡’σΆΜ Ά1ym‡ϋμ“p8ΗJϊaώ‘$mK™ρ5ξΈ:7ƒ_Ïΐ`ž}aQΨlΏυ΄•ΉγŸφTυf\Εz#Ι›x3txΎδWŽΡΝo; >~τQίnlψwŽΤPΖv²‰j¨^γΦXγκ›ΐ x9= m›sGΜλΙƒΪ‘f<φ„–SEm֟CΙ/½Sα0Έ<θΕƒ_Y^λƒνŸ^Šσαc8ψ‘άSuλφς8,_ω8άΞMS<0ΗΑ_ νNίj8-=εκ7\?~Q[¦Ά€:PβSCW‡-ΰzp,2Φϊ)ρ―zlΝ°ϊ¨Ϋ>ΕqxκL{ց—βk`AπBΌξEŸψ ³.VΑΧΗΡΚΰCiIX v…ύΐtΕ~šηjψ:Ό~ ƒmv]Ψ·Τ‡:Pτ«’x3΄\χό]ΰƒy#°|Ηp-ΫfΏΌοy Šε₯L퉻ίζ‡'!y’†©ο§IzΚσίΗη£°œ/ΧS$uoΣG²Υ4׍ρΨlwΥ]Λ'ΐ§@ί―Α2ΰ―^ΞoνgΝϋO,ΗK~Ex!ΌΖΑΏƒ—λ’ X}6ό,ό |τW’ζ˜L€m΅?κΪ]/>4\[Φ㞸|tΈνΫ‚Μεeι<ό| œ”‹Ϊ*KauμnϋL+―~ΆΣ<+Aξ‚ύ;Z·έΞ…kΒΆθ;š2qλΘτψ&Ο0£pρ eΞΈˆ¬‰’Θη jkt7₯Œ‡`}Δr”Δ§'μζœςoΪ₯Υ6y8ίγΑΈΩzp Έφr°’φϋ€>#RλN~m©ΓϊδvψL€οΓσ Ί_7^ο‚“ΰ½°)μ ώTμ d>šuZώ3°'¨›~LΫαψF’χn Αύ ->†3’›Η>‰RΛιZ¦ύ7yμ§ύΨ\_^N©€*‰·ι‡žOτ‘ΒŒ‹>Uχ2Όξλ[ ό§i·ψ*Άχεπ6X†ΝΑKτΣ° , Š}υφFx ^ώ)΅3‡)ΫψτŠy“?‘λβπ—ΗΪvΈΞ|πό„μίW’~_ΫΩ&™„αδ8ύ¬C’†:M±M!yŸFwO€]G‘ϋΛΜh₯Φ­>”ηhrH,DΚ :Ί©ν“ΗΠvόΦ/?ΫΩεZπ`ΆΎšθ˜HΚ¬KlVΰX)~Ρ Βχ`x#lγβΕp5ά„Gΰ xμ‹uY‡_r+Β[ΐ‹r^ψ<δ‹KŸf[Όψ½τM›S¦ΎΝ|InάώĎΪ)ΏΖΩ΄$ΞcΪꗝeΪ?ΓΨ«φ€FͺΝΈkR±νιKΚL¦'oϊ8 ΫOΑ ΣGΫGαTpμŒ;ζ//Σezϊ W™g Pμ‡bωΧιρp#()/νλZ§oνK͝>;ΆΧ―y˜ξΧ“ι¦έ φΧv‹ΑFp+ΨΗGΑΆŽ$ΆAŸmΑ2ύE0γiZΔ:«$Ώε{‰ύέΑ2΄\Ž€ζmڌ›ήζίa8›F`ψ˜M=ΥdΓ€(γΝ |ΆΣa7πρ‚Ώ&€‡›@d”oΓ^πp3+)³­ΎΗ³›Χxό εiπKΕCΥ6ό<¨δ3π˜U—?Eχ%ν>©kχπϊ;L0/²/Α·ΐƒξmp(lkχxŠΠ1ώ-\φΥΓψ!p/ΩΧwγ,ΞΕΝΰ—§u6ΕΆX·¨ΊDj›cK˜΄δέxb3¬εVϋHΊε€,ηK±­Jμ걩+5_Χςμί€ωοΨκφΫ°Ά;zڜΈυ8Ξ—ƒγ²ΰ₯Ή)ό–€5Α υΏΑ_^λ@Ύς΅{ΑΚ ΰΕ{8―JϊβCbf%eΩΏAsοΪ²OˁQΧL| ΣgΫϊ |V€•αbΨn‡Μκscέ‘POΫ’f˜4υŒyζΐυλψΊΎ-gcΠI8lwmε<”9q†€9kV²αlU6RΒΪψe£š¦ξKί/Ξ=ΐΓξmp¬·’Ό'’ΏŽƒE ΅D7΄Ιk\IάΠtΞ-Α:s=€>–‡ΙΰαSσε’ϊlcΫ›‡˜ι=ΰΫΑ'auπ13?¬^<Αοΐ/Θ»ΰjPsJT/η"mHˆ©/±Ω–θύĞ’vO΅5ύg6>¨μ6{›­Y||xύ\+»kPIŸΊ±gšΟ4ηΖ9Ί |, ŽρΎΰ{I=Όψί «‚’uθœά_ƒ―ƒsbέϋ΄-Ύ˜fZ|άlηŒP’ύ²n΅ξτΩ¬Q(ŽΩΣπix Έ]{ΦαΪkžειIύώ©+5­kιώm΁u;nyΗΛ:|LΕυδγA[ϊ€>HRΟh|•1΄Α4Ν9,bŒ@έ(Ω<©¦¦yήgΑ6=‡W^ ~yi)9Ύ‰ξεοαj_Rv³ΞΎŠiΜƒνΑƒ*eε€²]“Αυf]J[™–£Δ§»Ώ΅?Ά1mH{=„sΠy9όN…uΰM°3¬^>ή^6WΑΰAΈ xpϊςa ΅_ίHzΪ’O$νNΌιγΈκcΫ›iζ1-}5>-±ŒΆrΜ7Ȟ6¦žA~^ †U >:‰vΪh>ηΑ1L»΅‰ύσχβy#˜ΎL‚υ`}p›ί5”uωτ“ΰ\PζηΚς^Τ1•ύ(νΛΰzqOλ―γ`?΅%4Ÿϊ“π[Πχ X|DύάwUΝώ˜W1Œžς; ώθγ8:_ˁγιCεPˆ|EΏΡ”—<†ϊG¦Δ‡αlαΰύ`gLk¨žΈ›’0iFΟ˜ˆό'\ ^@Ο@€nžΨRΆ‘—νp#xX<ΛππBjΫ°)w€°¦Eχπ ΪΎ ŠΆkαeώΕΣ€qEΏψT½“8Ζ&‹^Γ:7κφΗΆyŽƒ“a<(Ήhτ/cε ΰcΘΓίΉM™–“~’N%ΆczΔ²όͺΆόϊؘž2šΎΆΑςΖς!–>'τrvΌ"΅ίΡ Ε<ϊ{™ φwΨ ΧΉγnyΙs'Ίε™ΰ—³>φ)υΆι˜ΗDlΟ&p>€¨EŸHτ¦ .o€wΓζΙ@θCτwPϋhžZΡŽΔ–ΈaΫXxfl K‚¦Gαp=ί/Bς¦ά„$υλ7kΐ5z œ /ΧΨΒ`Ώ¬/eԎ$-ρa8“#Γj&‹f£¨ <›ΘP{B«rΊqrΰΧtυϋαlΨά\~y!/ƒ‡DSR~3lϊYΎ(–γaόNψ/ΘA|Ί‡Όnj’ύΌκU,σ4°o;Τ„Y€[_³―Ζ/’ŒQtΣlΫν0…χΐNΰ₯δΓ+γbθ!ν‘7~“ΐ±r<ΌΜ,?ώ¨}‰-mι' 4mξ_ΞΫΰfx,3sq[ΏΛyμστHΪ?(}hφCίδ3Ν1w-yΉΨŽρ°<ψλŠγΎl ζΡί±4Ο}p!ώΚΰϊs^τq¬τiJκmΪ§·Μ‘ςšv€mΣ*/}Ρ/z ΅[§sλ>q-)›ƒkκ§°?|·61$Ίu(ΖSŸqΗκΕ°˜ζϋ XΏcϊp}'?κPώΥFΐM1”½πΛz ψ:dγ’vΔ κζ=Ά/›α:X ΞmƒΔΛ2›s£ληΟώ‡ΑΑ=†“`gπbΚ!…Ϊ‘f±'œŒrC"Σ›ύž†ϋ %Ϋ^ϋ™v{))ŒŽΟαπ%X<€—Ηή<~­~5έΑοΰΈ§ϊ€s.Μ£Ψ§6iΪ·]ών‘ΰ―0yZŒΚ_Ιx$μ ›ΓMΠV^Ζ„δŽ$ή6/IšΗ:\»φΗKί΅κ/YžQΛΒf°LΕρO]†Ώ€}ΐ ΗΥqI½¨c&Σ*ΣΎΨΜνh*ΆLσ(Ρk¨ές΄φν!πλ_ύ;°QΗR±ΜΈα ±|eπ’wݏηΔ΄;ΰ pŒSκ ‰νΚs4n‘Μ9#7¨E^^ΊίΏ„Ž_Ή˜P;β¦ςηώ»aiX~–oΎ'‘ΉρLΣ–uD±^/ΏΑ<^ώΗΑ^`;›mšVίΘωξ G#£)o4ε4Ηa€<£τΝΠ‹H»—ΏvϋξαΌ (Ž·lϋΑ―ΐŸ@}œχ‚y””ίΤ;‰½?΅½ϊΛ‰ έG—R}Ί–Ρυ?.;‡ΐ‹ΉY^³Έτ}’f¨$μΖΊ›6㎣kuMpΌ΄-ο€ΥzρΪΧί…°ΨΖUΐqtN\^ΐ£ΛlΆg€|Φaϋ~^ΌΓ Ί¦§\ŠιˆyΟθ5μΉuϊxnΟΧ±Ψ τϋ4,„ωAΫHV—}” ΰXzζμ ϊΚgΐΗ¦s6Z±iKΒΡζϊΝ’>fΡΐŽA±Ω˜ -ƒΖM½6,~νΤ9Œ―βπ]8«|!ά„o݈ζqCΌ^ ΪͺOt_ύGΓ{ΐKΟ6‡‚?»Nο—?Y:’ϊROμΝΠΓηMpLn&Ž2ž±Š{3{BΫ€O³mŽΩΗΐΉq‡C`9Ψގ§βΨx‘Ι$X…σΑΉτλΫςžmν©Άͺ;n‘jmzBλΎnυZvϊnm:ζVI›’G§<’ό‰y)pΌΤ]ΧοWC.sΤΞxL&tΏ…ίΑV°,ΌΦ€α 7νχƒu*Ζ‡ς/<υςψξΖ\Υt7Έ q€ω{1ιGΑ‘ΰε΄!œ«‚v_χU<.…Ηͺݍ.ζρr9xϋWy©yΰ|lό^ώdͺ=ΪΪΔΎ>B>Πζ0ΐfϋλ‘•C°ι>Θnή€eLΦΑΆyΟξ8ϋΘZόiφpψ¬;ΒϋΑρςPο†'ΰψ \Iπ˜¬§JκΧVυΔ«­κ΅ŒΡθ±ΆυΡlqλiΪkφΕukί՝‡Υag‚γ–ό¨}iΆ³Ζ«ήΟΠPμ£\+€}M>ά¦[2fŒ^C펩_κWγγΪωΈί ^Τ―Χcڊ:ΕΊ±ύJΚVw―Ο€sαœψp=όξΧnjŠuEœ·‘,\K՟h§Γ±–ϋ)π%p3Ψ.Ϋ;IϋjΏΫΖ.e₯μκ“Ί’ΆΞ~©*Ηƒ‡pΔΓΣΓψψ|v‚ƒa"8? ‡­Ώψπ:[αjπΰΆέ΅ DϋρΨ›‘>U’^mƒtϋ(5O³ί¦Ε'i–{tϋΏμΫΓΛ`<86^β^T›’ςΜγεσm8‡\8/Dx±½ ΜηmŽλdˆΤφŒdKΪhΓ<0jίG›w΄~);}0lΧ³ΗΐvωΠζƒΚ_˜^¦i%e5λpμ—eϊΊΏημ³ΰΊ^I=Ν|iGΣ>ŒΟ†>fΓ OGu“dc$΄υψT{­"v7Ό‡ζρpΈy=|kΒνΰ…XfΚΞαΧEDέ‹κg°1x)yΌ NλιUΞj+έ6zω»~k;ΫΚOΏΦ%ρ0x< ΅}ϊ( »±‘:vŽΑ;{nΌ'€γPχ•σ Ϊτχ— y)Ψ–­!Dΰ‘»?xϋ•ι‘~ψ ψψ@°œZ>Ρ~»kϋ«fλ¦<ϋ7k!–Œ“y›z΅ΕίP»}Ξ₯±!ΊsΆ ¬«ΒF0ϋλx:/~aώ|ˆ:·^ψc}ΛΣξ‡CΑΗΰn Ο7as˜^Τ©Δ2’^υ8j³MƒΦ—ύ›]b;k£gΪIρ²χαδZρ°(L%ΩžϋS׏e) ΓΥΑ±tώ6ΛVN…Ιΰ:4.$MSRη4‡³vκB˜΅5 KŸU#ΝdΨΤέΔƒΞξΰ%ΎόΖΓ]ΰ!bΎ&U7}8<Έ½θζυΛΐtmU’ΏΪΖR·Κh!Ϋb^ o„ πg°έŠiJ3μZ»MΣΏΦηe΅/,”wΐYΰCK©c ξ\ˆ{ΞΛπFψ|ΧwΓ^ Ψ?ΗwΨ|ό&χpή<”-ΛΆ9ώ–©―ŒΧ΄6½ϊgμτkΣkk>B^ |hΊζ~^JG€v/υŒ‰γz? ϊ=Ž“vΗ1OΩ†΅NΗύK°#l/‚ΑΗή `JςwcS–QΛ¬ΊυX·γšυ–όΟEh{uΫεC\qœl£ΆΫαΰšψ0,γΰbx 6}Sj_΄ΉOΔ:£ΐqp­Ξ§γή–σ@©ώu<«}`ζaΒ¬³¦τa©Σ;nΊHέƒτ6ίΨ -ΟΌ?7±ρmΐ Έ2Τω―u“ΤΙ§Ÿ_~AxI)ͺμΧ€—P3_3ŽΛs.Άι|p,ΌόΣoγ¦Υρ%Ϊ‰W›}v"摏€­πA<¦$Ž«e:~Ξ‹—Υή ΝC{x‰ΪΞ`Sψ6| ή{ΐŠΰΓ̟Α-£MžΆ΄6›―υ΄ΪίΜkΜ3/,ΫφXπ-πC8V/Ε‡ιY°,ΡΗΑ>{ΩΨφ΄#!¦)Dρ;ΜηξΞΛ ΙΈΤ:bKž-QξΏvηq2ΞΉγϊuπBŽΨΙπk8|P)ΗΩ~=Νώ&ξόω뉑e, Žλιΰƒΐϊγ‹:• JK»Ν0ΘgͺΒ††Y;kΦ?,}&F n‹©›F=˜ζ}ΌPͺhΗζA{=xhΈ±W_σϊ§ΊΉ=$^ ώZ°Tρ±ŒΗΐ|όJΝί΅Μym£γγ©Ψ~₯†κ‰wωγΈξ ^2ΓŽΝΞ0άCΐ©ΰhλΜΨT{Υ-Ο1υb|||l½Ύ“!mς?|| \ώ\ξm;,KΙΎMύέέΏΦϋyHy5”_W’@IDAT­κΆGl·­νzΌlχŽπ^ψψp}*Žύ™°7¬ ―ƒ«ΐ~+–UΗ«©7γζ±lΧφεΰγE±Ξεΐ΄ŒjGOά gΑp-Μ φι8Ψ\Γ–IΏ_‚Α2 ““Hx<ό²ΖτOΤ©ΖΆ™O•Μe-£¦υΩ0ΝΕ?ͺV1ΒdS蒍Qη¨mS™ξfύ(< ‘δ·L/ϊƒ _œ{’{±MΣγ‹Ϊ‰{0ψ•>,Š_gτہ_΅½U'iŽ―fkί›6ΝKύd8 .ΗΫƒτ]`Y^j€xsL0΅Jꬑ)±Ή΅aK8¬Λ:ύη6€%`ψψίc¬τΆΓ²SΎydΨ'χνΑ†}ΦIήΪΦ΅ΐΛώe`έ‡ΐW`WXς@΄έ‡Γ°ό…yΐ΅–yA•€:«[Ο9ps/ΎαΆ`Ή‡Œ¦ώΈh3M3]ω-l χ™βΈ;‹N£NϋΆ?υ"7›}²Ϋ~| ώ Ϊ=|¬:ΧOAΔςƁkΫςλΕmΛ]πp3ž¨iΖcŸVh[”Ά±ο¦ Ξςh.œY^α°‚ζ&©ρPβ6ΰ΅,x-ΥΗM}ά +ƒ—Ί‹—…?εGΜγΓ`CΠίΝ―x°ΎrΠjΛαήΛΙo¦=ρ:΅ώ΄ΣΓR‰_ΒΨ<(? ~+Ž°~²—ττA}ŽέC6‡(κˆb›Δ:ηxiψ0ΌVΣέΓΫυψαοα½Πv»>,Cί΄u ±]σΐΑΰ:²ϊΖίπΏΑ‡ΑB`;VMšΆmyΠ/cχΊkλp,(K}RΎΆˆωL$)7cXύLsN#ΫΟΒ·ΑφΪφ¦€Ύ„¦GOΨΜ3«γΦλΎΊƁ{ΥΎŒ΄ΧœΧAby…³ΑƒλΦ5q<, Ηΐ ΝG›cε―oΕυυ]πΧ•¬ΤώX©G¬―Jβ£™Χšo¨Ο¦p#elG ‹Ύ†κ5n5ξ&χgTwΕC^Έ ό:KΝ|nΠ;ΐMώcπR/jG·lο;aGPŒϋ•ιrΈ<άψ~5xπXΆ—Ι–ΰβαkΩΩΠUΗάΛ±>ˍ_'α9όΣ3›’ƒ3aΖ¬ϊ¦Ιφγω`šcπ X”γα$¨εhWR¦yεplόsœ’ŽΪΡjθoάGœσσk8Ξ/xmΛvΎ–ψΝ`Kπ—nΛΠW±,ϋΣ†udΞβgσΓ:0VΏwYΣνΏyn†#ΰψ\–;ΨΖZ>ΡNσ)†M: ό±|Η~ΈνSκD팍Ά`QΘzΏέuœ:P;ΊνΠ–P{S,﹐;¨ΤΗ“Ί¦Τ~4Η2Ύu ΅ω¨Ηε%ΰXn ΞΛ/` XSo‡ƒsoY― Νϊj=iW΅‘₯#Ϊ,Χ΅γZ}vΟ*χΑgz‘λc(³qάC™3G mC57`Zξ&ΎͺIΎ€šOόwΫΑƒ|5π—9iΣ=~ŠkγBx Έiέ¨©΅#5rc7šΗƒζΉ”Œ‡aΥm“qϋΰαΤ–ŽΉŸ'}υ+voπKΙ<ί€τ=ε%$©/ζwžΦ:σδ‚Ρ_I؍=ϋΧΌbΊ±ω½δγππŽωbΰ%θcΐΓάώΈœZΡΎdLW,cm°<Ϋϋjp=xp§=ρύ ΆOƒuΨOνΆUq¬ͺΏύ¨q}bS―’]qŽlί΅p%ΨgΗ/chψ0G‚ζΓzρZvτfˆkGbOόΉέwΜ`ΕΆ_q|EΡφ$œ ΗΡKέπΓ°ό τΧoPœηΓΑΉ―e©ΧzˆφγκmΆ΄‘ν9αΰ9ψ¬6›(›Ρbb«Ej«›ΤΈ›ύπ ~ΈΉ_ή‹/MΈ)x™ω΅ΰWΒi°θη—@€Φ›‘ε›φ]π δpξΖ¦όλnzΪ:eκμ‰ ;ko¦Ω^N=¬—ζήΉnΏ΄›}­ωΕZQό"ŽόšaΗ±ρ§Ξ©ω½μ½lύ7ݟ€σΉl~ε)σΓή°+\—ΐιp8ΧΆΝΎΚβ=\>WΏͺ}Έ(ι‹ν΅¬Sΰ$ΈΧNΚϊϊ^0X‡—ω„˜ϊ6υ6±Ώ–½<Άέ‹ΙςΔςο€»αfXόEΔ_*~¦+ΦYΕΈνRτ©στν8Νğτ=E4γ±g\Ζ²~ϋε>Ύ|ύvηj_p½ εM@9΄™'RΫ­^₯7Ν~€OρΛΎ₯Μa8#ΰ2”±l€ͺΧΈ5Φx7ΐξW–βWΞ=pΈωβΣΜGRΏ¬θΥG›’Νξ ψx¬?/ˆ‚‘Ύ­β! LkΓ&έKα–NŽΑ\w–;<ˆf•4ΗΑK\ΫHΨ–δSΨ^Ντ8°―Ο‡­ _·¨©lσδ"jϊ™ΉΦ;HοTσ΅ΰާσφxΩ9§> ¬o]Pl πJxxΘ;ί­ΏΑ2πς^hΏφƒν@Ώ%Α~ZλζHp }.ΛρQc{¬'kαVtH—€m‰€οΖΫτΨj˜ΌW£ά >|LWβ§ώwXμ·σm½ŽI›€½Ώ#ΡKΞ½ažHΚO|,Β5)δ\Έ k›οι Ώϋαπ“W€k`EX ܏―Λ<~ Šc5šΊυ­νIά΅cωΞΗC° 8ζΪ?Σ ]GC™#πΌΩXΧ°ͺ©G *fέuΕ/%4γ#Ω½ œgλπpsσŸ;ƒςqX<Έυ;€yΑΓ"mCJlΗHιSeΐ`lӝ½pmBmm³B2VmaΣfύm6ν^€{ΐό ΟωΰXΦ=”Ό£“ψRDGšρΨ -―™ξ|9fΪ=DCUΖύ3μ ο…Oΐξ°(xΏΆ€W~§Γ=0ή>,ίΉR\wΐ—αxπςλχβWη„Ϊ&Αm`›,O±½ρiκ¦'M½ŠΎŠυ©'žΠ4ηΒ~\o†aπgξ'ΐ‹ΞςΝ“z–E_–ƒΩ!Φν?©ψkΖΜJϊžpPy΅ΟϊzΞœΫ ]Χeux)˜Ÿη„σ–±BbμŒG΅A{Ν―›-ε ΓΩ8υπšΥ«*#ΰ!λΑδK;nIξ«uƒ57Tί©§˜ξeΰO₯k€?χzθk_Ζ½όέπ…―‚—…—Ώmj“ΊyΝ_γmώM›y”7Α8xΚΘ’ςwΒgόb![ΗG?°\ρΧ$ŒnΌΪ΄;'²Ψ†€nΉΉ„Q;bτO?»Φ)ۜ2jZτAa³Ϊ>Ÿ„­ΐ ώ-`œχ΅zxθ―d=\ξ?ύΌTνΧΰ˜κ?-©cS۟ώ§½–“tλPWΧΏϊ&3LΏ/‡m@9ήΆ]±ύζuώξ†—}φQcZ$u¦άΨg4΄N\Λ@Ϊ0£e§ο#΅%eλ[ϋ’yΉ»cΰtŒ\C¦9WΒoΑu’rP;2¨ξAφδ†sΨd³ΝaΝϊߜΊQ<ό2[<ˆrιΣζV{-SŸ§Α― «`/πΐv#»αο‚k yάψG—@Ϊ€:…Δ_cΥ§pšF$‡‰‘—Λ΄ΔφϊΥw$ΚhΧ­ύπςσKΠ>Ϊζ ΡNYM[[άωY,σ&Έ}•Ά°­œκ—τn SΝXVlƒΊ_ΑΑΰΊ’iΑΓ<—Ήcπ$x¨;&^ξŽ‰’Ώcš:΄{9ί/TΏXύ’·<ˍo 1w솑€'^ΓΪͺλ“Έ‘½†κ…¨ ŽΝ~ΰxΩ%e©»Άμ»—Q;RλŽmfΒ”οx+‰wc3φΧ6ZNΪ:R)©ΟΠ6x&8―·‚€›Ά,[l¨₯ާN‰§M kƒψΧ΄‘>›F`΄ιljΞ\Ysΰ΅"|šs’ΝDRGΪ6Sjθ!~ xΙ^ζσΰφ΅3X² ½ όbZ}ω“Τ‘zΨΤΆT=Ύczα<?Qϊ °#‰m±«ΐp x˜W1=$n¨€†ΞΕΞΰΕο’’νTxΔHOjžθI«‘i‘ΪΫτΖ «eXnp8ΤG›ώŽγ¦} Ψτ}|Ύ ώΤ1ΟPΫΆ)ρSΰπrp μ{Κv혯Ά/zμ qQR―α ©iΡΫBmŽΝcΰcΗ_³lΗ‘ΰΘτ ΡNΏͺ-Ίiκsš4Ϋ”xΒf{λ<¨+Ξγxp˜oΎ^HΠΩ;ώ³Θ`yπ‘0¨μjW―q’}©iƒ|ϊΞCeφŒ€‡δPž›¨Βƒu~¨#z3¬­ΝfΦVuγ–yμΎςέΰ‡3ΐΛΤMνζ7Ÿ—Α’ΰεο—’~Uτ±5¬ι#₯UΏ™Ρ"s. ‘ΚΙxyΐέ>tnΏτΌ΄,#>†ΡcO},Θ§Αqά|Dx‘Ž“ε™?yP§ΠWq\ύΜC€¦ -»JβΦ}6ΨξβΟφρΰΕοœo‚sΌ (i―ΑcΰΰXmΫΑ^ 8†φ[ΰ$Έœgΐώ(iW7Φύ›±­Άiιƒςd­Ϊ&%~†Ρcχ\» .=މϋΰ πT’IϋMΛψ˜Pν5ή&Ιί–6³Άf»›ρi•_ϋβ­–αΊάγYηΛ‘Ÿo†KΑ3ͺJκo†Υ'zυQ―cCӜ―‘Μ†ππʜ1lΩ ΆΘΠά4ΪΫ€ζ3έΈxΡΈi=€έδ^„iΖύL;’»ρ<šεΕ‡€ΎΏΊRΣΊ–YχwZCΪb(φߟ7½<Μ[Χ»zόΫμϊϋHZό…Fρς*xΡ¦Τ~9ΡSqΕςε@Xšι˜¦Μ_‡ΨtŒnΏμ£kΗ/ώUΰ-ΰ…ΏœŸ„mΐΛ?ε]ŽΎ5,†‹αaψ5μ>–ή “@1Ÿω_?€Kΰ³°ΜΉ”Σ.LIάpfΔρς›z2~†Ρ-?Ίcς 8χ—ρea[p’/ώ†±£φΛ‰?γ#‰~φuη^ψRBλKI[¦μΔΖ>(t<³&–D_«ηψΒΒδ^|QΒsΐ±sOLο\ڞ΄)!¦Ž­–UύLΚlύPfί䐩ΑΪΫ|Ζ―†κUάPuS™ζΕ°L7ΊβWΠ.ποπ4ψβχbZΖCΪ‰:Pj»tJ<αΐŒ³(!υ&L›Œmφ­φO]©>‰{ΰωo[γ¨|όί3Ξ©/‘>J-Ο―-_€·Γ?!υ’φ₯mώͺ-uZ†εz@―{Βnΰ~μ>ώRΟuθGΓxœζO»-3mΦξγaeXΎ·Ad1”χΑ™πSx/¬^v>šrQ[ŽMΫPϋAtTb{·Β}p(–—vw ½xΪo]σ€Λΰΰ<*ΆSέΉύ3>ΡSvΚΒ₯#Ζ•¦½kνώ΅^Ηΰbψ!ψΟ*ΪΖBR³¬Aφψ₯~CηβoΰΪY  ϋ;άϋϊœΆ+p XΎ>§ΑλΑσ’Yg|HꏏΆ»εΗfέκJΒnlΚx3->Γp Fΐ :”ηf²ڊlΦ΄(i ›ιρkζΝFσ0rc_>”GΰuΰΑψπB:ܐ»‚‚y„ˆυ§ΜlάΔkΩΪΆHΖΙxτ氏=μ“'i)wG ~+ίƒΆ°“Θσ8U΄yΰz‘>ΟΗQiϊΧxτ΄ΕππŸցωΑΛΝΰΎ4(Φηθώώx¨{Z·’έ²a€ˆ6Ϋε娏υύ ώ|h¬ {€λDρ²ίΌΗC„—ΐΰ20ξx½Ά…ωΐ²­#’~%^Γ€Y‡²/όœ³ZΡN; #Ιϋ$†3α­ΰ―>ζόgŽΑ=qψX±ΜΜκTqmU¬?u4Ϋbyχΐξ5Γλ©»-΄=φ§)Ξ©λd ψLΫϊ8μ޳λς ΰ:9ο3ΰ5`ΊλθΓπ°8fΆΑ‡…kΞΉΞxhŽ:…€έSKd€ΌΕm¨ΞμΈΙ‡2λG`Z ή4}OX}š«ΖΥΕ Ώ<άΠΣ :_Qnζ;aS8~·ΒΚ° ,ώΰ‹ί‹/eΫυ„¨iΖcŸΥaΫΈΔf8ΫεYΣ΅%xΎv5‚8ž§‚‡^Xσ΄ ζΎdό }TΥxί©Ψ΅Ε'Ίuϊ˜σέΖυΨ‰Π9ΣίΆ(ΔώŒ{|΄»ΟΣn}=ψΫ$e˜¦Ώ’Ύ>~!œ ^ΖΒΰΕjΫ–€ναΝp;œ₯‹ΐ2,/ύJ=Ν8.}IŸ νΣI½”؍¦œ^R'¨6/­ΐuώžNκΏύΫΧ ΗΓY° ψPrnm£e§΅Μύ1Nύ†Š~Ρ;†Ωτ'νK˜v%^›‘Ν‡£!χ½AύΧua~P\ίww΄ξ£ΰ6τ₯ΐΌΫy|ύdψ |œϋyΑϊΌπΫκΕά*ΦuΕΈcς|#ΘyπEψ=ψKΒb`{‘x8VέEα>πΏ₯°Oƒ>Ϊ1uζNϋOΑ‡Τrΰ˜<‡ƒγβ]πz°,γ—Α]½ΈJχ€u8ž―ΧΦ™`ΉΪL»†΄΅―k[ ,[ηΐ_lΗgΑϊ]C™#0|Œύ`gρ6±ΆΨ\ψρUχ€]ςπ`υπΏά|ρΝΑlXu’7γΦpΆδyp%l Β™sλτ'>Λ}Ό\<=ΞΏ &A=$ΣΜ1nv­cΧςCJO<‘φŒil‰*‰Χtν‰'tŒ…ίΒ|ΰ~ οιSHζ‘«­κϊΤxτΤmάCΨΓrύ^ψnB/φWΑκΰœΨρ Ύv€Αݐ²κşzHQΜ«―uσr4£iΪ3–·£Ÿ?„ `˜Κ Α‹Α γν°<‚}t\Λ΄ή*5ή¦·ΩlSΔtyόuΒ6Ώ¬Λ9=R?κVv’–0γ>hOZςΜlXΛ¬ΊuΫ/Ηχ\°?g‚s§Τv€}ŽΕDπLρΌΩΜηZNOwέX†ϋΐχ‚ηΚ=°1xQΏ?8~@­3zΪΌ2ιΦeyoΐ>|¬ΛzΫ1oΗ> Η`ό‘̚Θβ·τlΗ;zj­ρΊΠ½όίT~E ’šη)œάX§Cςόύ΅Ν©ςψzwγ]—ƒ‡‰_q~mΎ^ A›ΨnΛ1T…έΤ›rS‚ρΨj˜΅œτ„±7Η>ι SΎαί`'Xμγ“px`UΙ8Ζ–x £λ£žxtΓ|ωxθ»Γ[α 8v†Εΐ‹ίCς!ψΈFή[‚e8Ÿφ'RλˆmZ‘yή+€zSše:!ΧήY°, ίלώϊxρμ ηΑω°Œלm·_)u*]›’Ο iKσKυ˜^<ŽΟΰcΔΛ(σ?(Δ₯οc?”ψͺΫ~©6νΝΈΆΛi“Ψ{Ηύ[ππqΣ&ϊΫώ!γ΄qΟFΠωη"ΣL―ύr½]ž_ΗMΩŽ}΅Ω–¦€ ­CIΊ±ΑυKήΑ^Γ”LΖ efšj²Pk]η,ϊΨ £7ηΒ·‡ό'Α‹AIώ„]kχbπϊόόΊz!œ―ΏΊΜΣč«ΝCςp›wwπ@Ά,ι@Ÿ΄³Ά96σU;Ρ–”YΛ‹>(ΝΚβγ8F·]LΥFtŠx|k¨ΟϋΑ‹Ιό{X.κŒ#¦ΞΨ$Œ½-t¬#icνάkƒ—ΉμΑπΨRηΣθǁΌεΑως§Ωλzρ]3JκοƦ﯏ ΏιeKY SZβ ½8죏/ΪχΒ2πf8C˜ ηΒΰ­ΰγΚ/I%㣞ς6m‰*ϊ9—U| _ Χ‚ιώΕv==u%ΔάΙoΌ’2γgΈœ{ƒ\Cƒlηιψc]“fω΅MΧ:Fo\‰έ~/ ζσ‘Ώ)ΈΟoηB‰o υw} Ώ‡Οƒι_Χγ/ΐφik“΄Σ4}μOΣƒφθ γk|(c8Ν‰Γ’ηΪ’ΪirB§ϊ©77σ˜ nHυS7οcπ9ψ2δ>ύνΰα§θOhω^$Έ‡ρπUΨ<ά‰υΧtγ’$μΖΊΫlρMZΒΨ³N΅·‘τ„ϊ¨Ϋ_ΫVσhO΅΅bxω{πm BΚDνˆε+ƒΖ΅›ΪM―~^ζ/€Mΰ5°ψΨ:^ΐyT¬w7π2}'œ ¦ΩλυΎΌXk;ˆΞXŸ_πΆ«MΪκˆΝ0νΘ8Ϋ^Ϋ½μχ‚m·>ͺΌόΧΫxpνΧ>ντ­†κŠu* «žvωΌΌ¬­Ϋ6~ώ}mŠaΪ­ή΄Χ΄η‘ξΎs^||' ΅#5Σ–rcΖΟρ°­‰λ=‘ώ*±;κζχαeίέ›€βΨ žŽyΖ¬†ΪυσCβ|Έ>©c3τ aήζ$Ιc™‘ΨŒ«7γ±e GΐE;”Y3YΘƒs3έΉhnœΜOΣnά dϊSΰ/—Άγαν½x6šyΪΐά9ζ'ό”Δ<―Λσ;₯Ω9όPβΣ »©Οφ±¦GOΚ±lΫβ׊‘~ρžΠ~G7ŸβυqϋtC%a7Φ;υνΑΓΜρ2άVΗCΙ8Άι±%ΤΧάCΧ6ψ°8Ό€>?‚Γ qΎύ§°,ϊ€ξτΣν0>b{m«mIκΔ―Ν–yρβ±γaM°“AΡgpό#uόΤσ)>vN‡»αSΰ#HYΟΧΟ ±ΌiυΉΩκ_υAu ν£χPΖf²h-­ΉH“VΓκΧτ―-JZsSz1ύ~ο7±›yπpΚ¦MήlζΔqι–Ϊ<έΔ‹‰°,ψՐrP;’6ZΟJ0ŒΤώ8hΛ—–νύ XŸy#ΙWCϋδAuΌŒΧτΞ MSΛχ'ΟE ζQWvcέΏ^ ΆΙŸ―­Λ―&6^ώ·i–Ϋ†c€½Ž•Ί‡‘―γθ£κΫπ*ψμΓSα­πr°ί—ƒm²?>RGκΗ4ζbΩΦeG#iKBσD7tœmΏci˜_›μίzπfψΨOΕΊλcα"8τuΌSj§ž„΅NmJlκ΃ߍ ΆΗuψ"H;-;εΧ0φ„φCŒϋε=oOOΊ‘’xΥ«­γTόši)ΗΗΛχ`aπaΦτΓΤ±Ά‰sΉ68Ά–υP^‡t΄gΗ*cΦ uΣ¦8vΏƒλαpπΑͺ,—Β*Ά£φ₯ΪR–‰ιOΒؚaς'4}(31.β‘ΜόΤέpΦXύΪZ`z.“ΊY²1'ύxx(x€Ÿ„δoB’ϊ›Έκ¦{H<ωš±ŒΝΐrWλPj»sΑ½ϋW{i–₯TΏ₯{ΰψUkZM_Ms}ώ<Έό‰ΨKτΎΡ1υσi³ Άϋ‹πφ^άΆš¦$μΖΊ΅9¦~*Φα|< ^\–›ώEOHR?ΝΊdEΨlΓΡp ¬σ:ξ_€…ΑΑΟα°=βΈ[V­‡θ+Ά³ŠmχςMϋνc+·Σΐ5μcνcΰε’2–Gί|4^ ν₯ΐςꘀμfˆ[G΄;–7€uϊΘυvΜί/%cέΠvΖ]»’=uΖΗPIΌκIdkΪ-Ϋ5o}0ϊυm"΅ŽΨ k=Ζ€ΕU,›‚γ'_Au<‰vΔϊ•τ1‘6Ημ|Έ …;@Y |°ƒf;0υm)[[S’/ύKΌι7ŒΟδΤΕ4“EΝ΅Ωλ⌞ΠAQχΛΓ »atԎiSdσε: -»Y;‡ΔθƒF|{Ιύ ε'4‘κŠίΏ<ˆ-_ۊ`Ήƒdl—Rϋ–>zπ?α+ΠμoΝGr'έ2Ν³xp{©GͺtCσ$n›Ν“8jG7T΄GύίΦi9€—DΗf˜±3t~Ν―Kΰΰ…e_‚!ώώlϊkx+Μ^|λŒjGšρΨ…ΆΑržK±Νa_t5K@Ζ΅#ρqŽώ|ωΨόUΐ‡PΔρω \Ηΐλ`!πR4oζ2e&ΤξΌώ ξ„?‚bΪΞΰθΈ)Ϊ*ΪꚊ^Η86}›y7MiΖ«Ν΄*‰ΫΆ΄ΟτΨ›aΝ]Ÿ₯ΐŸζ‡WΒ’`›έ뎟kΧ΄6‰έ°’―qθχsw=(ΞΗ°!Έ—NԎ΄Ε΅ ΒLΙΣ ;ΜΨΤE€›vγ―fI­mΦXΏλau°―žβΕόcpέeŒRO Iξ―ίͺΗΗ:Ξ‡Ϋΐ_Ή.ηΘτ_Α›‘Ξ ΡN›M κ—v₯¦'^Cυ‘ΜΐdQΟ@ΦΉ>K]œŒΨŒ«;Ύ~Ρxΐxiη"Λ‚ΖΤmuSτzŠΩΛΙl…žΝ`W8¬Λœ2bšJjšzκV_>ΥΛααπj°+‚mhσ*ιSW{Η©εOς5}c7L{c³ mz-ΓτΔ{ꈁœ·CΠ}ψ8Šmp|ƒq/₯­Α ι8 ή!–νΟaXN‚{ }@ν”my2£βΆψpωXVΖυ9“Q³_πJ³ιw ϋσ0xioKΓwΑ…s Ο8x7x‘ϋHΨœ3Χ©σf™Ωg‘Τ³Ηί2ΗΛ_˜¬³ŽYτZžρ2―ΎΈυύ’*Υ_έrάίΆΉ™VΛlΣΙ*Žƒkb‘^ͺkάuθΨά }V¬·M΄'­†κ[υ‹α.ψ\Άσΐ`'pώ“?}ΐΤ—jkκ‰VέΜ‰χ *£,ήΡηz:m‹.6Γ莯_ƒ/†ηFIzΒ΅ύ―>n$@Ήq0ψEυV8|ΩWΙFk³%Ν°κρυΠPΎ)χ5θ ‡EΪ]ΓͺΫoγ΅sΐiobZυkκ5έ΄HΣ/ρ„ϊ5ύ“·†ŽmZΦν%\Gx58#η@| -~Ω _ΏBύ’O}–χkπrZv„kΐys Τr‰φλPŸQ±~EίχΟh!|ιOΓ<]QΛpνgΝ¬žxΒ6»γνXΝ Βώ°μ {/2ΛΧο•πΈ\»Ϋ€cν>±Χσ’νAπ±f>Αqξ–λŠ€ο ΅λ?žˊ¨WΏΔκ]Ώθڍ[ξΟΰπ -εE7bΨΗ'`o°ξλA©γŸΈΆ6{MWWβηΪΎ\ΗΒ―Αyχβ|| g-؎΄Ϋp΄ΰ:EΎWΚtŒ@]HΣ‘mvΝ’u’·-ތ­Ϊ―%~έΨΤ³™βkάΓa"όƁ‡ήγ°ό<Ψ<Έτ ¨IάPI؍=ϋ7vC7¦m?<8lσΛΑ‹o-πPQ—šΟΈ΄w­SMύΫτjkϊXZ³ΎΤϊ«O ν«8~φσ} h;Ό$ γφyΨ v‡“αΨ|88GŠΎŸ‚εΐωρΰσΰM[œ»”©―x ¦¨3,–λΊΨ~Π+ΕςgTl“ω½T] ώz5#bŸfώ΄/aW7ΧΈse?mΣn0ήƒ’Ο’°ψπφΡuΈv}(§Gΐyρ¬Λ‡ά£ d>šGω \€mΚΪDνΟq|“Χ°ΪšzόΎŸF/LmΙΣυ΅Υ²τuœ–ΐρ_ΦεN8 μ{Η‘Jζ Άš^ΣΤ­Ο΅|άΏ€Ώ„™~0Όώ>@š’OνkΥυMίbO<ε4γ±ΓF ‹q—aRΊΘ’V]χΆxl).ρ„±'Τξ¦Z.βαυΈάpŠ›'’ςͺ-i ›iζ©6λύ3\Τ³/O(+‡†ώ©'z[Ό¦F§Ψ©ΚΦ¦€όͺΧ2;N=Ώκ{ ›}]˜DΗΤ///.Ηw"μ “ΰ‹°δBό+ϊe°-86G‚‰c˜φ‘v€Φ«AW>ΖJšuΞhΉi«γ`_›Y)Φj=Υ–6ωPΌΔ ^tŽγιπ(^2+ΐΑ0œ;Ώ†έG^²·ƒϋΚ‹άώm /νιS‰uŽ‚ΛΑΉSκx«+5IO^ΏžΟ†_‚νŽ}€·ΎŸz$υΩή•{FηnW°Ÿžωλρa d\»±g۟ΈιρIhš{ΕΗ³bي{ζjΈ μӏAΡwsx؞ψ£v€ήEιCνΏN‰ws΄Η“6 G1ΩH£pλ]²(ˆθ ]ΌYœYΘΥVΣ’'oB7„_Ψ^$Κa>ψR/tξ€·ΒŸΐςΝ“ ™°n¬”[_bK˜γ±ώόχ֝@ρuC[ΎτuŠ|Ζ•:Ά-ώ©§†ϊΧψ΄τκ_uσ%žP[Ζͺ“Ψϋ“ρς²>V/uΏŽ^ϋΐ‡agΨLw,σfpnLά9Ό=X-?u N%–e9“ΰ)8―'˜)©Ξι-ΨqσσkξΉ’Μi³~ϋι₯bΊγ~7ΨΦΰFX&‚βZφrί|θΉ–―σ-+ΐ}—΅‹Ϊ)?αƒ(Ώƒ…ΐ}PΫ½†M½§ˆŽdoΪ―Τ―M3bΗΤ·ΥςνΟΊΰz] <7΄9NΧ¨γ™ΦΊ©e'Ώ*ΈOξ1Ηυ~0δϋΰΰ˜- ώw KΒ" ϋΗώΤφmΫΩΦ–8›–τΨ†α€Τ€i˜λ8EOX7«ΆPνΩ¬IK˜jŒϋ%βυŸ=γ΅„/\· {ίϊ+n(7D0ξ#β π »š’ΌΪӎΆΠƒγoΰζυqS >>.†Τ‰Ϊγ‘i₯ΕΟ°Άg=>ma΅U½–νΈΨŸ΄1νΣξπgpœνγ;αM°5ψ•οά韲…~ψσr. Τ)$υLalD,OΏεΐ1Ά-C<x$žP{tΗΥ/ταΰW§βg_ͺϋ%ο<ξϊί›σκ©d.k¨žx|o†¦+±wc#M?τŠή kšz-_}"xιϊψ±O{ςeψ0ΈΗAI~/ς“αEπj°^ΣjύΙϋμ–λΓΒύcYυ¬3Ύ¬«ΒΑr²§P;ρE έ{λͺmjΣ«-νn†Σο“ϊPZF ›‘%ihκ@ΎΡθ#…¦Υτθ½βZƒ,ή$_²™@ίn†lΎlLS,τ•‰{ωυ>3bάΨžnNΕw€?—/Ά-νKŸΫBάϊγ½ϊi«’΄ΨŒ+ »±©γ±ΧΠC|;8·Œ™‘iφΙ ί~ΪG<ί^φΟΤρ? μ·εω8σΐΛ\ vΔ1kΞe/iͺ ~’b=C™ΎΘX'4wΖΤρT ޝ}α€βΧz—€9^‘[QσΊκψ«kσŒ»¬)πΡΰΓ¬Φη~ΤW[@νθ–ΫhΒδ3TΜ3”#ΰ"JϋΤ…½†κ’1Lάβ§iΪ²Yb7>.Ξ„n˜ί€‡Υ= _έdκM±ŒΡHΚIέ Ν[u+γG›€xhx!.ͺŠι5OuΣΖ/yβ2’d¬ͺS³―Mγ^|'€_)†Λΐΰ 8 ΌΰύωQI[ΌήσΓ'α:°ύι—lκnΦI)iοsέΈΆ5;­6yΘύ°#džPϋσPυ:'Φη%fθcφ…0\>bΧ‚3Α2#~©w‚Qž¨ωΕΉdL›ak˜ςŸΑψf8κ%˜>6l–ς mγšΰzχ-Οχ±β―!€_Σ³¦“Σt‰g”s£ *Γφxnψ¨ψ<ƒmsLŸ›ΐ_σΰpLS^ڈ©#Ϊƒ†ͺ'^Γ¦n|(½hξp`Ί#Εg,zšc[ΧϋΩEΨ΄Χόκ9,\όΗΒυΰŠρOƒ_Šεœ'‚›’­\Μ%υ4RΞy$μnNmMm΅στϊ5δ†έΌX_y θ›qIΎ„YcΖ „*ρ­‘ιζυ«ϋ°ΝβΝΌΝΆγi{lπ ώcπmψΌϋŸό?B_<π=ψΝg{,'>5ŒNς˜KΖjf vΌμ‡‡ύΫz…UΩ½β¦+ψ,ήΫΓτ΄Αυο#νZhϋωΈΞCtCq~ηΙΰόœOρ1p'Όv†ŸƒbΫL·^‹Α₯ΰει£α °ΧaΦvκΕΤ_+κU,ΧΉpΏ άχJ‹θ†mθo•šΗvϋfϋ_>hΌ€Ο†a)°/ξaύ«€¬j­ήγ±Ίο­Σ:ξ…‡zzμώ’°ψ눢ˆyλXΔ[[<ύH¨OΥ“gλ@ΟυƒΡX$uΑ¨Η)i±Υxc3d!«»ω‚·ΐ=p"μnΘ* ;?^²ΥN΄U¬/uVI[΄{8xˆ­TLΕάQm/t$ΆΧŸ_O‚―v/hœfˈξα©―‡al¨}©6ϋν%φ3Xž”Υ¬K»6σx8lΗuc°­ϊ8žŠΊb[lΣnΰx[ŸeX—b{j›’§ώŽΣώ±ν^\c!–c_Ύ ·ŒE3Q†‡Ώ_πςΏœŽrοϋαΥ#δiΞIζΖπA8Ξ…¬ΗΕ<Ζη‡Λ`IΨ–σΩ^Eί•α0πςώ3ίύόωΪ5”2Qϋ’vΕΰCμΰž±nσGΤ―aΥγ[Γ€kscη}kHϊΠ=wά·φΝtΕtΫYCνJ³ύΪR^Χ7ώ±UΗH¬Ϋž*yΟ•σa;8lkΚCνKΚ6-z?±‘Δ'εθ½α:wFsΝ½άλΊ°Τ›qsƞ΄„ƒKν¦Έχΰ0ΘεΪ±*)/‘ωBΗa„?ζ©ΔU››‡Π'ΐCIΡmίΰmΪ‚pΈQ=ΔήΪ—ΩX)Λ°rζΥφ-8 ΜηAU%eΔfά<Ώ…ΓzzψΑπψΨΧEΐΆΨφ‡ΑŸAiΓΗΠm‹mΤ–²QG₯λ7VβΓhoXf τΐυΑ³?ό©W^ν[Ο4[Η~π€ŸqLK·„ϊ»Ζ]ΗW€sœ4CηΪrύ W‚’νHπηϊ«α)PΌ€άί/―ΐβΰΊq_ωP,;υt ½?ϊY–~΅?mΊΆΨk{Bά:~οEα-ΰ%λϊρl;ϋ¦n ›ω1υλSWβcΨ&ƒμρΝ$Œέ€γ—ό7χ—ίΐφΰ£<ι¨ύΆŒ€'ΝPIώ„ΥΦq˜ΫΈh†2υβʘΈp²x’7Γψ&Œβ†Ω ]θnŸΒNΰ!-B|ΪΚ!yD”§ΩfλλJžψx@ΨΎ}ΰXPΌΰ}°œΙ» ϊ’°ΈΧσZŽλͺYqλ{ξκι}IΏc0.ΙηAž;e{©ψΕ>ή ?†γα°&DξDωπpτ ο\X£€}Μ埢$$iŠyYŒψχaEΖXήDyΗ€γ9VRηcfΛτuμœƒΜΓhΛ¬πhσΔ―ΞElmaόϊrΖfέ9wM_η€ϋPίOΒ…p+œ ΒΰΌεFπ:VΧ“λυκψ€ԎXΆu6₯Žg›[BσGw~-wgπ±·lΆA9ώi‹ω”δž°Ϊ΅)Υ–όέ”©ΦρM υΆ½ΩkΖ? Ώ‡Μ“EοΙΗΉ©R뎞Ά5CσUŸ”[βsmXσά:u14uγM§ψ΅…mΆŒ­inJΏςXΏXO$υž°iKάPi–Σ΅vΧyD¬C`Ÿτ}5Ό §7ہΉ_^­?φ6[¦=ρm†ϊYο*0<ΆΙ3DίΒΝ=}5BΗτ5πbψΈg›Rλ4-ρΡ›eΟ5q³‘tG Ή€šρŒ“φ -~ γ—Π…―˜ξ—ιΰBWό"ώœ¦7₯Ynβ γߌΗnhZkXυψ*^²9@샛ς>8<ΨΌW7ρ;ΐ΄τ΅#Νς5Ίζ²ξβί ;™ΛλΆ=ζσΛή άCΓyOπ‹Η4ύξ„oΑ8πώ#8ΆΩ‡—α~ΰ!ώwψΨNγŠm ‰Fτ›μ_λΥ,Ε5‘φŒeΉcQ–?₯ϋXύβX6 ΛȜԹ̘jkκυΗαΔ^›ž!t_ϊΛWΐωv zΉ~\3»ƒλK›βΪz9œΪ\§γΑϊ\kΟμ ԁτ©ρθ -ΔυύR°O@>"Ό8?ϊfΟ%Ÿa˜§°Wβۍu¦¬€Χ΄6½ΞEΥΟΐΩρrάΔ½j_€£Αώ΅Υ[ £“eͺ<¦ JΧ“,ŠΉγtΈ.†ζ’HΪ 0γeΊ Ω/ˆ*u«{xxa^ Aq³ΊΘOœJ¨ύ «ή”΄·†ƒΪ:½vλ2O.έΔm›‡˜€Ί>^° ΓK 6Τώ&Σ§)ŽEν§q%γ•x.tΗm1Ψ~ ^ΨGΐ+Α4Εγ»πΨήΫf?όΓς…]Α‡€vο+ΐv6λΗ4Pτ·nΛ¬}˜a:l‡—ΡX—;MΡΥΓΨCΩGʜ.YKΆ3zΒ،‹sz+ψkΫyΰeξΊ:²Ξ\3ΞΝ|ΰCή}»lο…ΐ³@1ΏλτΟπ[ψ(Έ-ίu£ŸυViΖ›ϋ'ρfθEΏΨ>λέfηΚ²bΎ6jšm‹O΅W½-½Ϊτ€O†ϊDb7ξXœξΟ_ƒxΫαXΏάχŽ›σΡ””YΓ΄₯š7Ά”“|‰ΟU‘ƒ<7Jτ¦>(½™ζΨis³y0*ua'ΐ‘{ΑΕύshζΑ4•m€ϊυWβΣ Ϋ:zy’n8šx>ι'ƒϋ»xΰ¬Ή°¬{Z’ώΦ0Ίy-Λvψ5u$ψh:Όΰ‡ψ>ˆΎxΐ}τσΠπŸ$l‡σ_yb'ψ<ψ °/ζ©ΏΙ‹Ϋ{ϊ8F—ΓΑ‡yή ηAΝ“Ό˜ϋεΔfΨ¦λ«ΤτΆxΗinψ3α77τ?‹€Ή¦΅@j>Ώ„Όœ…\N.TQόj^ όpA+~iΌό2¨R7Θ¦$xˆΈ‘«€ξΪΖͺW_uΣ2ΟΝΌ5ί΄;ρͺ{Α~ rΐνŒn_ –WσBjZSΟΚΓl/ψ œ ^ΛCδΗ{sπ =š_–έ,ί―ΆΓzΎ^ώΧƒsδIH%φ3s6– ›eŽeϋΖͺ¬Μ³εeŽjs-\χΓM`ΪΪΰŞ=„Ϊ‘δ3 α-ΰΧΎ_³wNEΏνΰFπΌΨ ^ OΒί σ«Ÿsb1n6†Χ»qνγΐΌΦrP¬σ$°Œf9˜:ω2ο5lκΖ›ΤόΥ?vΓARΫmž}ΆΰƒΚρ?΄[ΗfΰxϊΰŠ ΅/mνˆM'υPγκ‘κΫθ°Ή¨Gw–Ξ5@ϊϋHaυnθeς ψ1ψeœEδΨΊPέ¬η‚‹\ί+αƒp˜ξ—„i͍q 67±φ”YΫ‡ΉίŸΨ­³IςjWO˜<5$Ή_¦z€ΆΝƒνhπΒΆ}λΓ𗍴5ώΝ—~?ΣŽ‹ž„‹ΑŸ_ΪŽ—6ύ<8/ω@Ϋ’qDν‹υ}žϋ―Ό…β―ΞoΚ3­κΖέΞ‘™λmkΈ όUΐπcAΡg%p{6œϋΓcπψΙ:Eν‹kυPπΌω;8g/€qΰY²¬ϊ] AΚΙόVr$4­κΥ7ϊHιdJΒnμΩΏΆ·)Ž‹cμιCμ>8μγΉ\ AΫ^H]5ToσvγJςvcΓ:‘s£ΤIΞβΘ8Œ6-ώ^@ΎZ΅‡‚xl§γ,ϊ} άπ.τ¦˜/βA²΄ jWΪ_ΣSFς%-aμ5¬yFsΐ}'ϋnφΞΞͺάΫ{Bο-…ή‘ͺ ½"Ψ+M―b»zνέOP±`ο½^lXΉzν›J“†„"½§Μχ<{ΞXΩ9gJ2“ΜΔϋώςΜZλ]οκuο3gς4˜ΝE]Ά'mΡ&ύ΄!~βΣα x¬ŠεψTτ1πq0œwΓ$˜Ι§]9ΡιΪξ=aW°ίέΰ―„€Η;’Εϊ― >)>Κq$8h1½‡Μΐ[©—4ΟV6#ήΙΌHEΦΧιψxΘzπμΫ‚kZIšžPΟΟθβšφ)ΰΰ…ΟBζρŸρ'//±ΞΝ/ƒsώSΰόΆ|κΜ3<:°XΖΦΰ<ΦοYζ£[JsŒ.έ¦ίpσŒ/Ώq‘τKΒ₯k^‰Χu^ ηƒsή·₯ι―Νπ_λƒbΪR.έ―­αvΊδ“Έ„—[ΧIφο Ό©ΟΐGŸ°}Tϊ›}ζΜb³O½ιΏ ~Υς›φtx;œښf ’ΊhΫτ[VY/ΓMΪ₯KšNi”,Κψ [o~_/ΪNσΨ\AͺΆΙί:™ξQpΓσϋ*Ψ'~œ°€?Όψό^nrο7aΣ™>ωΰΈ™˜ζe…΅ωϋΆ&›«QΦW”Έ=‘Ύ¦aζ†5 ,s(Εωrό|cbύR.ήA‹ιΝηY`½•τOhιόl?9”Ά½iWŽ«ώ„KW=ΰάώ.LbίzeΎγνSΜΓtW€o™\Φέ9| xΰo οƒΏ@ϊA»‚Ωπ386„qζy?¬Ξ]/ 3ΐ±\\‡Ϊ9W\Ÿ–έNšύ’ΊθγOΈΜ#qI“1I8ΆΪ5γΚόη±ΧΞΉ¨νupΨ¦7Γν`žΆΥ΅ΆψPζK°7½nIlΚψθt•Δυ„–ΣŸε-§M\h 3¨₯«Ώι“ΨŽmββΊ ]Θ.ΖwΐWΐ ν"ό9ΈΨ/m΄mJY†q)§λF ‡ρŽc§±Tί.uJܞВ?mCfό.Θ9ΰΫ ΕƒzSpS‹ ή:oΓφΙFπa8ΎGšΰb·Ž³ΰ(0γNϋ*°eZΞmp8d“ΐΫQ,Ϋ<ά(λα“—@½ρJܞΠΰ~¦Œ;IζΖμK}:Έz6@Σμh%^’:·²¨ΏφvKKΩuΜ]ν+Χ‘‡η7` mΧn p¬-/R¦Ώt'φ‹λՏ¬γS`'˜$yΏ—WΕΆκ·a6|†mΰ‹ ^qο _€³α Ψnσ1omΆׁsΪω­<ΌrIH=P-$ιwέ Aόq­oλ©N%ώ€QηΊUJ]ΆΣGR_]Ηνpάm·³AYΤοe^kIΉ©cιvς›0qMιςφ£μψε­mΝφd`›vΡΕί '―θ3I£7,>!Ό>nΪ{ψv3qqΆ“Nωj›8]q™+@txkIΈt§«Ύ”f8qi_ι6ύ¦ύV+ π-ΖΈ>©»`}εωtψ1\oγsyΉ/`;Ψ΄σ ΗψΨ€\σ[A}βπ."ΖYGΛψ|+֍Λρπ ―Σάο+ΟV6‹8ΦνbΨΜ»―C‚θΕ–Nu^μ —a)ϋΑ5Ρi–ΥsmωΦΖ±ˆ˜οΚpό#”vγ]\ηή•ΰΌώ˜ΞΉs4΄;lPH’Ώnςτ Ώή ŽοΑΥ`{ς§ΒŸΐΉu$xvύ{Ϊi0Lo^gƒm(Λ#ΨΦ―4ϋέpSW?Όˆ­–ky±OZuΚΊπ4HXέ`ΔΊ‹ωήΆί1}/\Šu9φ€μ·x’ΤKe³Ρ•ϊθt•Δυ„–³Ÿ‹;8£‘ϊψ jlšnΪΧ΄SŸE7o»Μ_ƒχO.ΐοΒqp)8y³yXvXκ‡ͺώNΌ‡©†‹°i‡ͺΦ5랰ρ$mΣ-Ρ>aύi³‹ςο­° ~+pΣυiιψόΌΨ?ŠίΒΛa{x\“A™ ©G­hύ0›ςξΰ¦nΈ“$½υt“| x)Qή–UΆG}θ_±σα”Α̟α¬G»Όm{_c’4™‡^Ό09_’‹M'χ…D?@{η‘Oο‡ΐs@q½Dڍζ€OΤα+ΰ2xίUΑ΅άNΚv$―vveΩΦΙ΅αβaπBμšωά™WϊŸ…‹ΰϋp ˜~'˜ Κ‰`»mΏ’²šυ‰ΎΗͺη§ϋeَ&Ζ9ΎOΧαΖ N^¬τ­@IDAT»ΈxλπƒΈί†ύ•ψτnYλλήI΄υν‡—1ϋι£p˜―}χG8 'uMiΆΕψθΪω£ΣURοžΠrτ3k9jRέ”rΐβΘ€ΗΦL⏫œ΄ ;!ΐ.Θ#@ŸΎ_†λΐ…γζ]ζE°u‘₯κΥ›WΔuqνΊYX₯κ^Iš^EΓSΆ'ΆΡ%Oέ―ΈρΈa½\€ΆοhπP·~™[κ­§ύδ“ψgΰNpƒ2ϋJ7€”w1Ξ<”Τ΅'΄πΟ27ςχ‚ύ/yϊŸ„_I>–―”i{4iΪΎ6±η4ϊ,νǁΆ=}μΪψ8+Ιc ι<(šbϊŒ}ς2μ±>ίηΘ1°\ ΞkŸΌŸ uηtβ]DΜ――ψ2vΞA]Λuνό|sαzΩ>»€6ΧΨΎp4˜Φ΅δ|)€μ²όI[±νζΥι‚c"λδEμ0OΛRtγw_x˜ŸuRWΪΩ‡ΚwΑ½αrp JI½΅ύόž_/iϋƒ6ίΣώ26I‹ͺ.Ώ¬oώΔ'λΏω-’X.ΣjD&šΑψΛIΧτ—v­,J]Σu2ˆ“Π'Ϊ#ΐE(_ƒ―ς\Μ©ή^i§λlyR׌“a'ωΕpUˏ³PώΙΧΊΕ―M’φh§_i§So7+7Œΐ: OkωqκKΚωΈ―λύ&Έμ“­ΑΏן €-©·yYŸ5ΰΩZβ˜Xgλ―¬ΫΤΎαϋa½Rχ%­AζΘ’ζ3ΨτΆΝ~?~έJ<φ&]+ΙRqΚωa†ΛΊ&<ύκ­ψ›pm›ύλφ1πPkφw™Ρ‹-Ξ͝α°J+— qw… α‹p=hg™γa Xwuΐ:π Έ75λ‰j1ΧσΫΰp'qi›eάΏύ‘FΏi}OΥ5νΛπΔ»ϋ»DšΖ½ζΧ |ά{Νί±ωΌ l·RΦ+αΈΖυηO|εύπ¨w3Θ£Ύ!­dP Ζ_N‚vώθJ·LίΚz!Η‰θkd'ψ)phΛoψΣΰ&αBρ`saIΩΧ–₯”eFΧ³pύΥ™^Λ–„“..Qυηƒ+γΊ(ϊ“δΧ]Ώnόζa[ »±ψZο­ΰβs1ΖEξΒσ.Ψφ„οΐTΠΞEξ]δ²ξ΅²ρ£ŒΧŸpά†y4Ξ'§­ΑφxΉ¬§}g=Ύƒυr#IΏφUΧ%-7c³€ω,nzΫφΈ~Ψ/ŽΑΛ-%σV½λΧΉq<ΚΈ| `?;χ_ƒNc}\LϋνR/ϋd8\†­£—ϋΰ πv°Ž=/Σ/΄Ι³ΰ§p<7ίΨ6Λ)!X‹σrψxxkΣ¬Ώαϊ<oœ:λœύΗ°eλFRΓϋΐ―` {ιμϋί‚γυc8¬ΏνtύDΛoί€)[·„`oΈ?:]%ωτ„FωOey‘r`βΟ@ΫΖΎό‰O_$}ΒMΧxu7†=ΐIζd| nœπzp1> n Ι»¬κ^½~Εψ,š€‰>nτqΝί§“wΒm0Κ ‚`―” °WΩςq!»Π,γIπ°MΆupΡZΟΜ%‘OΪ†+Α§/Kζ#Yηΰ7Ώ9`ώJΪ ψΓ6»I|Έeo>?λ–φΈaŽŸΆΓPJκώ\2΅Ώl·e‡Ψ~ΌρOXk8 θ#ΟΜ£―bσ¦–]9ϊHΊL£Κ:6ύŽ“σZ½kYχDpŽ»Ξί₯8ΦΙ#γή΍L[ϊΝc"Ψ—>…ίŽmζ¬ρλ€—ηΤuΡ&ω―‡yΰ“Ί―Ν’‡DΜΣ°mώΌ\κ%’ΌΦU}Σ_ΪΕ›μΡ{±QWζ•ΈvΆεί΄ά3pέwν;ε`Ψ±rOμ$©ρ}ωŸ|RΟ„G­ΫˆΡڐr@βοk@KΫ\†γoφ…‹Α8Λjp5l Š‹μΰΒuΡΪ―Ϊ™ΖEενΆ<ˆΛ2τ—a‚΅ T{]Λs!Ÿ.‚l xk1>Xύ|Ί©oκγEΒΈνΐ ο πΠ~1δ"cΊΣΐ`.Β§Β†-?NΏn)–Σξ@Lω±M?4υ‰k]".ό­ag°Αη!εi;―₯?7eΰr±?n7eλ5\ςc2ώ2 g}Υέ>-ϋI9WΚ6EΧ8Ϋυwπ­Φ5ΰΪ žΞwγK»‚o K)ύ…K[ύΪO†²κ½ l)γόΪήφΎ« eϊζνsΰ!ϋmx6¬ ηJΉ– .2†©C\m”δίZτgκXΊe:ϋ”ΨE—όγF―k]LoΏά/ΧΈqΆλUπ=°})«,#ωκfή6ύ±/mK?IG·€α£ΉΫΏξ@ύ±}§Ύ0ήΙ΄6\ΣΑώ»>ί„{@;'‘‹Υ ϊZps΁[–ΧτcΆ˜OΖHΫH'Ώφ–s*ΈΌ%« xk1ό4Έ<(] ¦³}ƒρ―€ΣΑΌήΫƒνQlη{Α'ψΓΐ ζυ`]…η€εLΘβΓΫ+Φί2Μ/m‰#ΓΡΕ5.ώΈ±«ήΝν[`ώn€ΗAS΄3ή:[—‘–δω#2ήnκŠόlΛOΐv:6K*Ξ 7QΗ±S_Υ+Ά·έXχŒ@OΖΘͺΕίt]7€‡Μ9 Ψ?Gƒ}cΌŽOΩ?ƒ5AbΏ•}W†K}m\ό°½|+Φ'uς­„IΣΞ„M[ώΏβ^ ΦΟuΈ7μ?€  XG/ο'‡₯%ιΑςl‹βΎU–©.ελo'Νv•m+γ’Ά©λd}_φΆΛ=κΈήφΏνx.g8otπφŽI™―ϋ@Κ‹?ρqM«Δ©ο‰E?mθh– „mˆΏ”ϊ.Θ0œLŠ›ϊ±πCπΰ,ϋ3eOAŸΕͺΊΎ†#₯Ώ©K\άΔwr]τ–«dρ6υ½ΔΉ`.Σ±ξΣΰΣΰbϊ&Έ‰Lō‹ΟΑΰ&§έlPV€ΫΰΎρIcup³μTοθKWΒx{ύνtΖ7Ε aΨl“α/€ulφͺa?΄šν±Μ‘–ŒρPδkŸΉyΎΎ^ 2ρ.wnN€?ηzrΈ^ξ/Ž/‚•ΐώr­½|8pΨΝωΪ c²ˆΊHΚ.έ΅ˆtMYώž`ΩΚI`½ [Wύ—ΐ‘­π;qoΣ)λΒ!ΰΕax1pέZoΌ(ι‡žΠΒαvqeMS†υ'¬[¦wPš6ΡΥ‘ψRg^g}+Ό܏•α2HήeJΏΆ±Ρ―$>ώfΈ6Ν?2Fcšƒ‘Α‹ήΆ5ύ†K}βΣ~Γ%κ½=zψ»ΰά ^€¨…°bΎ‘V4Β)7nlΪΉΪx0o9χŸAo»Ύ^¬³γ'Aν\Γ‡ΰωπYπΜ‘»ώ―Βα`Zσ΅άΨ₯?Pυξ7ϊK)mšν2NΤKόx{χ䀉M֝$/γΞ‚λΐ·5ο‡;A™^l«}γx5σN˜¨ή:φεO\ά2½ΊQ!vΔh”²³υ'ΪeΈτ7νl{$N]D ΫΫ²¦‹Α…ζνϊΓπkpƒLxͺKΒΝψ„›nIιΆν”Έό‰k_θχΐτπ Έ0>OΎβ&ζf°%μ Ηƒ ΛΆ»ωΉ ΝΕež¦uαω4’OG»€OβnTΆ΅”2ΏnόΪ–αθγ&ή Ψ1`›¬χκp$(κNλšz¦/PΥ «Μ¦Χc1|?ν'λUΆiψJxΞφΙ£p%θoΧG¨— IΫ:΅S½σGnj6σΨƒΤΓW»!{ή^Ι\R‘?Δ αf‰kΌsΩ‹΄—2ηζ‘ΰΊTŽ…)|βΧ”δξkηΐžΰΪωxPšΦz­―/ηΒΰ~\cŠv₯τUni§?{IτΦ+’|K7~mβ׍?iοΪU`³@Ρώ Ψ μΛ€oη–:ύMP-’^έ¨b4I9(Φ»Σ Doϋ€ ΗίΙ5_Ÿ$½iΏΎ nΎRώΌώN υ‘2?uνΒ₯ήγrd1wZD₯^3Œj‘6νN$ώΰΒψ l Φ]»;ΰƒ°lŸoΘβ“Ύ›OΚ΄Ύρ—.κήΝγχψ½Ϋ&Ϊπ$0Ÿτ ή^tqΛt–ο"ί|γΣΡ#ΰλP7)λo»”Τ΅'ΤŽ>Ί„wΖγ˜μ ©“qC)ζ«ψ4φ p^βΝ2cβόˆΔίt-χNΈΞ‚Μηηΰ_Ζ€RΦ/~]₯ ›ΏκTП8Ό½’^Φη·kκ πΐt? cλβΪΤ͞”΄MW;σΡΞ}ξVx3lΟ€oλ^Ρnwψ-œ ‡Βƒΰ%Β:X—f U‹aΕ66γκˆV\ϊ t/ΓϊΫI©wνϋ vQΛπCΈΒ$°ΞΫaϋ²ševL£oŽoτΊJιΖί3‚¦Q#ΈŠuΥΩΉqK]9`νβλ̊<.]Σέ „Š“άƒνγΰ$zΈpΣΝ² GΧτUΗ™Φ[½ b;°Œ€Α[K‹ϊψ[QuΨΕ¬Δν υ,`7 C7­οΑa0ΜΗ²/…§ΐ ψΈ@Τ‹m–”©[JŽMβ  ²°^Œί>[Ϊ‰m“vŒkΪ™―εΉΰοƒW€m΄μλΰtπI-’m€τGgΫ―…_Β0άΞυIς|3Ήl7-QnOμ†'£]ύ΅£9_ΪΩ'/έψc—pβ<0/ηš‡‹ωOƒ}Z~Γ)3ώΈΞ₯ψρΦsτrάί€Χ¦€lυΣΑ9½"<ι‚ωΊώΝϋυπLhέΤΏΜSΏ‡‘ιά{<Π―υΰ˜ŠΆλΒΡp2Έ§ψpdΌl›G))'qϊE[)γ,Ώ“$οΈΪ%¦ΏΜΓ<―ΗKϋ―ΐΉΰ^aΩΏ€η‚}_ŽMςNy cΆˆΊ2>i’ΧΡbΓGΊ΄λΤtzι¦-ε`–ώΆS›NˆΟΓΫΐβDr²x8 Ό+@ςΗ[KYΧθβ— ΞΌW‡ΣΑ· ΖΉ#ΙK7zmJŒ’υ^ž^VΌΩΏΌ¨Dΐσi˜;Αί yΰνΝ[S,W)ݟ8ΛϋψΩ‘‡ΝΆ°9X·HΚԍ路ӧ͸2œ4MΧΥ2Νγ-ΰ៾Γ[Kκœpιjk½_³Αt8Εzϊf m[’z8―•Βι`Έ―M—θ₯*iάN…―DΔΰœκO<,χ…iύΆβ Qœ#νζ‰ρχ€kλlˆΌλ/—^ΫΡW[Œsžύ Žέ•ύσψƒπwp¬;«ύv*xP½ϊ²%Ίw˜œ{柴φ£{Β° ά«ƒuί ώI›t¨ΪŠkώ­`žε%½4v~l ξ7ΟΕ:~Ύφυ΄ί~Q¦{‚yv’²~₯MΪh™βΈ(g·XχΙπ2x&(ξ=ξ•2΁ΟΐEΰ%a2X7ϋ"γΤ|ΫSJΣ¦ §ž₯½ώΨDŸ°{ΙυΰΝ1ϊ6άΦΩv~μ7Φ/?)#.ͺZgΏΆΣ―”q†3vu#Jš?R*gGŠΞM|ΧN—τ‰«3lεYκ Gαΰ"s ]θޝ0ή΄3˜N{@)σθΡ,Zίθ›ω99]ό™ Ίργ­%:]λε’sβ*Ηΐ p*λ‚β’s’Ώ¬£ξ`Sgσ(ΛJ9¨ΫJlK·τ›(yΈyό ί…χC™gόMΧ'έ‡ΰ‡ΰ>aά+ΤeΆ›·₯Ξ<§@Δ‘ͺΕ°σΧuσΜ/κ|“σs°μˆφ7λΠ—XnΪSΪ5υΞqŒΔ2£η€—ΓmΌ¦γ?ά+gƒ—„δ‘½FΫΜΕ€C5hI_•n2‰.aΛ±ώΦυŒ–WΈ?ΗήΊ½ ΎΩΧρΆGν“Ώ~₯ΤυhN«.i?bά4bΔT¨ΥY©§trΛΞO'G§ςˆaΕp&Έ“βΐ–ΞIϊ)ψ\άαΤφN¨N“7υΤ6’.φqWΊΝ|“.’šν9ΎNθ/Γϊ h|φυ_ŸΖm£›Tή^i–ΫΡΖ“ϊΗΥ$ώ΅,7 7HeEpσΪ ŒkŠυZ lίSΑ~ b™φωΏζ‚›₯oqlsκ„w!ι€_Θ¨M œOm’‡\΅?9?ΔΉΆ›£C\Δ€³sσU…w‚—ΐθπΆΧAΦD[”Ž―νΌ ϊ³M8‡ΦUΗΉέΡV½iΫΏmν7ƒύΐ„¦˜.4γΪ…-ΓΉΌ)XŸ^_ φυ{ 8Ώ³žSWΓργνS΄kgΫΤΗΖΌexrΌl³υϊ ό ?ξ='Βlψ(8]χ^Κ΅±ΏΪIʊΫΞf°:ΛS,Σ=σ °ο~ ίjωνΧ#ΐvΨοΆQ1MY_ύ‘ŒMΏn€τG·LέN±¬*•JηYψu­o9(}ιšiΛ|βΧF9œ€ƒ“ΟΧ†?7Γε„4ν`€™Ά 'οMή–γDυ5£O Ÿ/#Φλe`œιάΞ†½`ψO°=ΖΫWnHε¦Z–₯q%ω˜>ω4έοηζεΖq(ΈΙNν2ΊΦΣCωπF°½e|ό¨υnF―₯΅ΝΏƒY`ž‘ΤΛpιOό@\Λ²ΊKK£ wΐ:CT m_άφ¦ Y£Ÿ!‘o¦”Ύϊνzβ}"Sϊ«ŸsΉέ%²N\όH>ύΩfmΌ΄―€θΜ.}VΊΉ+ΑƒΒΛ¦ν•ΜiΏmn.³EΔt›υΩΆηχΝπ[°©ή~ϋM›v’<βΖ¦ λWœφ§LυξGŽρŽπjπR”K—ύσ*π`΅Ώ|9 †δ‰·ξ£2¬Vןτ5ΗLkŸyQ9¬Yπ)0mςνΖiΰC„ρJςΜΈιγ‚ΆΡΗV]β“O\γ–ΉXΡ‘"ν:&gSΧtnββ&>a]%αžPΟOuNf_9ž›Bδαω\Ι#qƒq›Ψptρ'lΎΝ²\<Φοp+όlΎΐLΨ .„{ Ών+σO™¨—HΚ<ˌ’λb».uΆe{˜ή γΤb»ΕρΣΞ ―c‚½μ#νέlάΈ½h˜ξ=`Ων6rΤ‹%–kY‚ύZ94λR©σrs#ς8šΔqpϊΖη₯­Š«Ιβψ6먝¬‰ς0fλN9\£Άέωπ.$M½α¦¬…bΕ–rw\χελπ8x` ‡Ψζ²έ G—°ϋ‹ύeέ‡Α/`ΨΌωΆFΡnx:ψ γΫƒΥ!2?άδ_¨ΫΫμWσVg½NiωŽλ% νΫ_ ϋ§φ% —~Π―΄³Ύtυ/S±β#AΪu\Ω©©g:ΦpˆN7ilSΒρΗNΟΊp*l.X'ΕqΰΔΈ΄Ο€ΐ; ΡΎLι6ύ–γε–£‹ώγ`\^+zΨύކΰ(ΈΌUΫ&1mΚΐ»HXέ’J3vωi³ψŠΠώuΣ²ή+·hŽ•ύΠiΡeμ0©ΗEWρIβƒ`Y“αχππ’qlΝΝΥ ΕΊΪ–፭ΤΦiΈΕvε0ΏuR:υAζβ>Ψx(j_ŽΑ'™{VΜ~—R Ϋ^έ#α‡p=ό .Σ{Ιυ-–mΝόΞάmΊ˜Τω₯KWt˜ν}ΐόέΎΦΑπpŠe”X'Γ‘ΔYίPωΐqφΒz.Όv…ΧΑΑώP΄?Ό¦ [γΏ \Ο–γ^˜όρ.δ7ώώ„{¬ϋΗΩΰΜ6‰iBςh†£Η΄wlΥ…θγͺ_¦²Μ+@λλ:L˜Zu­°"ώ±Υ˜1]UWwwΥ΅`^5·Z0?Μg[™Ο12g»ξ½š6θ–ώ²cΥg28ιfΐi°*vβ½Ξ‚@‰}άmΟΟvΊ2>ώf}Κ°~'–›Εƒpx3φЊXŽvNΦΓwA[SΣπ.δOXw8Δz₯=ζοbPβΪ}αϋΰ&ι+Lε«πuψ”ι φ†›ύ›pΣustΜΆh₯} ξ…πjψ2l ³ ε$=ͺΕ777Ϋφο*φ₯ύΈxω\ή$s₯tυ—Œ'Ό ά O†ΐ§Hί:'^t+Jς_ΞΑψKW[χ€ιΣ―m|2΅œ_6™‡IjΘΕφm γ¬ντ……©s/²ΝΆί8uΊΦK\7Σα(xLƒRάϜKΆνχΰήp ό샴/.ͺ^±œR:…£›4ΎY9¬£ovŽkωΗαΊoνχ@₯ΊMPΥγRی[-?©κšΈBΥ=nBΥ5v<.tΡ[κ8ί*Ξ·ξΥ6¨\ς›ή6šΗRΉLdϊ՘‡8ΦξšUwXυψƒUχΊλU'¬Y­άΥ]­Ό ‹‹@7“Κ‹ΐξ ͺωσ­ΎοŽκϋομ}Rž8f|5ϋjΎKΫVcβ–m³­τO‚ίΒTΠξ8|δΔs2»ΈΪI=°ED3l~BD3ή…aM½i.¬gΓ[ΑϊΡζψ,|\`>…Z†ΆΦΡόuSί,ΠfΉ˜ ©X±]%ώΈκ\Ό³ΰϋπ%Pφ‚sαzH_'T΅”αδ§[Š6»Α–-₯Ώύε‚ώ |Όέ§ ΌK,εψ,qf£4Ηα«ΰ%k p“όw‘ΜEΧΫΰΌΦ‚‹ΐ§`επQπ h—ω›ΉΌJW[η¬{”kΩύ*Χόίΰ$pοΚ^‚wXε›­ά-Χυœ=FuΪeΚ΅QΆU{ΓΝσαΟ°ΈηmΖΉfυgΈΏ{£}ꏇ³ϋ_ςΕΫ+κ§*›αFW½ιΌ΄όφ‡ϋαπ~pn―—ΐp5X²kα‘΅ZΐαήΥ…oξcυ0‡ΦjΕΥͺ)+U­ΜE` Σ5¦‡MwwW5w²<ϊ@}žy ͺe‡ƒͺΗ¨Υ•§΅moΜ†Δ-;nH2μ/“νͺΖ>ςP5φΊsκ§Ψj‹½ͺΉ)νGEφΫΝ«€jMτΥΙ‡`Οη^Δ9ρζΎξbυέ1o~u!‚³ώqaύ$?aςJΥ*txχcΥΤl—aχΰaδ«5ΕCθ“pάN2w„βj‰ΫΤ—aΛpΑ ·FQoΎΆΑMcx»‚βΰ»Jω ΑEp+˜u“ΤGݚ`>—ƒΊΔγrqA—}dyΑΒβΧΞΝLYΞ‚mΑΊ φΡ8PLΧ”΄£tυ›χ£ΰΒέ Τ½Ό,™Ÿy₯Žq“QΛ…ΨFΫδaρt°νΞ£vκ!•νΘmoψζ:r2Λ\,]ύΞ;%ώΔΫΣΰΣΰx)Κ[)/ ŽIβ­Ε±ΛœŒ_Χωϊ|pνψτύfPΞ…½acψ”i›Ψ.εΘZ²έƒΑτ΄ΟΥ›αup(ξ[ΫΤΎž}5lϋ.ƒ?ΐΧΑ6{Oσ*λB°–ŒG3}\γγΧΗgOX³·Γ P‚ηΒΩ0O₯ƒ‰Sͺ1&UcΈ»~#΄`ΣέͺέxΪί{μ˜j72]›ΜΧΟ4ži« &BŸ3ν.^xnη‘χ|ήœvειυ›žjΫύͺρσΈX\ujoy˜ ­Ψπ₯.ΫμS­BΎ‰ΫΠ)άΓa!I₯&a4–a~€γΣΡ±γšB‡=ΜΑυ»o­ΎϊΟλ믞%yiκdqς}»₯4;'ΧΗαoΰμ;Ή˜τΪ4ύ†-Χ €Σ™p0LΛRΆ†Οƒ‹Ϊ2ΚzΊi8Ι/jΉ.Θ“Α[σ,H˜68Q}ύxψ$μ…ΖΙ™6ΰρΒa[?.FΫ’φ€½†γ·Oή ε4p,l£6I‹wI[ζ„§aιΖaήn–kΑˆΔϋΈ‰ν}f›^ ?‚uΑW•φΕΙ’χ€ύ›yιfήΊ‘o;Αk[.NυJψ1x`gΒ#PΞΛΜI]q-yqπRη8~6ΛΪmbŸτ¨†]”~(ύιΔΕ>aΫγμκχΘΐ6{@/jωqj±ζq5Ό~ί γ,$)Oe;t}ΉŽΝΎ°ΈZή¦ΰ»ΧΎ ~^D”Ί9θ»WšQ­°ΞΥknj«^r₯žθ…Z°g𷀇άα;Λ#ΌψάγέΥ'―;mxί°₯3:We cΆή―κΊ’υ*c«ύ«=y¬?†^>ϊa:`ά‚jξΜψ ’‡«±›ί[™ώ Χ%–ΘΪlιS(Χ•Ρ'ͺϊ'έ~ΛiwΘkVά>₯Z0k₯ͺ{ξ˜jόd‡h~υΏ>T}{Φ_λfΜΈ‰ΥΔyΥ―W^AμΑΕΓu’:

ž³γυ“¦Vί~τΑϊγΨ‡6Ϊ₯ΪoδκΘqΥ‹s¦Νx κZοαjΜ¦χUc<ίr¦Ή [@:يηL›=•„d•jΑmœi7¬X-xll5a #M₯~Žύ—ω8ΰ4Μ«νφΖ^vf=ί .±€c–8£vl΅o5žΧs·ά»Z_|ψΑΈξjΟGΖVc9ψ»Ÿ=‡Ρ½³κZ‹εž†Φ­r7γUHO'Y»Voιυζd:†οe:ό“^½xͺϋδι\―ω f ΓDv>tOυ†9—VησρΒGyCπ.Μ3€ΏΒc8ά43q,©UΎ'όΡΕmηΔ4&εMΰvρ{—±YŠΥŸΛΏŒΣΖMΔCϊ§`^n΄/™;e=“/ΡuέΛ8uί\‡wFYΈ–gω_Αz­°}Φ”z(P2eλΛ•aE7~λͺΔΦ°σέςίΚιp<\eߚ¦ —~λh|bψ0ΤunΖΉx•τKΖν‰ڟ޽υΈ Xξυ“ uT7š$ύώ6*m[\#э¦v U]m»’s^«Wη|Ϋ Ό|ΦΧΤ“α6Xn‡μxZ―™ŸΘOηΞΡ°/(Ύ.2˜§Ά±‹jPb½Mϋ1xŽo[1>{Γρ·sΛψΗxΰ»oά ―„gƒ²9ΨOaΫ.Aύ!ΛήΪΖ~°Ο―…“Α}ε^X¬GΉφ GβO]Υ7ύާυΨ 6χ­#απsϋ±ΌuώθϊΫW'΄jυΝρŒρ£γͺqœMέϝM"Ο4`Wf0c3²’ƒ9Σξβ:s‘gΪ ^oŽ«Ί|Έ?¦ϊ3oΜ_yω)Υ[νWγBPΆ‘O!‹—z©xπVfΪ'a>7š1ήRu½p֝ΒηύuG «ELœΑ|~RLΰŒψΥtώΌΣτͺ››Tγο»½Ίβ–+λΫež„η7ΰαλνΞ7R†υ+qΫω[ΒIνβυΖώZP{«₯8‰,σ½p!hŸo-ζs0ό L7˜ @κX֟,‘ΙhYDΫ3¦έ6/!†m_;qވρΊJ\Ϋ₯Φ―+LλΊOξΓ΅­ζVp³dκg‚½aνΛ| ^ Κΰν`Ιom«}«.±Σa;ψεp2Μω²—ΥoPΗ½žΞCηρΏ£8Ÿ"™Γ₯[Ξm/Κ‘`ΏnωxΘ­^ ›σ1sš¨zξQΛξkΈ^hέ;6lΉ–_ζ±€s{7ςsήϊ aύϊ’vύ‘}Ω'Mβu½Ψξ}„Wΐs@ΩnΧ―b»RžΊ•a{0Νa0œ›Ω΄ύΌœΏξŠ6Ι§[κβΟΈΪ;ƒ—Ηΰyp˜Ώΐ·κϊUΧΊΤx~w5…ΉΥ˜8Σ^όΒδΰA?$g=ΰ/Αύjʌͺϋ‘ρΥ<.γΙώδqWŸV=ΎυώΌ]?uΙφ·4šΆ p;ΓνdΑfϋUkŽο~Η½―θ;lN5ρω7πŽ”αΌŸ.{ΩaρΰL˜ΒGρŸ<“wη3«ΉcΗVγ»·šγUχγUίgπ~ΗννLο'‡5H-ΜBIΈτΗΞj›ΞC”q¨f€‹ώHπΆ―8Ρ½½*gΓ/ΰ$ΈΤ‹“Z‘Ά½bωήώ]Œ–1”ΛΙ“jΩ>ΤC"i‡n‰ύ₯€ίŒ;ή¨ωψ,\@ΝΊio_Ώv„χΐ$ψ3¬nnšW€γ‘qŠ‹j‘<Υ §Xηf;†³Ό‘ΞΫqpσsΪ§ΞβX*ΊMκ-¬₯Χ/O‚™π-p»Φ·Χ~ςΒΫ;?\σΞγμοηƒ&Ξχƒ@ω Ό\Ώ±ΗΫ›‡ώΕΛΜ<-λ―?αψΫΉΦ+v^ςŸ >₯ί ΆρΉ l7CΦ°ΊΤΝτϊ]λφρtπ`_Ωίζg_η6 Iπ[8mΜ£ΜS}κ\ϊ£s<άΛ·αάΨ…sγn~“9l]=g…5«s―ζ?ύζjόσnΰu«eΈΞ΄ρ‡iυΙ3κ‹ΐ\.γy“~)υy oׯڜΨ―Y‚o Ψ‘C&ώ]ώ<υοΘ.r#έ»ΣΚ ΫgΟ«&u«α}ˆξυfγ-i¨ΔΌΜσ1ςΆAΚηΞ«Ζ―Γ ·q+W]ν\YgΣκF:νL’]`5(!Ψ;AΤg±ι*nŒNΠa78.Ÿ@=όs¨kη«΅αψ,xαp3pr9‘Ν_‰κSGΆ~˜fIΕ|έΤΛς–4Ο2}™o;Ώ:ϋQ~κŠ‡ΊOx¨kc[›ν5Ν†°U+ΞK’‡Ώϊ3Α1Π―”eχhώιΈ―^Κ|*3<Τ©Νv uΉC•ŸσDq»Ks,Λ°ώ2μΌ t=€\οϊ? χB9·ΛΉ ίΓΡΓΜ½aepŽλWήY΅bˆ~”u’,”ε–ν/₯OKΧyh?zΐ»½Ο†/‚oZžϋ]Ρn#xό8uκΟπ}#ΰaξz7΄=ε ͺ₯Τ›Χx~©οjάσVZ«Ϊd³έ«ƒWdt¦>XωψΥψ—]ΛC'»ώpži>ΠzΆ½`_1ωs5~_—§bΫΣƒpήξ»R§žμiOΏ?νŒ!‘-yΑαί½ε~Υ^Τιoσ›‘[sΤ~ε~«ƒχ|zwH μIFteΚόΔωΌΣΎƒ_ΰ ­Ύauμ¦O©Ήc79»-¦ν\sΧΖIΰΔρp_|½œ ϊ«€β₯ΐƒθ(p’½ n7S/™tζgyζ§«4έmηE’ψ‘ξ¦]Νz2*υο#Έ°§ΑΊ°-δΰΑΫ»A˜Ηdxμ ·Βρ`Zϋτ[`Ÿ6₯]َ§ιΌ”ΉqΈ©Ωό'―ώ€]ϊK3Τρφ,―2˜Ά ΖΦώ*Η―ιwšŸkήΓόΟΰΎ‘nΨ:‰yM… ΐƒκ@P&ΐηj_Οo–ΩŠκΣlϋΜl#mKΉn³ΣΖv:ΡVμη ΰXx*Ό<τοe L‡ΟƒλώΛ°)x1³Ÿέo?ΙUο8wσδ_ρΗzζOΫΉZmƒm«cης·i6α*χ5Ξ΄΅&βM8άb~΄ΰ9ϊ‰ΏT];ρ8Ι[οΙ¨N{°»ηO“σ•A‚ƒ—tόΰS)ό,‚ο*vs Ψyό‚κΜΉ«=kN5ωΨKθ$J@·x΅+Κ¬—Χ$υg2oΏŒίj›Εˆ­Ί'L©~ΆιξΥas­ξγ—­TΖO7~cμŸR‡WΑΟαzψ,¬ Šϋεΰ }/ΨΎN0γrqHήΙί…½68`Ρι–a‚£Rϋφ“`?33κ_–τ"ε&ΨIV Β>} x3έψ1x§ρΦώ,†vϊob³ΈY——‚Γ"Yc_&χΓΑpκ7,vΘΤr폲O:˜ŽH΅λ*}Ω‚iŸqιίΑτCi[ϊS”ηœfΗ©χ³[υpΎϊ΄ͺMμπφΞSηςΖ`Ϊ™ΰεT;υίvΣ…K‚–vupβ₯hh=SΧΎάΨΩ7κ†ν«;Α½ΰ9°?ηƒ}§ΈΞΏΒ©ππ2πΜχσIΩόEdb5fƎΥΞ+M­~8ίΗ?ψ¦jβρqσ`¦yΎ˜`iŠΏΟƒuυ.fΨσfUc©‡Ηλ)Ό 8μο‹ω1€‹f‰Ε_DΨξiΥΌŠΏπ Υ‚#㠝π=4”―ϊ[QΘW(Ϟ]U/»κβσ4γoςδjΎ0·5άΊh.ƒ‡Ρ‰ΰMρ«πL°―΄s²xΣτ©ugπ0σιίxq²99Ϋ‰Šƒα&π Β@Δ:Yξh‘ώκκaώ?p#h»!μλ΄Β8‹ˆ ώA8μCΗΰ=ΰOyqQυκτ—~§1*ν‡Βoέ=Ό^ »ƒαΤοRΛ•£Z%:―F“x‘ΆώΔΈ·ΐ½0ΪIΪΌ9‘Ρ2pΝ•8]ΫΞ/ χε?{œήΙ uη†OΦΓ5ΰηΥΚ)p%8O;_¬ΗV­tOΒM»ρŽ)ΗΆ—mŒΏι6+oΙΗ9ξήp |ž ‡‚ϋ·ϋΈb~ΫΑ»A;Žσϊ‹γe>©‡‹cV_·Ze…UͺSψζZΕﰍyΥ΅=gš―δ—•x πL{Α ΌςψGΕ―ΩΥƒϋ‹­¨vδ[wφΑ dH&Œ]ψ‹Eσͺs捩έχ–jŒŸΑσ=Ζ1λœωώvζσfσg΄n«Ζpeš8iJυυ57ͺVZqΝϊ qcρiίΕwœ>ΊQΊ²xΤ} VƒΰPŒo׏Ž’Ώα[ΰ˜c IΉ}˜Œψ¨²έ©¬ Ν>t³vΒΊΑ½|+b?Άk·© ڍ7(φαoa2΄+υˆη˜Ÿ7/£ZΩΗΑ9po«#₯ίڍw«Š΅γι˜[ίγkMϋυfΤ₯p2tΪSΦΛ±ω6(ρv[Λh_ig]/κ¬|ΒtήξBςΕ[ϋωTΉώΜ=Θύ'ς <ƒ);ιtMη…ΩKHΦHY.κe*Φ₯μΣΤ­ΤΕΧ λ/Q§D—|άGξίƒϋŒ[ηBζΏsΔΛΐ/α.8Άχ§ϋψ‹²σV›YωΏΐΎ;ΛΝ:Ν*,m‘^Υ7πΉθmLͺ±όyαωΥ/_ίžΏ0˜ΊtšόΚcΛͺ1 ˜fc§TŸα»ύΣΧεΩωuάYύ­|Β#FœΎΆy-uγ;–έ—­VΝ\szυ#~‹r> xo^†Ιΰ%@ρΐN,λ_Αl »λEkΏΉ©ΊΈΫI&’ιKΏ›ΓŸα<Πί,ν—7)ϋΐΆΆΞ€›`C˜{ƒ—*oγŠύ¦­‹ΩΎzaΛΕ©ΎΝΝTύHΗΪyΥIl§mv^ ‡Μ"Σ½ σq8ΚlžΆ9;…ύΣν1m^>±)Ξ‹¦ΨS[4γNYAq"˜¦έ˜¨·Œέ`:x©θ΄Φ‰κ•ΜΧ;Πh]8|2ύ¬ «CμœΓΫΓc0 6m=~ΞϋΤoΫv«oŠύx#Xˆe.KY’ς;₯-υ3ۘ>˚γψ–Κχq½ ϋΓ L…·Α›αRψ5oΟkώu7| κ~έl6δξGΩ#E|π«η5WαςΦύ‚5« &Ν«~ι™Υ>ΫνSu]vΖΐζŠ p±e>wΦρ+V›Q™Χρ=ΕξcYžφQ9*‹ω'τ£oM―ΏΌκς9Vφ[yνκA_zδπwρ8aŽ€Iπ>ψ+ά ι/mϊjf_q.κ‰ΰδμKœΠΛ‹”ύ‘…:ƒΖmΖΩ';‚7r7ΐ²ν^V…—€Σλ>pΜ”2ίί;²~:gϊλΏ4Ά˜‘ΤOΆΧ'3λ΄ ΄“¬΅οι欴kC;]υ’?οGΕγ@O_ι^‰Νηΐ7VYϋxϋσsϋ°p#ΜηχZπb`Χ¬ΕΆ{PοΣͺϋ:mί -ΣQ)ε\L[νίvcVκβOξ!–ρ0œοΗβHψ)8&¦Λg;π—ώŽ2Ήz–Ÿ·Ών²ͺ«ώΦ‘#M¬—Ώ\kͺ1kο‘ψΛΊcύ–BΧ‚κΈ-¨&ρmΌŒΓβ_:½Z@!Η°*Φίςžͺ‹ΟΧ{ώ Hν©V½ΜΓnζΛΰσ4O¬&¬9ƒWς]Υ>cΖΤΠ›0»ά΄$‡S&Z\’†EΚό-»Ώ§Ζa©ΔRΘΤvϊšτύπKPό¬rWΨnν|"z30Νλωz£Λυλ){Α~·Vc'Σ₯~.1ΔYϊςkx΄δΘXkΏ Γ_\izςzΨ8)lΙ²lτ™°.8Fϊ€Š I_f {«Α± ί αω°ψΚUΉήUϋzύ3ρ_ ŽSΔq2μΊ$ύdZλ½ <&Βb­ F2Ο|π,pzF7˜|F’mΖα­Ά8Ÿ‡β‚Ά,ژΆt*ΫΧʁΏo«"ΞΙKΰ0N1―#ΐΛύtπ γΚ°38ζŒΏ₯Ώ9Π_½zrYz?mΣ«ΰ,˜ νκ?ύŠp&ΨζΎΔτΞ›Ršmn†KΫψ΅ιd—8Ύ$ήΣ«­WΊ`AυψΣoͺΖψΖXFΊΨΰΥ\Uοη£xΒ_Ί„tTύΉΕΪΰ•ξGͺ]IΌύŠό-δƒnaχξΤΕ#°χόςδ“xΡΆΕ½όIΗjΓMχ¬žrο­ό2%ρB«ΊύΉe«b«τ—6υgΊ™›‰·iύΡγρβΖ7 όŚԻSΏΈ!Μ†_ιΌ|ωϋ^?#} ψ‘ςπΥh6συIιΣπwπ©Κ<GRΧ3HlΎWBΚΕ;μβZ\άΊ{εY@ϊς} Φ€₯Ω—ƒ¬ξ›ΫΆλΑy›Ά;žΎπP#8g«―ΑMΐtΫ‚^γΟΆρIʐρYžtZΛa{³gnŠmτ’ο«wίDšyZŽzϋ©g8ΰν•Ψ•qΡυαiκ [ήΌMv«α!v=ώ3Ÿq»Q;ψΞh0ƒw»ώ“3mL΅ΒΦϋT‡ρ‡ωœo}Š“tPβg όџώ-bΏιk’τΰ 2Z†Ζήκ|΅σ¬ΩUΧ#ܑƏ―_ϋΗωξ§iأ׍ΏNΠ 7u‰[ΧΌώ ‚c;ZΔΊn —Γ1Πί¦ο·‡Ά»bϊέ`p³8¦£TξοUΩΟΪώ9||ΪMύEu{ΕφΪν$λτεDΪ'JΩ=š₯ϋΣςS―N%ŸCΔλαNˁރκx%d>Ϊ,ΗΣ νΰ[‚3[aœκƒ`ίΉΎ½ΰΊQ{ω;LΣIμsνt {΅ό†‡KΜΫΊΎ^Ά«]y^ΰ½Θ?ζt°±Ό|n‚HζQςu|.…uZF‰krΪΕEΧtM¨.ϊŠΏ cŸŽ0‘:t[ν“o―ΖLe»ˆgšΏθΞWυΞλSϋΔΆφ)ιψ>ΚHzΝΞRχ»ώΟ˜Γ•\z{³'nΔt|7βmζ«‹αέ}•«Ιάί{x؜4)n³MM½Y†ΔΕMZγϋ’€Χ¦™Ά™N[ŠτgΫL»ΈaΛι―¬Ϋ°ωœ5€Blƒ—„9p&˜χϊΰ%`]x)D~„Η ³yHΊaώ>^ά –7ɚ{/ {y«qƒ^»Cά)Žέ$pΜ:‰q}ΕwJ7υΆΣ>‰ΔοS±{ΜΙ­ϋ#Αyκξ›2ηπωp1L„ΎΔΛ°y?6μΛpˆβ2†/!?ηŸίάKΫ{¬{~–ύc{ϋ›~ς=πΒΠNR―δ“°Ά₯N\τ]όΦΨUΦ­&νͺφ«θΟMœi₯•–£A¨σΌΩ^υ±ͺ›73·Ϊ§ώ†IŸ5οo Iάύx5o«ͺιόџΥω[σ'ŽνΦΫ_¨Φ{¨~Ίμ^{‡ϊσΧr²9μέώpƒ^Ž‚ζ΅™“I»E”„Ζωzμΰo±6ET ‰uXR1Λν«^ΪάŸ‚ Α§‚ώΚ6??ξψ d6=°7Έaϊυ§ΠiΎΊ)φυδDτ‹cή©>C]Xή€μIΖ―meήiΜ­“‡ΘpΥ-γω"ΚπιΦΧםϊΎΏωBεBlgζ 2¬θ²ΛΤӝξ`_Ιήp0dlί„ίΎ,σ!Έˆx™PόΈα$°Œ”‡wΘΕϊ8Χ^ ΦYι4χ¬G§Έ:aλ‡ve;3§tΕ}Γ7Η‚oœΛ‰[—δνΣσ›]]ό_1+Z­>l΅ΡΞΧ™KΌΨ½]©Τ&ΑςΧuΥ9|4² zvύ9ϊέυŸΧΆ¦¬θPϋE“θ±ιτL.zμ©­22ΩΚ"ΫM:γ£χΪN„m@1“tŠs #lŒΧΞErψΊ|(€―ςμΛτ5ϋ/ΐמ€νD½Ψ'ύIΪ; CϋΞ‹€—°'Α‚βfια#XΕt}Υ·6ζntε&6œΕ₯Ÿn₯υSuςξΊyΡ»μ‡ΤοI«2?]Χ΄tΎ‘ŠΌ{†ρ^–―ƒvb>Ι«ŒηίΆešNώvω7m=ˆοΨ6ΣφN3tν)χ™Ψ5λpβ-―ΤΉo4γκψ±γ«1χίQ=:yυj_NΜ“Yu{SΛFcF£IlΤVwWcψϋώ£ϋhŸ2¨v£³›7€χχό©eωχώϋl]?‘ξ\3ω@³1]υΧΎΊψο ·C³ΪVRϊ½ΕΟ/…Δ%T΅4ΓΡΗMN‰+]ΗΝ9 ŽJS’~G"m:·πV°τ%ΆΣΊ¬gΣΎ―ΈΨZ·Ξ°ΦGΐ‹C³Δζψ}ΓΣ₯‰k†£*ΧόΧ€ŸΑλZ™w™­b䆕ύφζYή(cr&Iχ/myŠ|n£#EΖ7ξ@j~Φ5sμΞmΕyžόΎƒίsΗ~Œo―”yυ*‡ΘγλΡn}•E΄«W?Ϊa^ρ›.α”‘Ÿ8ΓJβ£ΟήVκγoΪ'M Vγ| |ΧΗι‰Νξ­Ίό_ώͺ©G‰ψΊt³ϋ˜HτΝΨΔ―ςŸ₯oiELjE,U€ΫψŒv2™Tη MΫ#[ιJάΐ_ΟAhΘΪ«O«V7Ύn“νjb_©Σ-ύκoχη7σ2-Αή°ώHi“<WΊβ¬ƒOΗμνžφ’Ξ'ιοƒOνν. ¨λοή_Žϋm$ωΏ‡8Ÿ\ά¬²‘•ζ)S7ώ2>ώrρ«3μ+Ν―›`nύζaέ½μτ•Ρu±iΊΖ₯˜Ώ—¦‘(§R©§ƒsqq%σͺ―τφspy–Μ#Ϋ¨ίyͺ}ό Υ{t¨Λ»Ύ>?Ε8ηϊ½ΰ[΅τeYFςM9˜Υ}Β‹λZζͺΰCΛΑ‹›Ιb€kΦΏl_β’+]λšz«a\©?zmρ‡ΰŒ›‡»“#²!ίa˜F£Ρ(N(jλ0x0ί„·^4mc[ιΡΦΪm‘sΦœΜ“ £Z\i«=Ρ¦©γ§π,χLˆL’΅­εΌˆ?6φ₯§nβπφϊ£‹½q₯¨·J‘Ψ'άΞυ`.Σ”69΄Ai^^L|i'³P> q9μNwΔ&ρeΪ¦M™ΆΧΦw(@IDATτΗnI]Oo}‰“‡Ζ¬ΎŒˆsΔ;z»Ίk›Ύh―.ωΉτ*MίM%sίώξO2—uKιϊ‹·n^F|α%dΈ$υNŸΈnβ·LΓ±1¬?:Γϊ›¨WΚώ(ύΝΈ2Ώ'Κβ³ςV.«Nœ_-¨ΏNW+Fη~ΏΒΨκά©όΙϋ1|eΏ£Έ‰-ŽtωWFΛ_ώλΤ@ϋ%ίbΐ?›Vχ‰ΙρDBušχL–tΊJܞΠ?£OΪ„Λ4ΡY†(ΡΕνΡώηpΏ§)λΣM­ΥΫΎΔΗ―λΣŸ^Υ“§–5ωι“ΣŒ–;7.γΤ{™ˆ67ήO‚—ŠwAτxKΪ―–γΕf φ‹U™!Lty½nΒΈ ΐvφL&rλζkέ2šΕΫήΓOΌy|ήΌj.―LΚ cσl₯Ώ)ΰεΑTN€¦_ΫτJβ λΧu < άUoΩϊγ”Έ₯ίΌJ½qK[R~\ΛοδoW7Ϋ Κƒΰλ~η―›₯Ÿ³+λΓ-·|²7n bΜΧ_t:2x‡U,wπ«{Cڊw©ΙS)i°sΕz—γΈΤ*ΫOAΦΙ7EύIyυΧΆΔΗMyeX.ΗΆτ;—g΄l·Δ] œ7ϋŠr ψρ€ιq7—ΰ²œ²A½ Φ―kIƒ·WΤEίt{ Οβ^$Š,읍εGΑ7–Ά|Σ!ύ>:h;ϋδΉ€6Ξχ΅Α‹\ΖΈ™§σc#p>νO\ο bΫ^yς.’z½eό½Ζ}xόθi]°ΜZvΞΏΒe °>^‚ί_?›Ο7mκΠt‰ZD΄ρΠΈάΉ“ή%–²ρ—™ͺ‹ΎΏΤ™ΞΊE—z.ύ‰oΊ¦WΤGbcΈΤ'ΎWίέ³pLVΧ?Θ7ΐκ4cΚ”#Ψο«[8Σό]ώέ œφ’nΫYϋΎ39φ_“ΘΈc֝”WΘΝ­ Oώwπ§€οŸ?··E™8Άp˜y²Hͺή‰«.¨WŽk‘?†‘ΣΫ’Σ7%=ߌk†›ι†:\–§ίzι–ϊNezΈ»‡G·\œϊC3€Kr5x&x9hζ]–\Μ{λQκΤ/ ±/χΐ]ƒ(ΠΊE}›|ΌΌΞDΩKΫt ντθο%\™cΝΊf>ΉΎnlF6Β― όι–¬Cιwn†R_fΥΤg]jγŸ ΞοΥΑ7nΘΞηχΙ`œ‡ύ  ^&Bς*Λ(ύ˜tœ'ξ5sΰlδΥZόŸΦW±ež†ƒρJΦ_Šύ‰]lβŸΈΨ6έf|3m죏[λτ<πMμžW]δα9gΕΦηηI5Κ\7ΛΩΧγΤ—€=πo n νΚ¨•εRu΅Ξ¨™ύyΆΈ΅?NŸΫυΝβζ;”ι¬—νά<°ϊ’ϋ‰άŽnuκŸΩΔΏ|‹δ… X£έZ‘νςς€Ϊή>‘;†ιΗΈ¨ΪJς3‡Ύg½ΜΣKμ{ΐϊέ W‚>dΌΤ»–•ςtΝ;aΌmE/Φέ<­Gκ„wPόdΈΤ§ ·σ']ΣMzυιηvιΫιΚΌΚ|’―Z€Is―.π+€W­Ru§§rΦ•™ŒΏι f«mαŸgZυχSκyΧΆϊvφ€εΚS«ξ-χ―Ζ―0·ϊαrΫUwλUΓ€σ)††ϋYb7Oνω¬mώcΥ©¨Όu·Ν%°M©+ν2)γΊΠΉgΦέ4±MΌ:%ϊrŒ’‹»:AγG3ΏFτSFn³0/ͺ“αοπV8"gβqS΄Ώ=ΐά$™mό‘ŽžƒΒtΖ•’ςΥΕ·Τ•i–¦ίϊDRηύ1~¦}΅8RφQι_œΌ†+υςΙώoπ~ ρ£Έ³ΰκ~μmΉwΒM γτάÈ,¬Y‡‰‹ΫΘ’7ψΎ-Αƒx3Ψχα_ΰ~ύcP|[πŸΰόnζm}£‹‹ͺ­N}S:šιΚpΚΥ Ζ—ώΨ7ueXϊ2φ†›6eX;Γ}I3Ύn›Φ ΐŠkTγο½­:{"ΧΕσΦΊΚW/m`₯O•<˜Οηwη³ ϊ«js ϊ³―φο…gUYPύcφŠΥΨ{θ­υtΏ9/]oxχ°όψ ^ם_ΟŸVΚΕΆ€•JΧ΄sΥE_–cωŠqžŸτΙ+ρJ3ά£]ςŸ)·]N‰Σ-ϋ2ν2›δkα$ψΰFiš―‚OnΪί ΞSϋbπ©Μ·ν€,»―ψvq₯.λ"n7œώτΧ­β«nϋj΄IγPΆΗ±τ³ψ–Κ~ϋ§œGΜ¬ξ+/ηΫa#Θ%4ν* P—uͺ>uτIπβr hη|ϊψ`žχ‚c}˜Η6°'€œ¦KT―$W1„σeΆΝ2Λ°mσ²Zφ…ΊΨč]™oι]teΈτ'Ύ“ΫΧؚΖύΗΊ¬υπ½Υv\α?Ί›ύgΒuŒŽOΠ£Mμœ9όΖ m˜Oγ;©ώ&ϋl†0(Y0Άš·σ κ˜κ«Ό ¨NژΗY:«ΏήT!Γll]ύmΙ‹Χ¬Ίο›XMxμακ4T{‘ΪΧΧ}.Ze0.φI·Ξ¨ΘK}'b«λ!θkOΗ¨΄'Ψ[―vϊ¦.φΡJ1_%nO¨ηg©+ύjς-Ψ«Η΄~:ιΐ2¬§ŸfΥίΩΗ©μ³p§ΣΌWΚ|έtEQ.]ύ„]Χ§Βλv2&½Σσ*ΨΓ£IμsλμΈ€ξ>|λ3€œO^NfC.€‰‹k}½ΈΪηsΪλό™Ξγ΅`Pόμw λϊR0οσ@ωώ(εAšςtCmΤ λMτƒu“o™O©ˆίύΚoKYχδ£k[ύφ—o?μ―HΉΗE—τ χη¦οΫΩ₯ΏΝΣ1υ’²3ϋΟWΝδwΏξΉήjΞ ŒΖΟ6κΉ‘7jΔΚ%Ό•Ώ_Πη·Oύϋo{Ο±ŽνΘΐt4hF\ύ§ͺϋᩜŸέΥοωΛIύe­ͺ»ώΪAΣp‡ύͺΗzλ·T]S遛.­Άβ/>6Ν€ΪNŠCΑ?‹ΠΎUΟ=‘ύtI€τ«K|©wΩΎ>%dA5mΖ€meΌ6J;]OΜΐ~š^)σ)u‰³ IŒσ€χ3J₯ςIpqFΜχ°&Ά”‡γ ŽEΚ5ΚΉμ³OW}•Kτ€δ¬άΠΜoYHΩΆeQώβ–™΅r&όͺ•ΙhhK»:ͺs>κJ»9χ4τΡυ[68υ\œ¦Ω2_OΓοG~ΞΧ›ΐ·Άg‚ƒƒλ"{s»r‰^hξk£Δν μ§išιJ]ό±±^₯.z]Ϋχ1°?J‚½νΩΏ— )*[’}6y©Š5œύΪΌτ;{Γσ`#°ξ+Φι[wέXνΝ·ΐͺkθύΫΉΊ••!~D‹gšœ³v5Ÿ_fτωφψT8“l Ά½6WžV-€ΛωC@ης·œ<UbχŽ™Βt8e=ή½1ΠέΞ•τ‘ϊ–ώ:ͺ)x)μΩΒ‰±`]ίΤ½½:/€τΧ ΝteΈιwcπ+<“i'εBlϊM»ί,#αΈΪφ%νμš:λβ‚Ϋ¦΅2‹MΩ?Ϊ<ΦŸΪ=p}r30N[7Κα vl|’2Οδ‹·ή@‚Λˆ.τtΡ΄ΣΆ?1Νo`_ΈΎ?γaŠ/ϋjIŠhΎ–]œΌ“lIο¦κkογZΖCΥ–”½$6™Oεœ‰Ξ|›~ΫυάΞ;ϋH›©°!Έ†_μκ=s‘5­ώ‹ΑyύΘΫ«Οΰχ­Bφη”«k:ίHβρZΜ+ωš8αR½υ³ΌΔ•ΆΡyQΎ¬_O°7ώ»ΰ·`~Δ~pέ'οΨ™w_’xΣΉ‡86«ΒΆΰEνπ`όSαυπ9x LxτΞΰΫm ΞbWJfčh±ž~dΑοδUΧ¬R» ΊόͺS«Kύ}½ώ*Ύ$Θιw<…=s½jžŸ§{ ς(­ώΡ&\³―ζώλΦϊ«fω{Ι΄’ώœω Έ^^ [ƒ—€gƒ―μoDZۜ'Ν0¦‹ˆi“ήȟπ΅xΎ ήRΝΣ±JΊΎΤE[’τ¦Ρ―ΔΎ'τDΩM}βγ&oΓMΫ„u-Ϋ‹χα'ΰ&Wi‹ϊ#!ςcά…`pO˜ o…OΓ+αI`Fή?χ‘κ`3ζ—3ͺyώ΅[ bγΎΆ“ƒ–Ӂ―άφiΥ8.ξ}Κb7oσύ«1WœZA―Ÿ{Λ ΥΈ_M―Ίύ»ϊ#ωΰ,ρMΕIqΣW͟Ϋ]ύύώ;λΓ§P'ξlpςΨ/ή@<_‚χΐΰEΐ§Φ]ΐIξ‚pŠbNlΓϊ•Έ=‘Ef‘4c2‘uc⬧υu+‰/ύΝ΄΅aΓ6ΊfΊδ—<:Ε—ιm―σρCπAp¦~xk±ήλΓΎΰΌ ~Ϊ‰yΓζq$7VΗB}κ6Ώ―gƒω…τ7vνΚH}ΪΕ- ξΗaqΪβόVΞ„ k_Ϝoy—K§9~ λΆλCυΑψ‰0‚Aζ¨—S μ ΪΚ£p#ΨΧ~dβΑόήΖ•βόwέΈΝ€ˆφJܞΠυJΈt΅-νΛpό#λθA|4xΠ[OυIU―D—|Œˆ.yτ7<Ϊέ ?ƒΛ!sΠz(鷞PΟOϋΤΎqΰ¬/POŝφωp|ά7Τ+χΒEπbΏΒjΥ§oΉͺ:•_pέ½ͺq'nΖ!@Ξ#ύ+“ι‘olΞFJΟqώž1«Ίxήάή½’¦u–tng‹1לZ-ΨjjΒΌωΥ‘+Ξ­ώyςΜjάlγΫίΓ‰”‘ξvY©ύžoeŠόf:ίb`‘>rwύέ\'ƒ·uωΐSΰιπ πwbmΎFςΆ~όώ‚“hά λΒξp-\φ―½α€mη’ξW›qM`~.B/o„αΥ M6λ]¦!X‡ΥD,#RζcΉΉ5{Π+ΖΣΩξίƒ2 ΚτnφΩOAΡφηp=Έy6ΛM~'g?Oƒ-`{Έώ)ΫΌmΏR–i8ωͺΧΧΈ‘”f9C™χ`ςrœkϋμ/­„ƒm³ι•!‡‘›νς(ε|ΡlkβJ]Ωι'Χ£>΅>ΦΗαL8V†τŸye^ƒŸ-Ό:ά{\WΟƒΟΐύ`νΥ_ Ϋ΅Β%ωτ„φΣόJ)λΏν²Μ·ƒkΣuŸtqQυ^ΈΛΉ_ΖkΣ”τ™zϋ(iυ{π»ΊwΨΖΔΕMήΞImάΦχ_ΧΏEŽƒΏ{χT(ϋύΒί‡Σΐ=Δ2VyθξjώJkU+ήwυΊΥV«.ϋΓΥԝΨρw½‹6%b8’ďηZZwκϊυY0‘δίpν) ½]ι³ΊvφbKΧΨjξ΅gTχΞ[½q*έώ9ŽΘΫŽ‘ΨWΎ™π½§ͺ›οHN\0·:ξ†Kͺ«ΗO¬'Έ}°b«#ΞΓ=f€‹ΡΙh“œ|.θ}ΰmπEx!lϋΑ‘ΰνσ'p$dΓΜ€ΥM>x™Τ₯NΏΆ’Δ_†;YΌtg86ρ'­αΤ!6¨zσΦ―4Σ•ΆΖ» όώ.Άf|Β.LIo-.ΞMαpΓ4?ϋ¦)Φ7ύg>ΗΓ„–ΡαΈ@Ή‘€}-“…œ²₯!£% ¬Fϊ ΰ z/a–‹<ύβΌόy+—ΑΦ)y|‹τ?XΜ΄Έ³Ά“V“ψ+γg;΄‰Δ>αNvι mŽιZΏΗΑώYߐΈOΩί[Γ›αΰ>ηψ€qΌ“ηΓ ΠΛ«αGχΈIΥ€«Ο¨>̟‡Γ=“«/mΝ™Ζξ€ΑH;Μ_όϋΐNΌ†ζ0ΰςqΕιΥ»ωΕΏ Έͺω ™l½_5‡wλΣ6Ώ―κ>αΒͺλ¦Œο²/#+1}>ψ$VΨκΥγœpχί1«Ϊφκƒ$“Ί]­Ήύ#N$'ΰ@NυgL―ΒU<\½*·―θN‡+ΐƒκΈ œΔN`σ+σΞΈYόφΞΜΞͺjΫ't){K€Ko!$„ŽˆQιM)Š( R€Šˆ¨Ÿ ϊύVŠ’(‚ ˆŠH―’‚tι½χ"δΏοwΞs²ηΝ9“™d ωΜ¬λΊg―½v_»ž3CXΞޜΥ_ČΠzn/HΕ|ρͺzΚ[ΏbZzιή^πkΑk4ΤJχ™ž€ίΓΰΈΚ|D«φ—vΣΣ―τ£’₯%–Y}i=‚+AiΧVWJΧδ|yώ°™ΧCσπh3NPΥ‘z λ΄ΛcŸSΖτΙ%SJ?&Χψίλvλk5ηφ`ŸbWOυHl`py9}ܟ'‚—“ŸP_€Τ•2†Ϊά –χAϋ]˜ά_ΒUrYϘZk;zͺwχ¬υΉο†Αœΰ™c#e΄•qυvρΨΛ΄Τχ&Šcq\ž?>φE_]φ©μƒ>±άb0Όπ=ΓόΰΓΕϊ =KΟ@ΗfύίΟ1ΟγWΑ‡B½ώμω„dΑ§όίσfž½1#@ΠΘ₯G6~πφ …†Ρβq|·ηοΩ383OρΒ;3#=~EΎ†—;νΖ‹c§k ηΖ+wτπΟώΆλ«ιYn½Ζ ―Ώ―±Μ o7x'ΰ€5Ή1©έ qr‰ε?Y|Πjό_eζώηwξΎ¦ρΙ—ŸͺώΪάδ υΞt€—γΜπ:\ ϋƒ―Μρfν‚θλΓ±ΰ&ή–‡νΐo™Τ™Άλ!Y&(ι{ΒΤ‘‚ΪKqsϋΈπ₯μσ$Ÿ‘cpc­`>e½e~Σ|Μdύ”υ`nΥ«IžM1Μ–½ΨVUώ²-L•”6ϋηζΎžλ[<4MK_P;ΆŸ>˜§”²ώ^λφγp―ŸΚΫλ΄6J{©Χέυ~ ξ/-/4έI?}ΊWœΣ²|֜‘λΧ3ζV˜ ~ξ1?\xŽXWΚφ²VRΟo{^–;ΒG«γφzςjVWΚ0zWJχ΄Ψ:…³ι₯ž:γ C φo=XΌό=GΏ_u!—Ώcςg>όό ¬ΫΛ_1’ϊ»b]?cσp7—θAΣ4溍―LσV㉇¨αΥ9<₯Ι(vOώGršsωΏΓ―΅=h†Ζ’|R_/‡αΪ/ςΤƒ·ŸΏ»ρΦάΓͺΏζή’Ώ’œσσ5ή^ςΕΖ4 ρ†σ?½+Oθ~i΄C%ώaΔΜxκ1lΎ"ϋΘlΌCήl zπζƁ~­1Σώ]½x Έ]§JaW¬σOσ» ά Wΐ·αrp“ΞΎpݚ°1x»Pηνnΐ—Α>8Φ»ψ ΧΊ†ΒSMβΎτsK΄₯νBΣmΣ δ&HΤΦ΄˜Η4Ϋs|Ιgήv`μιWΒ²nσDlΫΧό―A?)ΗΒίΑ2νΖ…Ή%ϊ‹e_εχ“TόψjΣφ"aϊ‰ZI§ΎΔn›‹ΒA`?μίδΗ₯ο>Ή;ς_ή~Φ@ͺ'^ηΨ–Ρζή_ό–Πύ»Έ‡ώœ ξηr}—υ¨‹ι?Χγs°6x‘Ν Χΐύσ:εS–€–€ίΪ₯—ΆηΙc½ξM₯]½₯-yJ[©ΧΣ7τa48•«³oABΟGΕ3αΠgσΒ°ΜϊΞψ¦°?p Wη©γ΄mχ­{εΣΰyς(Έ‡τ§ϋ¨'1’Π>Zv}˜ λ΄ό5ύ½―<טe–9|qΞΖn˜«1ˆ;mΠ‚ΜTυ—Œο…x§ωΧώOΎo³WnŒ½wφΖ >ά>Λ?^·ήl<9AOίίχždAυ½d‡~΅>ΟΠΖ)όcλΎ8Ccπ΅σσ?\Τ΄KΝ?½wΖ²b:T1ΡfgΡ?φ›ƒi?χβwψ{_οΌήx›λ.Αώ †ds‘Ήο†‰ιŽMy!Ή8]΄ΐωπkψ3xΩ{(ζύψruQ/.4/A7„‡‡QY<@¬χaΈ ž'Αo]u)ϋ½ £Wξ) kOZtCΫ)/γυτδ‹έ˜κΑ›3Ϊs+ωRS%ιO&O§ΠΊL«ΧYΟoΪ”valez]7†€¬ώπΜΰWθž‡ξΕαΐ5WεΫ–pwΨάηΦ‘?μσΟαSΰ7$Ώ?θϋcΎž€άζKάΧπ‘β<άΫρqΟŸhΌ3σ̍iߘ£1θκ˜s~Q°"-ΎAoQΊteϊMμ˜υšwΪΉΎΉBγνη%9ϋύ˜VΈγ²Ζγσo βζΛϊΤΆΞμw™oHcμν—6~BΈ εJ7ΜΣτwώβežo β?lψΏ[μο/ΐΎί^σi–ΞWω«ΘΏ,ΜF0/ό΅ŸξΌšί! βR[½¦Χfΐ—€‹Θy?Έ`zšΓ2-Ίaœn]^P.μGΰTpaj_\ΠϊΪπ°), .ΪEΑLjuωϋΏCωxšψ“`]}‘τΣ2uέxlΡOώΨλρΨΛ0y •²Δ_CρsŒŽο[pd\ρ%¦nRΦ₯ΐz0¬ΛΤ?ΐo/Τ–”u΄Œ(ΪοƒoΒ Ν8Αd—NύΨŽυw}ۏ)©œ>‰_J=λ1Ά„ϊξžχ›)/ΆΥΐui™³αtΰœn¨•˜”u{α{ι< Λ‚gƒ΄yfœ>¬―,O΄[<ϋ(uw S.ιΖ•ΤέλOZvS>uκ§‘ΰεκ8<σŸς8όfƒΕΰΣ°+δμtNήσ [ΒYπX―ηBy—₯O˜Η“ψ'‘ԝΏ A+Β°;ό‡Ž {Ο?¬ρ¨ŸΊωWχ¦ρN[ϊ…Ζ ΩMυΕν©U*ι«X§ς ·Ζq+6ΖώyQξ΄±Υyy.ώ:σ kΌΕ§ώw&ζ“WΝݝΫ$‡vjωQόCA—6Ξ›kHγΖΖ6V}~¦Ζά,Φxλ)ώͺ’ΌbΠPg΅υ_ 47!κ/ϊκA8#o”η¬ώ'c΄tγ?ΟΞؘ–―GψΟΨΖήό΅‘ΣΝΠ8T«‹ΟίΥωΌ œl_’Ao~ZuρΪ ]sŠZ›¦κvλ΅Ύ?Β€—“q/}Λ©Ο£`π _ΐ~’XΜσόžΊ&ν₯L-›z]Jwͺ—˜7ρR­:]eΎzάόΪR.y =ά67ΆιOΑΰKΖhΎHκLšώ½>€~*₯¬£“έ<"†©ΏΜϋYw­\O“λΫ ΧDΦ jβ!ξ<¬ 6u‚ρΔ<²œΏ…rOν&ŽΡj­˜`Y%aϊ—zΛ°ΜW׍{ό||ˆξσϋϋΛ ΟΣκxΊνΊφ> ;Γ9`~λ4mqX¬Ϋ‡ΑUΰ\*υ>Ζζ%ͺ/μCΖeZ€^Œ«—SΌLλ€[—}0έ}:›}σ ^”{ΑGԎ°x*–{Nƒΰ pΜώΪBίE‡„±'Τ'‘θ†Φ17Œןw„ύ±-ΏπΌΧΫΐ/ŸΎ―qίpίΘοαΧΰΫεΉΈΣώύδ̍AοkήiVΠΧ;"Υ}–;m&ze'nšƒDjXγ/έx‹Gΐt3ΎΣx„oΣχΰς?r™υΣ–γ·š>K'gυΉ’vx Ίε²ΖΨ₯Φΰί'ž΅±/_•|—Ώ’|‡Λy, ο4<Ϊ΄ιC –7­#ρ«Žςy[―ΣΩr5ρ/5U3ς:ήΎ˜λϋ’EcŸαίoΖρΣπBβo8‡½ωzγΔ{―«>ZΔj/b ΒΛΰ’z œd?ϊΚΤ'νόβεΏ5ϋ‚_;»0μ–’°+Φ½ΌΧWζπeΪmΛ>Ϊ‡WΑCΫψ΅pxˆάφYq,Šν₯Ν„±FΚ΄Ψκa§<₯κzβ λu·^Συ«/vΗψνΘ—ΐqꃴŸΠ2₯N΄ΫΌθw†Q Xη‘`}ϊ(ε £Χ}W¦‘­•OύΏE|d 'Lβ€\Ώε\υΆ:ηΨΓΪ5Π“˜Οωω"|V€ΫΑ6λ’Όϋ“ΰ#p™fχP;ω6Ζ`X§λ+λΆΤ£}ˆ­ΜƒΉmYO†ΐ:ΰΕϋMπ’{λ«KY§χ…cω)¬ ƒcΏ’ςΣ=ΈΦΥ­Οσ‘τIϊbhΎƒα°ύ§ΐϊ'EίΤ‘xOa™fΏμ―—ϊπ!ζ9ηεΊ˜ΟυEιϋ›ˆ.Οhσ™§”²ΨcKάΠ²‘θ υχϊ Ÿ|Xxή:Ξi.Ψ Π/‡iYžΏ)»₯1v½₯Σ<΅HγΣάi'ϊϋyξ΄·ωgx§ύXcΠ&qΠ3βήήiv˜?ζ«.“Χθ¨Oγ’…cω6ϋώβZΏ ΟaτπΫ·ύ₯Ϋ―L)9ibΫοΊ,»Acz:ώ– ρί)Οί|ŒxcΥfΖhθ&ύ ϋ#ΗΐΓ`ϊEΰY_h˜ͺ½6α·ΰhΈά”YH¨έτ΄«]Tsμ&υ1α§—ƒ`0Ψō’ΆοC< wΒ½π"ΈyR_Ϊ‡diIZ†>(υ1X΄n+γeΥiΧρ, ΧΒΜπl ΞƒyœεδE­ζΔ £9ϋ­φl'mZοh8”;`gΈ τ΅uΤ–[ΪL’ΥκΣζef\ΧJWΌώ³¬Γ~X·›^\„#aGπb¬‹‹Χπ•ΝΠORΒ£ΰX¬Λ~”}(u’Ζϋcž2/S3]{ϊ_Λ4υR¬ί±Ϊ·α0ΠvlnΊ²ίŽΓ β\π?,o₯l[έ4Η,ٌϋιηϋ`™€ΪͺΓφ”€%,mU†>ό˜—Όΐ!ΰΊΆ.Κ"φΙqfμ}ν–~χΐaoΠΦΩρΡμRo2χ1}³/=ωΌμ«zD=ρЇÍπQΈ¦ǘ<¨-=λf~lΐ,ππ,ρRπ"χ|¨.ΒRR_Ϊ7tŽ ӞΊηΒΰj¨Ύ½±ξΗae¨ΧΉMyΟΫ΅\ςMh&”NU-±ˆz‰}T†6™‹Pۜ0 V‡EA±oŽ[ΉΎ W·Dλێς|ΐά’z’PΪ΅•γŠnθΩ³¬φΓ‡’Ÿό ž%ΚΣ`ϊέ`?ΝΧMx βŽiπα]f½Ζlƒ¦k¬Α}΅5¦Ό5¨± Z%wχ•ί~ηNσWΣx§ωΧ»Ξ4ξ6? Ε―ΉΟα"ΊϊŽK«}εF7¦Ήυ‰ήΓέϊ]Fκ+Σή}Ήυƒήž‘ςVc›i5Vfv–£α₯θΨάθΈ―'νyΈ nΑ97LσvγœΫ.οwΎUf~”γͺλΖ]ΧΓͺ€Ϋ« >πŸΰΖΏ \i΅­Ψ―H©Η–0}(Cυ2ξ³?€lΤΦΑΰ!|ά―‚ƒ‡ΑzzjŸδΆ’φΛΔΦNΧ{Β²Όzϊ’/_₯·ΐ {[82>ύ―θkσζέ ,Ÿτ΄eέό‡Γ1 \ ΐΏ sg_Ÿθυ,έςŸΨλω xΉ„+αΏU>Ζΐ~ ΞΫx‡α‘Agέ”‘Ίkeψ&μ+ψδCν¦;οζωXφ³0Μ9ψ>tςSκ4lGΦ-ΙΥ {9ν+h@v„ίC™/λΩtu₯–Ά*CσGς•ΆR7έ~&LZΖa\έ½½T“μν…ˆ»n<ΣλεΣe·<Ί²ί1΅Ζ ”mN(žϊΝ=α‚ΨΦΫςŒρ\υLš\ΫΟΑJπΨfΚ‘φN†ŽiΜΕος·¦τΚ<V }γC(Ύ±νΰ.jΏ…|7N;mγά[.ͺy˜ί©;τ½i΅Ω rΰ4Όr¦γ«‘±·^Vϊ½ni~­ΐ#ΐΗΥΫόν€rlΡϊΥ’θoa+p³»~—‚_]] »§P¦•:Ε*I›F’ΆΣ]t?=ά>BƒŸ뷜 τpαϊ(Ί žI―cρ€H¦nΞ[»06+Iύ₯-•§έ1 G4|t}|XΞMΌ†β\(ŽA)λWeΪKD\3ΒNp.XwΔzMχ“‘β+ιiW{©ο­ #γ½ ORoo˚Ο1ΩΆσοΪ»bCμ’qΉ|œυU¦”±ΨΕ°ξΨό΅sQΟƒ©Z?ζqύxΉ­ο‡cΐΛΓυΎ,Έ?]ΗΦSJΪΤ–ϊ υ―b»λu8¬~0Ψ”‹a;pΤΫ°½€ZIϊPλι‰&oi‹n\γϊavπ[Χ\`e`xΡZ—e”'αAp oΒQ 8žσΑΛΡό=΅Or%©3qCmυ²‰κ/}Ί6ΨΎβyωpίΉΎoƒΰεά>̝…―ιυΑtόš{μΝ—φm―,5Šr|+@GίΎγ’ΆwZη†'2₯'²ͺI+Ά4Ύpφͺ>ωcZώχ/³ ψύIΩΟ±ώWόώ%ΫSΓeΉΊξbσBψ6|©»±Ο_Β¬pψiΝΦ“τ¦/–·ιG=tΓ―Α,p-ψIΒ…θΒυΨγ9άT7‚ύ~Μw3τB΄z6I­ώ”z £'½΄•i¦+i+α=Ψτ₯Ύέ~–s ζ)!ΪκoΚ§2Œξϊ;εrΨ žδ³.κοΒ₯πC°?diΗ°Τ‰φIl+εϋTΜΉXΛ'ΐuωE"dRΖ7E €N8†¬‰θeθανΖ†ΪΚοz]–‡_ƒs΄8¬ »ςψ$8Ÿ‘¬ λTΚ0ν”‘}Hά6ύΖΜuϊmp ».Vƒ‡ΑtΕ6NΒς!Z¦'ͺ+)›°Λ:?žQž™^ϊ>t ϋ»)Œ/}χ–yݟΚαΗ`\ίΨ7ύw(Žρ0=m'ΔΤΫι‹”u؟₯ΑGΆ~³-?8όήΚΥ°ψ(pJν›π·όηπ‡ιvΔοπλlώiαΖ ―πŸ¨;“Šφ ω&φΏεοͺdβ~¦ WΊKρ»ŽLVΒ²φvΆ2½“nΉ,šR7Ώ Β…ϊyπε~$˜g+Π/Ώ‚•AΫ=zPΗ“€™·“$O§tνeyσ{?έ ·$<\œ_/Uϋι'ωά gΒαAψ'ψiΫ±*.jΕvŸR7-vυRR6ιυΠΌ©Λπ-Ψζσή>jόφ%―oν)ƒZιΖ•ΤίkΣ :| |χφӟΑ:΄ΎÚΊ€΄›φ0΅$ύjz‘΄«§Εͺ,ϊJ ϊGΙαή›ό?'e|“ΏχέΧ“σ+JBυ¬ u%i†^Ÿ€}ΐGμbΰΌ}²Ώ~Œn^‰ΏR¦nRΪS¦["ξο[ΑΛKtPΎ ›ƒŸΊm;u₯eHr7IΏš=a λw?έϋiY›ϋι°¦NΠΚλΪυœ:ށΐoα3 Ο|΄»/#Φm;±Y·LŠd,©χTζ·)>`μƒΎ΅O9‹ΌόG€}±ντuβ€ΈΠΣ—²’vΆ2ύ=Ρ]ΤSƒ”‹©Τ_ΑSΰ%κ‚p‘{±\CΑσΈhzλξ„εvΒf_ζƒ]ΐ‡Ι£πx7Τ…π+8n³Q^°–„…ΑΎ»§ΆΫΙΌ«§/eΏ1·ΖQκeήΨ #©ΓϊΓW`ρfβi„Ύςo,ϊά|φAIέ]±ρ¦ξδM~Λ{Ξk@Ζͺ―ΚMμΌκ»ΣA₯]Τ–€Ξ–α=Vμ“•ώ–Ι=ώΟΔΤΧnύΔΈG”Ψ»bγ~ώ υ<πΛΑpέGφN’vR»°μƒkΩΛrMπqοωδεΊ$xy=e~’-I[-C₯άΡ-η~ςl1\–†Υ`~kΓΗΑKžΗ+θηΒρ°ό²}όϋaΖsΑzη…‘ ψ ς|] <ηάjŸ%c± mY―θ;ύΉ8|Ώ·?gΒfΎ•ε1χŠπΤ"ε†(uˆ~π²w“νo€›l8\^¦nμG‘― 3›#m–atͺ­.$7Ψ.ΰB΄-nš'αjx μ‡ύυ“‡Ρ©p ΈI―—α`uΨ7Φ0°Ο€RΞΩυσ&n‰· ­w( ŽΆw< ^ώ—Α}ΠNΪΥ§M)ΓθΪ³‡αΗ`z˜ώ@ζΛΠΓΚtσ‹u$Dμb_ϊC—>8ΏAι/’S•dμ†ΡΛu‘3’–τ2Lšλϊaπ²xŽ©ƒυHΚ”‘i‰«—}ˆ=‘i^Β{ΓΧΰJπΫΌΐu2œ ž–)₯OZΦ—a©'έr^Ύ^–ξאc›V„Οΐ–°2ΜξmΣ€}α`8|δλ—™@y‡Εΐol{n ΚΩΰΩη£ύRΈτu|Ρ)$Λx’qYΖ³} π,tολ?ΟΔcΐ|φρ‡°+˜ίρ€<κΏdώχ΄ϋΔ:ΙεD«{ιz‘YΈ~’>τΣRΰΖ¨K§Ε™θ'ψΆήJς&œ‘‚.X©bέ$/Αΐ Ί$ψΙΐMμ˜,3μ Ύ²?` 0έ±ω2N½ρKB’*I?ƞP»νΪ'/γΓA±ώσα0ώt£+e¦…*±φΓ4₯Μέ6„ΫΑξ#cdQ+IF’'μΚ1ΞψΥΠq-Χδκϊ£ίυω΅ΞΨΚP½S<ύpm-ξ•΅`πτ‘~3Έ³ίS¦Φš­·‘4Γvβ^ς"ώ6ψ°χ˜2άοΆ)ΫΤVξ―δΡ{tχΏmy9€­ΐvf/όŸΓ‘0όφLy Τ΅M•gMmƒΟ‚qάΡ ]”n’ΒˆΝ rΈ8‡ΒHpqJΚ£ΆD» Λ ΥΧ΄*ωQ»ιΖ•ΤU――§x έΠnV^~c`/˜> φEqŒŽg38~ nŒM`4xΘXW)nξrΣ–ιφέτ%ΐOίEόξϋ¦”uXN1 υx'{ς%τϊ#άΞαΒ°1δ@)λΙΊΧ΅m‘RΧf^}=<(λ阦HΙ:™ΨΞΥ}4±υΌΧε2? ΣΎρΨ2ΆΨbO˜2ϊΠ eVπ²\ ΄έ Χ‚ςy8| »ήΫΥΙ­>τ4?3oNΈΤ/ƒ·A9όueφ–Άτ»¬S]ΌθάξUΟ$?l C`iπRόxn¬ ρ jυ+< [ƒz˜>Ίέkže{D+Ρ–ώ4MUP··-}«^B΄’z™ΨΪ–uΨ—Ρ°xnyω<γ·gφΰεoΪ¦Z)'xjsBΉ`K]?Έ0^„ΕΰQPζ…―‚›q ϊ―^S%.ΔΫΐΊάΌΖΥE‰žΈΆ²Ψ–ieήΨ ΕMω °o–uόζ‚ ΰw1Ήy‚αGΰΖί| 8F/U› αYψx€DΆ6‡=aM°œcΆ½—‘έZ³_F'Ή%νl&ƞ>x(j³ύΑC+yP[ΊΆΨ£'nΎHisΌΓαπΣ‘ρvγΒάk)λοu‘χ(cϊ6νΉV&F&Υ?ΣfY&c0¬λeά2υxlŽAΧΥΘfœ ϊC7χ“kΑ5±ΈGκmkO[u=eΚPί?ξ£Ϋα°ΞΛ–π*DΚvc{eψΈ—7ƒυaIp―ž_oκΣ*ξwΧω'ΐ‡Ηqπ:XΏΎ°e[ντvcŒ/Κό±QeUoYN[]Κ²κ%Χ/Ϊτ‹gΪ‡@q[Αq–uažΊdΊ©kΈγΦΙΟb«/7 hEΈόdλ§γ£Α cΉΡpxΡ)Ϊ$βΖυb,λ.ΣΥΣ‡zΩΤQζ-υ΅ =lΧ‹_IάM{qƒ΅apΓ(ΎŒ}έ{x=7ΑΉMέWςηαy¨χ'}ΠnŸ/]εJΈάlρj%ζ/ΛͺΧm]9Ηo3}0Œn^ΫΉKΡ>ό„s*Ψ7Ϋp|)Σ}’[’τ,w¬γΦ7)bϋΒ½pα€Tτ.”΅oŸΑƒσw Τύe§>Χ?ž3υω?w—EŸZΞu;)’9ΆŽθ S―ρΨlS)m‰›ζώ~Šή<f‚£af°œ~ΉΌhΤ΅EJ½'[Κ•‘>΄Nχδ(8lΧ}ιΕύKhwΞxXΦG«{{5ΈœΡ°Μ₯―Fό‡p9ΈΦΟΕ±+Ž%ύ3^κΖλbώΊX&Rϊ^[κ+ΓδM˜ς†)οZlοI8|ΰΌΞΡzp8ώœ¨S―ΔqS―Ί[Ζ_.*m.|7ϊ p Έ°ά0.¬ΕΑ‹n Έ κ‹·\_Ε6ύ:ω@8€YΈμGΣ4Ωητ"ψi3Μzκm‡ά+‚k«·γΚΪκmνςeΎ KέΌ±Ε-}KZ=ŸλΚoΏόμzRΌόυΛσΰžP,οE­€„]ΦqφΔ{ ³Ύ>CfχαcpΨ–Ÿv·Ο)%kΤ>­¦y ϊ‰ήuϋ9ψ¬ΞOζτ,τ%au8¬?}·ΞΤ‹ΪZχι—6σFβΟΔ ΛτROZi+υ²υ²Νθ†>τύ€ζζ·γΑ±˜ζεΏ \ΦmžΑν&jjtL’cžΠWοϋ`œ ŠΪΡΰΓΐ΄›‘›$εP+Ιb‡υtγΙSΧ“· Λv’F―η=y =ΰ_ƒύΑM²œ‘•QΎǁ‡y=Xζ ?…X—‡€ΝpΠ?Αΰf«·©eSWΚ±wYΊ,Σ£ΧΓ”ψЇ΅} k€ύΘzOˆ©’Δ­―ΤMlΧFΖSž„?©, [6λ)Ϊ¦©ΟcΘ8ϊ\Έ(ΰσ1β…¦_έ—Αφ`ݟΛ˜fψ~BΕ4}φ | ΁ϋ g@ό—ύ@R7©χΉ[b›H™?Ίaτ²HέfΌτAςj‹]Ώϋ-ŒΎ<Ώ΄;Ζ`Π%Ο/ύdώ ­)²L]’Ε;uΊσh³ΈΜ=‘6ΪΎΰΚ†Ω}ψ²v#ϊ΅LY\ΰΡ j%=•Kž‰ ΛzΛ~₯lzΗ'n˜³aiXΞƒΜεοψ'ψ ΐμπ x¬;λί6”΄ΫλέOΫ·œa]b+σDOή́ρδWοΤϋκΌlΞ‹ρYΐ‡ ώU<Γζύdκ€Δ₯γc›ΪΓrΡEOθkΩη₯w$θ?/ΉέΰΓΰa°)ΜΥΤ :.bΣ"Yτ^¬Ύj•̍iIΧ=‘ΆήJΖ‘Πr₯žz<\έ0χƒόaΈ7σŒΰσ Ψ―Sΰω¦OνΆeC‰”zlBλρ“ό²`?γ‡2΄~:ϋψ`qΞvn†ι»aΚD7b³Ώ>j”€%μ²NY?ν«s΅Θ­Μo»£o½0{ηΔω9 †Γ -σR]_>|T(IΟ'Τ$Οθ^€σŸ&=ξ…K εP«ό†Še#κ^ΐΞΙΰ'φ€gέ'$©ΫqL¦™ίv­ΗyΥ6–Ε|ΐg`6P,s#`5π/J׈υ₯έzHR―$γ0sέo¦•6σ$Ώaτ€?]±xlŽΥsi3°Ηaόpπο™lΣq/ΚΏ!e+Γΐqȍ³ hz \0Ρz y@Ή±Ά‡χ‹rΨΜ·!ΈΡiΚV†¦M=!›ΑΚΊΛ$έx'έ΄‰λ―χΛzb3τ‚Ud­L¨¨ωτ½ω¬Ο‹qΧ‹kε/ΰ'δΜsBL•”sέΠ΅ξ#£UΗ€Ÿ4υs')Χ―Ίν― 7ƒλ3y Ε}/‰ΪΎy½δΌμ?ΞνPπ«ο₯@ΙψΥ=›N†U`=8l3aύQž1D+±½I‘zyϋ­τeYϊ -yΤS&υ–Ί{Ρ½¬O΅†γaaP†εΐsΑσ+eQ€ξrαΤΣ¦φxΉp’—‘Ύϋ5μκ.θ1°'Έπ6_€nΰvβ’/Ε:όzΟ ͺΤΣ»¬γ~¦/Κ7Dο΄Τkξθ†φozpœ/Α‰ΰΓ`{xJBδKp*|φ?y)εΨegΪ§Μ€αp&>ΐΪ‰mά m&z@lάΤ‹~“θ&EΧξAj[ Ο€υ%΅Ϋό€.ν}Λ½βϊ+Ψw£ΙUgέΧΞI)ρiBηp7ψό΄—σ=‘ιΙγz]ζ„Α½©oxυF²Ž­Σ²ŠΆ’Ψ 7/|σϋ¨^ΌψGΒR°/|ցR,γ₯7 φ€ϋ@›{Φ±υ§Ψ·ˆΊΤύgz=Ÿ6₯΄'?Uš?J›Ύ› 6χ’c[Ž€™Aω ŒΆQ–': uxΘ HgdΥ“qα΄π#x.©Υ―ϊΎΒUπ Έ­Ο²€]šΆ`ΉδI˜:ΣΧNuOͺ=ν8nρ`υΰEόcˆό Ε―JΣwΞ{α:8ξηΰYp κGλΟκc#©ε;σθί=Α²νΔ|/Β°x@mgΑγΝψ;„ΆešΊέςŽσs‘τ)a—uάOσ—iΖ{/RφίvϊSϊ»ΎώμΫΔΤUϊ5εckΖ–Kι1 υKΫtΕόξ]ηΜ}jάOύ«ƒkh?p}Έζ/œ¬ΤJ2ί 5FO˜6ΊJŒΏ^όVanXμ«νψππSr°”βΎΊ懕ΑΎ<“Ό Σ.jΏI|kέΚm«΄EOhžˆΆ²oυΈiI7MΏ- #ΐ΅­ŸVηΒtχϋ|{pN_…ι…²z‘uͺΞ’Ε¨ΚΕι!ΰζ&\<4”ΰλΰ& ΓΐΌ.V₯¬O[(ν™Γ€[6’ΊŒGO˜<ύ¦_ ­Wέ όπΕ­\[€›υ»ώx0- »ΑΉπKΨ6„ cκOHR7W<fƒψH[]lΫΤOko‚‡ι(°Ό’ςζ­λΪ`gχ³bΩr½–Ά2­ΤΝ£X‡ό\~΅ν'άαπ1p}}I;>€GΓ:p&\YΣ;£»ξW;qμ퀓½]ήfΉΣΚΊ’ΪΔ3–2iΣnά3t¬ϊJΫΊp0Έ'εk°=8Gυ:0 H'θ°ιΚ…₯žΈ‘›ο6X Ε‹ν› Χ€₯ΐ|)‡:AΙfIFγA›ΊΗΒM ’zΉΨΛ°¬§n/γυώ7£cs#zΉ*?ƒgΰ%π“³›sΈό$₯Ψ€ΰΗp,μ CΑ―σ­Χ<£a>ΘΑ–v’TυΓΈ’°+ΦυΣzf‚#ΐCΫΊv‡9 ωƒbήθΖΥc‹]›’°Τ΅•φ€•6ΗχœΗƒiΪ¦$q6„‘“©Sςc™ίΖV‚ΆΜm©k“7ΰ88\³ χ«ςAπ±©xΩ> ^JνΔrYW ΝWκΖ-ο£wNπΓΑ°%¬‡Sa7πΡ’~ί‚ώmXZΦIž3#`fάϊ‡CΑύmΉΊ―1 H'dquJ°wχ@ΉΈ²!Μ‘>δp'ΊΎυrφ›7²s%p“ζSj·[Φ_ί@υΝc{n /Qγ3@=¦ JoΚdΌ ύT½7ψιίΝχΈ΄Ϋ//νΏ8ΏN5|ƒZ}uΉαWαάfΈ6‘‡ξοα0x Ώ΄€JŒ+ £·?ϋ₯XχΊΰό€ήθΖΫιu[β -P[zκ/mφΙ η³p”}&:Qb;©²½‰ͺˆB#ΏΙΊΎJΤΫUSϋŸΦ_’\u[⦫dž₯ž΄Ί-φ„Φγ₯αώہλΝ}΄(87›4C/Χ‘ωsA™žω‹ž€*-ιΆιΎ°~Βj0φ׺؞g„β\œλΑHp>|€Έ¦­Χ5ύ/pξΟk†ΥΗϊΰΦΨΆbhΉ2%έ°”δ=γoŠ„Ψ ΕΆμχ—αόfάώ'jKl³.φ+˜Ώύ °*X―~s~φχs΄ Ά‘o€h7}¬bͺΛξ"dΑχPpΑ? λΐm xώΌ–Ώ pqχVΚ €lo&π±;ΈI|1GΚru[™¦ξ8bK˜2νB׍γρkΚȟQn}`}φΗ>NίδAΒ“aiX~ ρ₯>ϋΐ_ΰ‹ΰΑw*؎υHς£VΊρΨ’'LϋϋπRίμ—ρrΌ–5n₯Τ΅/IžzhžHς'n[™―Lο«τ΅\»όΞ›2>]iγ|܌φkΠΞρ EO>ΓΜE==σ–Πτ2oiOYCύηžτ›+σΜ3Γrΰ#^Ÿά WkFΙ:3μ΄6Λuk½+‚{Ζ3ΐ±ηΒχ`SX/²§ΑΗ²eΌτ^z>¨•Τλ%x Έ€—Α3`X,Σ“θKΗVŸψΩrρ΅{eY ζΟΎI^C±žΑ Λ:‰ΆΥλβ8KΡ«€ώ·=Η»-lŠηήΦp*”σAt@ϊβλι»\άYόY艻`έΎξΟƒ ΐύuψΈ˜υϋ_!eP+±ΤΧ4UyΜ—Ό†Ιcx€NγΙWκ˜[’]<<μ«’2©;ρΤρΊaύšΡ‡Žu=@ iS•nά>f£{ΐ}f‡` ΨσnUi]γ΅θΑ৚W —7j«~ϋœ~[‡’ρ|fƒΰa|€œ}σ¦ΝFI=¦₯±YN[;1O»Ύ$oκ0^Ο—<} ‹oƒ—Ιω})Ψ&oϊviΣΜi›¬mʘλΤνe\]Jί&=λ*yJ{©Ϋ^™ΧΈbλ5ν-П^¦;‚{Χ½²δ6oό”Π΅Ά΄ω p}/sΑbΰ…ζεμΎ™Κύχ(qΟ‹Ÿ{ZρbηΐΊΣOΤJO›w_,ϋi0Ρ°1δŒOΩ„ζΩΦ†Cα9ˆ˜'b²SΣΰήK»ζ ΪτΟφ`›ξ3ϋžtΤJlΧ½ξ7q₯θ3σκUΑ½¨n»Β† OmcKΠW^ώΆ›9@Ύx ›‘/eςvy ΎθŒΗζBvαΊPΟεπΣοΒΰ!`žΌ`QΫJ6‘’xt7€υ—φ€–bόδq:x°9©΅’z=ζ)Χ‰ύυ«Η/VΉ»~\Dΰ§ϋ’Δ »¬]›u%"·‚ε½Π| …/€6Eί ‡ΰ‡πG8όdγαια`ύe= DIΫ†Κ?α*°^§mA‰?ƒ~%XΏeβ›Τ_“eΌ<ςΖn%qΓH;[&κwΗ‘OϊKτ‡uo%cqΝ?ϊ8Ύ΅ŽNγ+ν­'k1yΛxΩN9_Ρλy“ίPqνxYΟ«€λΜ½u μ#AŸθσŠΊΨC/)Η>ΦλΩώ‚Γμ θŸ?€ωΌχ//P±.Χ«’6£§]ϋπ˜οπ"·^/vχZ)φQΡb?φ„gΑ2IGmIόœ>™[τVf}ηψ“§¬Sέvm/lΤVή7ΠG€—δσ(^ώŽΟΛUπς·ψu@&ΖNΖ€LΌά„JΒθY˜†~εη#@ρ€ώ¬»Β. Ν|uΙΖIhztCηΞ0 vKOΌ,£m7pC- ε…‘|˜+1nΏ²ρsΰΈη…­!Τ/Π_7iΖΏ€¬αmp \JžΗΡΏΊΑՐǀ_qz(ψIεOp<μ ΓΑ:νSyY΅kίOSίΫ3―ύ yύέΉΉ³υꃉbγΝ…},λ2’­.ΙW·wŠίDΒ o&—Δ_χӁ{a0”c&Ϊ’Œ/c7΄|$ιΖ£—aΦ~=μeήθeήθ ΣΆλyπ"rΔ~ΊΡ6`}Σcφ‰ϋΒ΅kΎ•ΐ έ½ρ Xfσ? ίχΛζΰZ μΏX_Ή†m'})Γθξΰ°.ϋ8+lJΩguΛΉNl†φ+u™^B΄[ωΔΛ<κρ} λbζϋψ ŠhwOŽ‚EšΊΆΐ _ρ|Y|δ؞’>wΕ~φΩqdŸ θζψΡ°Ž—‰‹ΧΛη‹ΰBΧ¦œΏƒ+ΐƒa0\ο‡[α$Έ…ιΑοΑΠ.ΤP[›C[Δ²s‚α‹ΰƏ”ύŽΝκpΓz˜Ν―ΒYΰαβfv ‡cΊ wΒ`ΠξοΑ1)OΓHψ€ΎŒΣ€L¬œΔ™t”‹1›+‘€_w‡‚‡Š‹\YLΫΚ:ˆV’M•E_Ϊ£—1ωMK™Ψ =p^1ϋ‘6“S%Ζ­χ xόϊέΈύ ‚‡Ž›ϊ"x ξƒγΰ«`ωԝSKžiΑDλτ2°-ϋz?|<”‡’\ώφeΨΞj²+‘°ύ± σ{ξΦoϊΞ°˜xω«Ϋ?₯ Σηn=’$½Œk·έ―ΐ•0l7y •NρΤq?“oœ₯KΛψ2Žzϊ{OίΚΠvOΚΈΎrΎ}ȝξη]»ω”žΒ²θ 3νκ¨*.~θ7ΧήpP_¦Ε΅υ ΌξӝCΧήΦΰz3ό! Ϋΐ`°>ε)ψ, Ÿ‚\ώφΣ<ΦΥnή΄ΤnybO9/ψ;`$ €γχ›³έΑ~Η/¨•·ΌνΗίΪκ[Κ'LΎzΊφ–|₯=ύOΪ²( ƒεά»Gs‘< ¦ί εœIυ€›m@ϊΗε’WΦξ&½^†\ψƒΡη?½.~‚έ<=8m@}I•Έ©΄%ΤXΟ›φP·%nh?NƒϋΑώ{Xμ ›‚›Ρρœ ρσŸΑ<9όμK)‰·λW™/ι†Ά#b~?‚ΑOlφQί,›ƒ}ΪύδΰΧΉouMOΐFΰγi]7‚m(ζ+ΕΈύξdOή2=Ί‘υ^ ^ ?ƒ<`Κ<˜[»υΠΚΠΑV¦Ώ—z»ώΩ~έ^Ζ£λH†ΞΥU`š’< c3oέ–Έ‘b%φθ•±φ#sλϊφ2·ΜΐΗ­Ί—·ί 9o~Ί£aUΐϊπ~p})‚ύΰ'ΩΫ@ΙΎ°ήR²/J[ςΤCσh«£}(ΜΏ† ΰM _ϋŸ±¦=눎ZI»zK›™Κ9(υz>γηΩ=η'ΕώlTi]ώΉ έΎ seo…΅α%°|€ήηΨΒ>z ΆΕ²·ρ@6—Iεuƒw{aύ<g€Σ: ύ›°.x™)ΩLΡ+cΣ^Άgάv<`fσΦσb―ώ2ΏιJϊl½φΛ~ξŠδ%p/Ψ¦ ¨-Ψv]ΪΩΎy“žΎ[·bύΚ)M–#τΩVΛω)7ψ<ΒoαaxF€Ύώ8¦mαlπwΌ‘τΗ:ΣΣκφδO˜Ό mγπφQ’/SGς$$©’z<}HΊq%υ¨Η¦ήW±ϊΦΗRo€l·Μ_ΪKέΔmΧG€bύ§Αγ°ψX\Ζ€…α ί²6ΥΟ„_Βuπ$(ž±ΞΉϋ%RΆ[=4cͺ‡ζ«—·~ϋ«ΫΌάϊχΣπ}˜Rj₯;ζΨκυš§τk]7ήΞfΉv’ϊ Λ96ο’p$ΜbΉΆub^Ϋ±\κ@Iυ@}&΅Ύςγ/P7gΔΓΒMxlnTmΛƒ‹ίΌεœΈΨ³ΑPΫκν³a,£ΤσΤγ]ΉΊLžτίp5XΌ(=ύD½ΜSnΠR'[bސŒ‰§Ϋ[Ή<τV5αjΠ§ζΧΟΛΐηα"8~W‚Ÿ4}Ώ|¬7cFOL+ΣOh€—‘Ίσαε―”ωO˜r‰*Κt₯vύ¬η)Σ&€;φψΈ§ΌνڈΝ0RΧ/σF·Œ—””λΆLξƒΩΛu?pξbG­€½ΦΗκ'εeAϋ&`;ŠΗ=ΐυu0ό>KλΟόϊN»kΚ΄ίΓSΎΔ·ζ ¨½σ+υ°Λ:ξ§ιξAϋδΎτύ(¦νξ‡τΙPiΖVOέ0Ίy”zΌΛΪύgΖ ΥΗΠ’Eς‚θΗ@yωoNά©cR,_ΦQ~LšάpΘ-C7G·5?M\#ΑΝ`žaΰ!“―­,K΄Š{X•ybO~Γv6ν™οvyS¦]h9?ΕzΩz Ωώiΰa£8ΎŒ1qΓ‰•²ΤQΪ’ΫΕKXόŠρ0ψ;Dόt΄#l ~_nΛ){CκJέUBνGιΫZRλl—'6Λ¨ύ½Φσ7Ώ½—•ί~8–+₯¬§΄·ΣSvw=l炬­2κ,mκ)έΈ}TΚ2u{WŽqεΛΌ)›Π΄ˆ_Α»ώmΓ9MZBLέ€΄;―ΑLΡ}H,^ξGj~βί%l‡Βwa dl―’{Ρ›ξςS`YηFJI;₯mRtλS:…χ“ζζAPΧφqUH΅•mω2z=½΄ΧυΔ K±A»zΔϊχέƐuηΓex.:7e’ŸΘ‚θΟ:κκς€I„lC½/©΅ΰYπ`›f„HΉψέ0/ΡΛΰ†)7i©;―e<:ζκHΌShΎˆ}πr°]?ύ .>‘”²Ώ₯}btλ*±Ž2žΆτ―žq/篁ύ]/ύ{¬Σ4xΘl«ƒγŒ_Q«: υ“bэ—zβ>*²―:ω7φ”IΌ ΫΝa꽌‚'ƒλ+ύ-Λb€΄EOša|x ϊGA?Ί6“7!¦ρlI«‡ζ-ϋ½΄k‹½^ΎŒ[FΡ¦8›Α‰Ύ£Ž'©Ώ]žΨΜγXσ•AϋڐΆ†£ο ‹‚kLΉΆΏ1Ϊ΁ΰ(Έ¬S±Pϊπ#S(γΓz¬Χ0½šώ8< yφAwΌ%γ4΄ώ„κA[μΡːδ*]›νvΣυcϊcŠ‘vΧςI𱦞|D[eΤ€=IθΗ*ͺjzΐΝ07|²išIUE1/*7l]Μγ†ΝζΩύ|Ψ Ϊε/Ϋ‰^)Ϊ­?¦+ »bέz1^–PΏΐ2Ω¨ 1URΗ>±a½>γu<ΰΔΓΔ՝πMπ“γp/ψΙB™΅+hˆ_$>|˜Y―υ€Ν„˜ΪJι;Ϋ6i³ρVΖ£—a½ŒiιΗht?u:ΧHΚ‘VRΗn˜΄2|ϋΉΰš^Jςu²ΥΣSΎ΄ΧυΤUΪ΅Υγ±%Ώ{Α3Λ‡ή„DΏ(ϊ¬D{ζg(ϊΗa>π«₯ ’6τρ…ΰ§θeΰ,x——я}—ΊQ'Iζ₯τΰΥ'™wΤnΊρΊΨο›`6Έά³ŽΑqϊ»ώ³NΕ°'ή)]{€Μ[»PΏψν"ΰXŒΏγφιΗpτΆN²Θ€z`ΐΩ“κΑρΛλS΄Θ%ΰεξA“"° =Lό ςO°(ζά·ΐ]ΰ¦Ω€§!β& Ϊ’ΫυHζ[›zcOX–q³Ϊ§KΑδ)p£ώ μ·ιζO™zH»"ι«•G·/K‚ΎΚnε50έOϊΑςPŠsζαιά–σΡc¦+νΖλ!»>όΎgA¨γIό”„2ξ˜N]·ŽΕ~%jKRWΚ%!φΔΖ¦GOZΒΨΛ:]ΓϊΕKF½^ήΌ)‡ΪSi‹έ°.ςΕ^ν‹r ƒO€λ — {ΜΛή}δ§xΏwm―Jόc½—€kΰLπS΅ΆΑΗ°ω’ΧΠ±;ο†Ψm”cn»Šρ Ν>n >F·…­Aω5ψ˜Υ’qX6z•PόH½1•ρθ Ν£$ΤχsΑHΠ>˜άΫ€λΨ9σ,qL7Ώ}‰?Λ~©H?z “ԏUNυU•>u£yi>ΫτŠ›Σt)uγ.ψ%ΰrπβπpw#ϋϋGΏz”›!e-ŸΝ‘‘8I•ΔžΈ‘εK©Η“fYΫ°_GΐA όφ' “{£¦φχπ`w‚>4½ΔώšΧCiπ“‡eκβμ*πpϊ+<ωtŸ:0΅ζ@›—‹υν ƒ‡[€έ\τ”Vζο«n½e™΄Σ“έ΄ψ²Μ½L‹nΨ^Œκ±£vΣλρ2Ÿiuι”{BΛE7Μz=}π!¦ΜKƒϋqx!ϊψso–βXœGΧΕΰee©΅›_;ωΨ|ύ-ε8­Ϋx0nCν ΒΖΰ8~ ž%^Έϊα)H~ΤJ&4–vν[0}Hzl†Άg?ΦbϊsΨ”¬™ύΠΏ#ύ―$4Oϊ–°Κ0πc=PP“^Ϋ@ z ΑΠEμ'CCγYπ¨­|₯ξ΅;ΈYά ζ^&N^@n^/ΆN›ΑvΚ>$nyΛ)±%_—΅ϋOΣlO9쏇ȷαz°ύN} ι=•ŒΓOο3ΓΟ‘έΪΞΈυƒs’/ƒ6γŽΡOόŽ[†ƒŸVdψ­‹ΉrN­S?Ύ?ύd<2!?™7€LYΎL›žrΙ—°¬7ΆzΨ›<)cήΝΰ;p”σ(Ι[w₯φό3eκΉbofM^B‘KΑGή*0Φ†ΟΓV°0ΈΗzλ3<Μ™φθ 1΅lκο•ΨQ&€»½|]ΗŽΛ‹ίu&ό΄§.ΤV}½΅YF©χ#6/ωa„Δv½θΧƒϊyhϋ§K?wΛxςξyΐMΰFT\ΤεζͺŒΝYτζ/ΏBΫg`=«P9w©7›± ΙZεw#ΎΉΛ°(XŸ[.γ~½\Άc[}ΗΪ ΗXϊ"qΓθΆ•xl–)mΛφ“ΧzS‘c\‚Ωšρ²=σ(e]]–ήύ̜%L©ψ3‘vΫύ8§~ς_FΑp8Ξί< Ψ'Χς™π=πα¦ψiωgΰϊ±Ύ΄S%eΫ±΅ ½dϋ[v§>ΕξCφa0ώWpΟ*~ϋ‘£’οD™‘+hΩΚΉ‹n–ϊγ‘μ“ωlk¬ ΦιΌμ χ™νή ZkZ}@ήCdβίΓ&š*<Νͺ©Τݜ7ßΐΛΕΓvgΨƒ‘ΰ¦ΚΖCoΉ_˞ƒ£›φlθ2Δά뢍ΐƒΡ2?€ηA)ϋ›x•π.ό°­•@ΏδSoΖq*ŽΗCF‰ΚΎš/y =Μ*Λm ί‚εaMΈrXΌΎθ“ΏΑ/a3°¬u₯ν²=Μ-ϊ ^Χ}ΦH}.ΛΆΙΦQ|eMtΜΤΗ„Nm—φθΞέ]Νϊο&ϋ³Œ%Wύ·$˜^Ξ9Ρρd¦¦%{,nκZ…}κγκΛΰ^²Œωއ+!bΎ™ p²dςx Œ­gσ€'Ζ½lΞ€SΑyrσlΫΒ‚°.˜―ά<₯NR+ύ'θŸƒ;ΑƒΫ|u0΅κ2νΨ<4έτΧΑΰΧΰe;υΎ“ά')λjWΠφ‡Β p$θ‡žΦmϊΣ.,}έίžϋβ©Ÿ½<47σΓΑOpΚϋΰΓπ[Έ‚@ρ[ϋ9‘±™·“ό›„a03ƒu…¦©}B‘cΆή ελmΊσ υό˜Ζστ4oγθ`p^/―Ωΐ‹λ“°+| ~Ξ™λάφή€ΐΐ‹~WΨ Ύ­1`ζϋMSΟΪОu„ΪM7ήN2Ζ‘θZ±ΌcοO)ϋTΦ[oλYΏΑ;μ›cσ~¦…ΜέIθ>ξυm$i Ν?žUΐφσ8ήαΰ·”Φ3 Ξ‘β™r,άΦ±μ€Ld±N†¦§Ϊ&³iΪ-z7g6”›Ι‹χΨά0~­·9| ΌΦ‡ϊEc½%„nΈοƒz™σxφ9±ν΄›~1ψ­„u€υΠυδ‘j½½λθi:Ύϋ`$xp™W?υF¬;$Wχ ςbπςH?vCwΆ3xpλΑ&p;hSμίΰΈΞƒ½a^xτE»΄mΨIβ;η/p68οΪƒύ,ιd―ηFΉΑ Αρ•ι½Υm+y‡>Jϋ υ™—‚e"₯O:ιΦ«ψx±όςΰ·YŸ†‡5u`©ϋiτΓa½&ί!tνΞ‹ΐάΰ·σ€sx=ό [ϊΪm½‘δ;’Μ•wKŽ‘ώ)γ7Χί‚~˜n€'@ …ΝΑ‡ͺe]ώ}Δ /,_Ξ%ΡJ,k=ΰŠ87ϊUp½ϊέΑ0,χμΥ λkσ€LΈΨδ½σ@6iZ4[Β€Ίqf‚ί‚_5ϋ­€²μ^2nΦv»ΞKN)7v]OΊλb4Έ‰Νsx8ϊ ‘Ӛ1Ÿi<νΖ‚Ή%0ζύ=\ŽS[;I]W“ψ ˜wB’2ΙgΌD{WΏ EΪ=ύ秦=`IΫΐM1~ϋh?–ΕzΚΎ§ύ*±φC_zho '€ρžΔτNθηψ{-τΣΐΊ]νʘ?΄KΧ)ΣcλzA8‡ƒz;‰Oκ/uC};Άηΰl8V/sE{‘o ƒαDΈΏcΞόύλ°+ψΠς";κbΫJΒXΟ?“Χ‹ςΟΝ¬±υ\²ο©©7‘58wϊkμΖ‚9α4p¬>zΏξ'Εςχΐ5 ŸŒ—σL΄%O£ι?σθ;CύκεοƒΚoR†Αη`A°/OΒjπ€–‹tj+ια»δ'c@&²\όΡέ,ηf˜~ln:7œŸ€άdσΐzPΎΖ‰Ά6pΉΉJέ<‘Ψ =,ύdπρ$ώ~Υ΄₯^’=Š}νmή+j“XχCβ -β˜Χβp8ξ@_?υΎ™^>p|(hσSΦ0Ψ φEŸ9OΚK O‡sΐ6ίŠσi;φK©‡]ΦqφΔΛΌ₯­nφΥώ€ώvωzk«ϋ₯§Έiρ“­ν—ωλϋΈ( n²‘—‹η©τηˆξ…ηA_:/e½D+ΡfΩ•a 8¬Zη†p%Ό Ά―€Ž„]ΦΙσΣ‡Κ p L ι£>«γx΄šO¨{ΩϋtξΚ)pP₯u7c-ΓR7kβκϊkmX\ύCΑυeΫΧΓf ίΕG—{J±O?΄_Ώy†Ν–³΄SΧIώς€“0 S†\θn\ΕyyΎ ^κ˜Šλp£½ΚΒpΜƒ›<›΅%©» Υƒmχ98Ξ2œ _ν•}$ΪMLσΡβ'<υv}ΐάM—ήδνV°‘²ώθ†b υνέp18nεσπr₯ϋ‘Ό±ΧoΞ‘>ϊ|¬ΟGΐƒπ&(>μ<ΐαΰ\Z‡€‡_]L‹Δ_e<Ά2ToΗ΄Ψ=dνo»τήΪ(^I™_Cβ]©γ~f ^FyπΔζ%’Ψ§ΐN°|N†QΰΪ6έo.†=ΑΆφ‡»ΐ‡…λήρ₯^Τ–ΔfϋKΒ"0¬σvπA­˜/P↓K£β:r] ρjβXτ‡βΪzŒ;VΏϊwœϊΪ5ιZU2i7aWκ8ίƟk’°8ΈΎ—…#!r%Šƒδ՞xBνΞΓ€L 8~28½ΦdΉ)Κ ΐΉyόέg%σΊΩž‚•ΐ Kρw ΗΑP sBŠljLΥοšΟ2Φ±-ψ’7ώGxr˜ v”τΏάά3“ΠΫ|=ΥΡ—΄΄—Π²ρ­‘—“—β‘ω1ϊΑ2)WIͺ¬CΏY‡ͺ_W/ΐρpdΟ9gGΒίΐovΏAy ¬G;'νD{’ZwBυvρΨ{–u₯ͺmΥ[ΪΤ{’ψ/eΌ΄γΒΰ'ό/Γwΰ8–Εόσ―ΓΖ0 ~ ξ }l=Ώ’6J=sε|ϊΐpάk!εΧπ€ΫΆ_)‡:Ω$}Π'ξΙ+ΑΎυUτΣ /}Μ³©ϋ ϊ$ψ θm½ι“>τ1εYύKΰ<ΨΦ°>xιΧHΚ&>NfΈdΚρ€$›$₯—R¦9o~ειWj7‚β―ζ²νvzl†ΡQ+έ΅{_CΛΊΚ:Τ•².KχŸρEΒ΄νE<¬›ΓQππβ ϊά<Κ°.ΈΞυίΥΰΕmyύλϊNύ 1΅lκΛ σmΆα76?Ηδœm^Άξ£wCβ»ΎΦm?Ο}h?ϋ*–ΣW~;₯]cΪή„ƒΑ~ιm$ιζ ƒίR:‡ϋ‚ύr­ώvΑΦΙY”x9_λ—Τ5N€?‘Ž{—Š•›Β&O›q7”σ7.Ε x ,Ϊη‡δCν(n|/’yΐCPρ1ΰ'ρ²JφtX}Š•τ?LάΜCνvψ δΐΪέΗUφJςΧC²΄$iΤ-λάΨ†Ÿψ7λό%Dτ§_Η~ ώ 7ƒι’`Ή¦ρ{ͺwŠ“TIςτ%΄ίΓ`YψOUK׏²--‰Y*U»γ·/Ηηεϋψψι{˜τmά—Γ°ό\‹>DD±ΞήJςΞA^XŠύ9žΗiέΖΧ‡£@[IκςRœq,ν|]·/±œ }«ξγςjΠfάΗΧKPJY§yΗ1 €ς°θ“ΎŽ1υS΄’·ψιaιEδΧΏ^vž―‚b»KΑΐƒΪ‹r{°ΤΆΞuΉ3ŸŽC{β†υx™Φ“ώe―…SΑ>%/jΛΪJ1n»eζ†₯Α‡ΤΟα;pθSΣΛ|6€•aC8τ“y€ξKL½λΦηϊSŸ}²œηΑΊυ­ι—‚ύ7ήbϋΦ΅άC œ7’%ΦμtΓvbτγ=03ό ν‡y )_ϊ[]?†…ΰEp=:§o€ώϊ|ΌόσXEν(eύi³c恄wΗύ±ߝžM=΅Ίψ³ΛQgS$=iζ-ΡnΏ†ϋ.δ«εέΠχΏvΩp©SKάΐ/ΐ1M‹λΒ―eο…΄Ÿr ͚υSΪ΄Oι_ΨΟθ„ϊΞKΗOH§€‚β'€ς!€-cN¨­.νlΟ Ι‹ϊ"πΒ;Όύd΅-ά)λκ'-ϋσ(8ΗΦΉ“S5ξΦ­˜V¦WΖ>ό˜ΌkΓͺ`½ΫΚψGί >gΑχaXΜ£<^>Φ}\ o~Η8!ιi|Ά#~ς^Fƒ™σμ£ΚŽ’ώ8/ΦgΨ_’Ί½όŸ…Η!Άώj£“lΗ΄€ΧηεgΰVpοƒu‘.)―ΏFÜΰήπαδάκ'χΛ.p"θgηO±¬τFΏήδΘӏΘޏUTΥGxθ 9΄S<›Η‹)[β ݈Ξ₯_­~£iτe>6C ΣjήQπp{8ό^Ί”}P/7o©ΧΛMΈœβυ~(ϋ―]‰-α,؎«RΊ>αx Κ„>Ωdό ›Ut œ3ϋg~uΧΐωΰWξΛΑΑoσxΨξwΒΉπX^ΧΙ’pμ †NύΠ^Σxbξ/Νv’ϊ]W곂ύφΑδ:ό| Cδ”Sa-g‚—}κBmIi+uΗζšΥ–9kj*±›Ουoής’ϋqΗΥiOΤ―ς7js ٟτmb(}aυxl©ˆyδ p½Έ–―†Θή(ϊ"ϋE{.ψ Ρέ;±½`DS7οΦp:8χJOcKšύˆn™vύΧ> ο²Κς.75P},ύ0XΌ|#Ω ³a K’?›υΛΌ$Ό,άΠƒνaπΣ¦m€NΤJΌθΏ¦yθŸ/BYΤJ,W§™4EφUΏxωΌ²ζγCL-Ρ¦$ν(tύθΑw0Τ}?Ζ'dΟ·υ΄”1―υ:GΆ§ξΕζ‘κδnψŒ† ΰ πΒRΜ7 NKΰ7ΰΨnσώΜ›ρ v“Œ― »e("ζ±½Rτ‘θ[ΧΛ0ΨΎ gΑ‘°9̊cϊl φswψ+ΨGΛ[Ών”Ύi§k3ίπKx ςx(σcΔΌ~ƒ²,φSΉώφί μΛ‡ŠL§’ηαUΆeέν€ΜΣ.½·ΆςrνmσM¨ύΎœγϊύ>Πož7€6ελπ ؞—ώΉϊ2ϊ 8χΛΒυΰšμδ7’dJφ@_Ξ”<ŽΛ}σpr² 'v,Ω„2Φw2|΄Ϋ†‡₯ί ,@7Ίy·/ε* Ÿ…Τ‰Ϊˍκ‘Τg}•²žή”΅!½ΘάI^Ώιh7¦ΨZ­—ηKπS°o³ΒGΑvΤJ7΄~Άm₯$―Ά2­΄'Νώ‹u9G'Βόΰ…fίrΕΛχΈ|Ό|< Ÿƒψ΅κŸ}μ„y#φΙΈλΕΊ‚ΰ{`_?‘»ΖΜχψιόc°xqh·οζ©K}ΜIΧ^#㊏‹“ΐGΕίAΏ΄˚ΆΜ>ήΌτ\?λQΚ9ο²t9Qϋv7Ώg±ŒΫ£†t€ŒGwlŽ3~Μ|λ—‚kτΠ/ΣυΑ‹έ$ύφΞN²ͺΜΫ·{9ƒδr%)’AΔ΄Τ5‚9¬ϊ)fE@L˜1―ŠbΊΊ*ŠDΣͺˆHrΜ&}Οs«ώ5§οTuχ„žžΦzστyΟ{r>χVuίy&π"₯ί7;Γ`9ΪF+½βšO_Ζ‘21Ζ‘θ~‘νpQΈΉκφZ ν¨#:I―λ"> \Τζο&ΌΌ¦ΓΰζΰΖόB0Ν½πsΨžΩ$Q;Nk7W›ε(κ‰S†ω‘xq‡‰Ϊ Ίν§ΰ4RΊΤΙΔΆ3Rκ±ιjϋνλp?(G―E›ύβ:Jt„ΧΑ?ΐΝƒO{@ν©'L·σp“φvγ>^ϋΑαm.‘ΦΟKέΰp*xiΈ”I-§ΣΞ΄7nΪ’ί2Χƒ}ΐΉσcxxΉ°ο£ά¦3μYπCπ ρI~”δ›~θε–ύYκ‰ŸρΜΣgμΡλεανμΡm«ϊqm½t†έvm7Δ°]Ϋ‰^φ‡a±'^ι:ΌόΩχΫjΫcΫ£ο@ζΞ{Ρ}X°ΫmMσ0Ύ„Λ@q†λ»:ReΊαΪΠ#yίΌ,zΐAξˊΩYq‡«eΉ˜Œ§ίΕιFG8\ΌΚζpΜ€νΑΝΪM@ωx°>Ω8šuˆΏtKnΘoσ-Γπφγy8=<,’uXy ‘Ÿ‚λ‘ΩώaΆGJcΈιΰ†η‘ξAψ4Έ²§ΎΊ±yΠόώϋΧπΌ΅”ι£'l8Χ [¬‹OΒ3Α Ϋ>Ή σ[γ‹ΑKΓ‘ΰεΠKO~Ά³Δ‹F.,[£ ώ›Bζ‹sκΫ°k?η-¬‰“~NΫJ7:ΡιΓ½tΣΧ‰ΣΜΓρΪΦ/aφ‡νς큗޴uDρβό½c-ΫΝv™»uvMκ–}wΏΆΩp=< kΕ΄φΕ}p-؏†{@ΩΌΨΩoΗ‚oZ”«a8orY@]b±‘²­±υέεΠ~_Vœ(ΕβΦͺΉˆβχΰS —€Α1χΙΘΕύP8ζ€ρOŸ<ΐL§-ω vτ¦MΏρ}:~Έ‰(™_eόVHλ§vy8œ›ƒωŒF"G eŒ&M·8φy0\½¬―›έλΐΝCεΝ $Žnκ .“αGπhπRcžΖ‘ΔIΊψ ͺEΏ·εk₯΅.ΝΊ:v «ϊΛΑ§΄gΐWΑΊ(Φ}OΠvΨwΐνΰ%ΒΓQCΗοέπψ0†)ŽΟ/ΰ…°xΈ\ wΓ“ΰw`=#e;’λv#}”ώLΕ-Σ—ω'/m–νηΕΗClp>›‡o'Œk?VΜΛ>LΩ£M·4ρΚ²6ϋ}wψΨ²OΚψΥb½7…sa pήDΤMγΈΩ7ΐίA»γοxΏΌ(ο"Ψ œgΞ•€G],1]7ιVnρϊΆeάN’Ύ¬x=°Έ , (nΩ"7ŸΘ‚ahσπ^π`sΣό\ 7‚εgΓG")C·ΤoZ7›/€b>‘č_ΧpλrΈ©]½Κ%¨«˜~i€W½bχiρη0 μ§ ΰh˜ εϊ‰n:1]yψΗ^ΊDιHμC‘ζœOiιSέϋΝΎ°u―€ΰ₯`ZΕ0ΫΰnΏ_ ΖΫž_ƒο‚α[CϊΧφ}Ά„§‚ρυ%Δ>²^₯XH©kK»cίςΤKW½ιO|‚κ0ϋΙφ= œΫϋ‚βx}gΪF+|Ν66m·xiG·°τƒaэοψξŽ‘m΄Ÿ#eD7o‚^Ÿϋ’ΩvΧ«c8œ/¦΅œGΓJ όφΛ4|Ψœ) u©Δ|ϊ2=ΰ$ιˊέ#-΅‡Ν³ad£Š‹©³πoFί.ֈ”y Πλι(qKW½τg£Β<Δ_Iά–oαOλzθ–υ^cό47ŸŠήΦߍο(π‰;νkϋ£7]‚jiΪγOΈΆˆΊOן„ aPΚΎŠΧpλ’ίρ<Φ…Cΰp D6Eω œ…GBκt+ϊoΰhπΐyά †O%eκFJ=6Σ(Ι»—[Ζ)υf|ΓJ±½ΎΦφΠs.― [€ ΗπΰaΆ"ΘZTΒ~Nl―’뜳Ώ?ΆΣˌγš8¨΅θ/ϋΎœ'#F7ΟλΑΓώχΰώ`š\ϊΌψ:ά_’χΡθΏηSζκbKYΟΕNάO°lzΐΑξˊΣY˜‹S£‰όψox<Έ –ΜΣΓδPψ)xσx m>A*Y q[Φ–½άxΤγΧuc2Mӎ©–č_ΧψΆc<$uMΩeύJύ\"\ ™½a―Άnœr£ή͍Ν4ΓApG¬Ÿι~ί†ΌΠ.nΦ½p,ξ7j/1ζρdx1œڝ’±ΣξAzμ §€ΎO‚Ζ³ΜΤ‘ιΤU|ΊτπκΥvΫ™>*υfό„%αŠξ³α°=GƒeΪΖ`ΌΕϊ*—±•ΑΎV¬»(₯[Ϊ}Šχ’[ιšNΡζxDlό'γhίvχ„ΗBΦΎ}η…όIΰΕΟ=%y|έ9αe!ΤZΜw΄’όŒΏ8ιF›?ή(z rQϋQ–C,Ι’pCψ <N_;χσwΓρPΈ·ˆδB~=ϋfs#ΑΤΩxF«'^ι6uύγ!Ωtšn·ΊΈYή>)›AmζSζ/Χ4£ _ΛΏ<@3OtGΒ±υΐφ:Ψ†ÁqΌwλό8 .…g€‡€C·ƒσˆbœ[ίΫSJϊH[τΈε![―΄^PΞ‚λ`Oπm‰ιmΓ/!嚏·εϋŸ90χ‘¨·ƒ}κ*λ]·›ξxΗŽΪ‘¦-σΒMέqάμ#Λ?œΟΎ 0η‚ϋˆ—ΗM›y(·ΐŸΐΉ’φ .Ά$?φΟ‘ΕξΎe“ ίρΛ¦Η2—,Ύ,πΈ)SΏ‹υBπ)Ύ\”YdΩtsoϊ> ΖΝ'pŸ ƒOŒnJςiω†ώL}t££τΗ7αΊΛ[Κ:τ*»Ή.LγαρI°ν«§ŒaΖΧ-Α;Δo˜RΊΡ[!έfŒ’VΏuhŽIιwƒ·Ž^χ‚w€ˆ―Α‘P–{.ώ7Β&πlπ-ΓmΩΕtΧΒρΰαυά Š‡EY'υ&Ζ³>ϋƒυ±,Εz€.MέπŒCΒβ6ϋΫΈ†YΧΑ!‡λλΠ½”koGL«Δmωύ™ϊψρΒΘe$φr•φ$―”_Ϊ‡ΣM—4Ι£ι6ΗΒ>ρ©oΈfΒQΰ˜*—‚y:Oί:”b˜y–υ2|€z§σθΛ8χ@&τ8W£_|»ΚE1ά‚ΚβKœltqΛMžY΄$π=ν¦!|\θnGΓQΰFν§ΟτΙKΏΝΥ6uβ"}όcε–υ±ŒΤΫω?₯π'^ϊ*~ΣψΔσψ>dS<ύH<]σ ₯½©­ν₯€Ÿγ&Μ±q|3ΊΪDέ|<ψυo/‡ΣΑƒζυ°άΪv/οηΕWαHpμ½žφ²:˜ζgΰeσCΰγݐ~D2Oτ+ΦΟψ°δA­%}Pφ‘y*ΊΪγv‹›t^2φ€ι°xˆύΞiλ8±/σiΪ“ŸφRμΫ£ΰb8–T2^eϊ²>Γιeέ―ΜΗΌ#κΑώsό=ψwΗν9πLΠ> > „ΫΑΆ>6†ΜΤE€¬έκΤLdœ²žλfΌΎŒ{ ίρcάΑΛ8ϋΡ,nEf±ΉΠ}ϊrΡηΖτ7ΐ[!›Δθ―79Ÿv!M7ΛfYρΗ%ʐΆ6Λ0Ό›XnjŸαρλZc*°3απ;8v‡5@qΓ#›ΒGα*07xϋCW‡³`?πψ<ψ΄h™^„Ά…Χΐ­p.xΉΠnŸΜ%υlωZ?΅YζHϊ‘t›z7ΏΆξΎ©Ωrh}ύ>°"e:mM/a‰χM”Kΰχ‰΄ ά”‘;έ"―[ριϋu\ [‚~δ§8v/„ίΐ]p8”cΐ±7NdΈ²§›k}ϊ²υ@yͺΏuUΚ’E¦έΞqσln eΈqΊ‰yί .huoό>ΝΞ†Ϋΰ,p“p‘λ /…ƒ`:ΈΑ–εtΣcΣmκ½lDνΔU_R–e~ρ§NΞ{ϋΐ‹Oπ›€νN<Ԏž4ΪLγφ-°δΕπ $m\L΅MW)σQ·χ΄1_₯tΥ»ωM«=_ξΫύ-πs8 ž~$€XΖωΰ“»‡τΓα,X ΜΓz{HYqρ0υl>1ž ΖSLΏ/œ Β;α@πΐϋ4}Z§ΝeΈΆS”nzl₯»q-σQ`ύ«―€υ΄>Φ₯ΜοˆώΔ1νv Νl»8K$©ƒ‰£λŽF/Σ¨G2&q΅«›§ξώ0μ——ƒm°_”§ΑηΰjX ~¦ρBηόφ™ΌP;’ϊΖPϊΥKβΔ5ΏΘpρ§οŽAΈ ϊ²bυ@Ή0ʚeAΉ(]ΈŽ]sα4ύezγoo§3Ÿ}­νΣΕΰΐsΫ~˜}ΰXΨ Ά Ε:6λ™²s9Ρ[S72,ώ2žΆΕ‘6ΙΧ΄ΝόJΏύααψ)π §™S-I£kϋν+7ΛΘSP6„¬₯2Ύq⏫XζΗα“ΰA›ώŒ‹©ΣΟΪ’γΆ6Xξ·α8λθλφ-ΑΓPύJ°Žβό™ζk›š`ͺΓ<4-kcπΰπ0}>l//ŽŠι΅½ ώ./ΐ˜ŽυoΆ SGΎΈ Π―4ν-λB»υτπš ΞAΛ< fΑϋα³`{’OΣ%hHXιWWΚ4-ΛβύLϊf^έμeœθ₯«ή”τ―ΨŽΫΑ0œ;oΧ³αΞ“}ΰΗΰ!ΠζΪw½ΰ*οΗR±JιͺΗ_†©—’ϊ•6υ2m3¬οΓp3θˊΣειΆ(\Μ'ΓμF΅»Ε7Ώ2Ο‡γίΊΞMϊ"ΈœΎ"=Ύ ϋBζΖVθ^-Αƒ£›XΎŒιR¦Ά²^ρΗΦtΛt$•$Ο΅ˆƒ&ΆdPϊS¦aΧΐ+ΐCΚφ7γ•q ΕΓ΍˓Ίύ³'7}†:$/ύέdŒΣ!›kΪ―¬—Άωο'ΐ9ΰαοG5ΎϊWΜγOπL0ξλαpΞX―²nx‡Λvœ½Tά―eRΛ©ϋλσθΑ#α»p ( [ΐϋΐ|NϋΘ·ΐyoΣFΤzξΔ¦_=]·ζ³98ώO†τΧΫЍo?{I[”2ΏψcΏŒS¦ΣήMŒSΖ‹ήΝ-m₯nΎρw+£΄ΩΞMΗπpXμχa;Pξγ;W.‡Υα' ˜ξ©π0_eΈz ΦJέϊiy‘Ρ¦I&ΘΒͺ@IDATόΎ»Œz`q6…eTd?›az ¬ŒR.78717Έ©νH†—qΪζ!Žιξoς‘_‘όάΤ³ψWEw£φ©r/p“pΩήΑŽ ­œ;–ο…δƒΰΒ§ σUR?㧞±•αΆέΓCWg$Χ4nX@οΫbΩέ₯ ³ŸΎγΧUβF?‡ν—!}p,ΊΆ”‹:’ΨGΒsΐ|¬KΔ|μc/TΐΛΰGpx¨m ‰%ϊρ`^GΑγΑ'7ΗΡτnΰΖ]\l«σΓKΖg@1ϋΧ|­³εœGΒΓΰ™πCpLγiϋ?8ŸmΐΉβ6}ϊμ•θ>u–~EQ2&Σ‰y8Χ]~Όu587žO‚{ ρu›XξpαΝψD―₯ioϊTΪ†σ—aΡKW½›”γk;μϋΓύΒ9~"ΨGΚM°?\ §-Ž«\^‡³Α|œƒ^,#RΆ§ΤΎ8ιϋ2=ΰDιˊΫέΦσ¨ξ[ΐMv΄ ΗWzΒvΰβώ'ΈΩήŠ [<,ΔE1μχΆMΰέpψy²ω”ε›fΈΩΈy˜_ΒK·©ΗOτϊ097ˆΆ^b:ρ tc ό7˜ΦLIœθ΅±°n=u₯t›αi“‡χΗΐΞΎέ fΆuœ!’όJ£ωΘͺ`ϊ2ŽvλΏ| ώ Ÿ€ƒΑWά†»nΟƒ=ΐ1}'xX{'§Μo-ΪGC;zΧyr”ω™‡ut^x€Λπ π X<μ#Šρ­Ϋλΐ'NίT<n‡δc›Œ·~Ϋ†SKYnl₯kΫ€c²˜‡ς^π­βρBd=Ν― ¦Nϋ ³.Jβ΅|½ύΖNΚπnyZ^β”α₯mΈόΣζΔΡX\„@Ή œ7τφ‰ύoόδqϊΰΨώΊνβΤύx±r$iCκŸψ)#ώΈΝx±χέ1ξLτ1.¦Ÿύ({ \ ΝEQ.ͺ^γV¦)σςωΈYΊ!œwCΉ˜&ΈYΎ\–ιu,ψTψP0­bšiπQx”’:•n©'Λiπ+°žΖ‚k±lλφΈ¬S‘vΥ›εχς'½bΌτ‡ρ›ΑMΤΝς­p/DŒ§Δν¦§ά΄Ν3ΰpœΟŸ`#žο‚M`ПΉ`9_σ4//nJκ[Φ£ϋg™ΖόRΟΨ“2~έΔ³>φΗΐΊ?~ ·‚b<ί"}ΌΈ½φηη> -mBϊ›bΩζη<έΜίƒλ ˜©j§©klρΗ-νM½›Ώ΄%-Γ£[·RΊΕ7\»’kΏ:WœγΫ§θjw>\Ώ^€\“Ϊ” aoΈ7¨Cζˆofμ;χ /j7‚εξ;ƒ’:΅|C6Γτ7m¦(ΫΠ-|h}ߘτ@ΉΠΖ€€~¦KάYqΝHέ…“MM ގ_]qρ>|sSπ†οFp˜ΦΌΊa9n6j¦ύίMύMπΨΜί|Νceπ’)λΦԝ{Ϊt3O@˜_3ώp~’ΧV3NΚΠ^κΝxρ›z7I–Ά~έƒΦΌχ7W7PΓ•^ρ½πΨ·Άυ‰π-8>qΕxοGνΗΒυ έ1²Όl¦ζ›±@ν”―Ύ€’vΗ5ŸθqK›υvήX?ω<GΓίΐΓEΡ΅=gΐοΰ} ΝΛ‚νH<ΛQR^\ηήV`»ΕΎψψ›t¨)Η7Ίn‰ωΕ_κ±ΕΝΑ/·™‡uτ0υςh{₯ΣθΪ–#α\XΜ3νΣ5Ξ*p˜οΰZς©έ2μγΐΎM~¦ ¨΅nά‹ΫΊe8/3Χ>‹nΏ:6ζΡ Μ]ΕΈ½dΈ°^iϊφeΠp_Vœ(㲨• ΛΝζ(pΡ:ή?ŸΌ^–=.Α΅θχ°wσQά$^ \3!‡U3A΅dλšvσu³Iœ„·”2²0½νϊ4xξo„»@1lk8~^ž·€}βxn ›Αξ`_{|rMβbͺηΟέΈ[ρ#έζFlΊ™_ΖΧoϋ½ΔόΗΪ8eάRo†™ώl0λWΖU—¦X_/8^ γh[ϋυ`x-˜§q?φ­εW{‰υΆ/?‡΅Γ’―ω]¦½μwΫq μΦ֍ΫΣIΈεͺ—n"jοΛ8τ€Ϊ—h."‡φ{ΑƒΪ±φiΰj0—‹ϊ\Pʍ!z+dθO7 ‚cΑΓΛΈO†ηΐcΑr΄ '©«ξ² e•yi‹Ώβ”~7>ŸHΪRφIi[ƒˆΗƒ›©iί>9y=~έζ(ά !bίΏց'‚œ‡“γ– »¬ζϋΥ8ΛCμWeψδ©dάΣ7ΪJ]D»’ω¬ ϊo/–9М—φ§aΖρIΫΝCώ°l ‚eΫgj»y"7­’kYχΐΫαBΨ ³RŒ#‘^~ηύΕ°+x€ZvβŽΖ%z}@[~βk‹hλ%i“m΄ Ξ›ΗΐΛΐ΅nzϋꕐqA­%ιυ8ΏΖ3Ε΄Ζ‘YΰάΏeuψ0άΌ»ΙpυοΏo§h.€qͺΖΏu±έKiΒt£7;+q›vŸς_ SΫΕΝMή4^φk‡Ή%ŸΈ–½­ƒ‚ς;Α'έΆέ§γzΊ!žΓ½&$xH{rh·LιVΎα½$u-ϋ)y”Ά^ιγα%ζZΈΊIY77@ϋβ,ψ x Ψίί¬`|ϋΒCΓ2N…Ÿΐΐ10½Έ‘§ nΜ^ άhΣ% uάΕΊΚ!`]νέHκšzλ*Ϊ£Χ†ΆΝρ7?σYΞ…ƒΰ}πzψlŠσΛ ¨8§7e6|άΣμoσŒX¬ ŸσΈ Κ8x;¬cόiWάΥH‘zw£nš2mSΚ°θΪΛΈέόΪσq n 3αpν ڝo‡Α9gύΜ;uŽkϋ½x­“ΐ8Žcκr=Ί— ΓOί4˜ζph·ΜΔGνHlΝr!φψϋξ8τ@&ε8έ/²θ,–ΒT/*νέšρJt7B_Ί*<.ζ»τ ޽‹^ΧΕPkiϊ5fњ§›Λ‡αΰFΰΖπpsΠυd)iK7Χ:Δά„Ε5_υˆz™WΌΔ/έ΄Σϊ― ?‚χ@σ@KΌΈD©λΰ₯a]8KC[ŽΐΖ΅.·ΒΛa#πRφ]0Μ>²7ιδ›Ίον(PΛ₯•d`ΟYڌŠτι#ϋ›Xž’ΆE―m{©›sΛ±\> ϋ°'|2―ΜsgπΒ ξάv>{ šΊbXΔϊzQpήκφͺ7A]%c“yiϊθ ΣmΞΕ2NVΪS`ς)ύΡu=p·€™`[_ ώΆΝΓΥp"Ψ~η•RφAτΈΦΑxζ«Δn].‚•ΰψ-˜―~Λ΅ΟM?œ˜‡tΛrFΚ£•Cη2ο~Η/σ.]ͺ ΛEaFϊ]8M λ%¦qcςIvKπ φIι6ψsΫί­‚ΊŠqƒΚ΄n>νΎ2—‰ξΕγρ°:d“D­₯[[bKΊ±us/aν¬λ4nd>Y—υLψH›θŽπnπ J»ΛΌά,=<|š||nWBDΤϊσαoαξό§€i­»›h/±ΏlΧ[Ϋq­ΛΚ­dπ}Έai3ZFιΛ>5Λ¦ί~Έ .ϋυ*x6xIσ’ιΑ€˜Ξώςγ(Ϋια΄ψ€οX•b\/TΊΓI3<ώU #Νη2Τ)εθO?ΫΒ~ΰα{ φ•σδ9πQ°}ΪzIϊ:ρ,/ώΈζsΈž\ΛΞgΕ~=—ΔE]€/΄)eή-ΛПe[‡†τ}cΪΩHΗ΄~ζ£κζ"Πί΄%£αΒSξBš ΠnΙΣ…[.^Ό΅τ²XΖWw° ? 8«=ΰMp¬Ζλ΅ jσΙFγ{ΣMύcΧέMο πeπrb? ΧWΧmJ›9ό¦B6QνΆΛώ›οΣαπL°ΎJϊ[έ>:\ >͚‡ρ’'jGΚ~ΡΌLcί- ±ξOλ<ήRΆ7}Ÿ:•~uϋ=xyψ=|”f‚ν`^^$}ŠΪwυ^’Ή£«Δο|‹{ζΰHuhΖI^₯kΎΣ8w|γ‘ΓίKΠ>`ߘ_ζ}9o1wΪYφkμ#Ή¦ρβΰzΊώΎ…q,^ύΤuˆh¨›_lκ}ηpβτeΕιr‘–‹*zOjάτkχΠX<|ζΒΰ–…[–Ή^”ΊJΉ(›ρZ1Z?ΟΝWύsp8Μ€ϋAΩN„ΗΒ¦`]R_έη‘―3Ώ Ύ₯Ψ nXt]ύM¦zs·μ£a{ …€Ύ…©wΣn{ΜΫ>LΫtΕ Φ>Ό|γ±#D l ΆΓ·Ά{:Έzˆ§”fΩe˜Ίρν³Eq›’­μ“θΊΑ>ρΰΣώ ΐώρp΄―ύΘδGΰψί‹ΐ‘π‰Ά{ΈλcSŽEςΖ<’4λ­ίςtKJ[©'}β¦ΐ¦_{βͺ{ψ?v/2^p|ϋ”ω±ϊi`_h³MJά–―υ³›-α ‹ϋ(·ƒ—‹Ljϋ,Xlc€Ω–²‰ΣtΛτΝ°Ύ { ίρcΨΉKu·Ε²8 Κρt‘Ύ«]ΆΩ―ΐ›ϋσΑ…ΕS-MμΓΉ©―εͺί…Y lο€€MΐƒΠM*’ΆιϊDρψ˜>aΙ[$aϊ£›·y<φ7MmM)ϋΐ°ψu-KΙ%ΖCεDψ|vΕ2oƒ‚OΥ6ΆσJ° n”ΚΙ`Ό²ξΪ•nΆVH§c‘dlΤν―ϋΐ1Ψ6ηχεp\Žύfpœτ¦σ)Ω9xx~l ζ'ΞΙζx€Υ _·©ΗΦΛmgY§Km₯nωΦΛ9μαο₯ΦφΎ6γή 7߈xΡlŠq†“²₯ž4ΪΔ§όkΫϊUΈ’έuΆŒv¦>e;Iή—ρκlxγU~ΏάVdat[„ΖhΪ?ύΏ›…OGήΜ=•―ΐ£αΓΰ¦ηB€™oμq“oόM7ιέhΝWχx\ΚΊp<άΈ­WΉa€ŒiΨ}Ί0ξJν8™ŸΊ%¦)Α[ϋ-ίK@ςמ:vΣζΖκfλFgΪ·€Ειp ΨΕx‚§—ƒ'‚—€Υΐ:― ―Ε'Mφ΄΅«$–οη˜ϊ'ƒ‡ΌvEύup5xi~)¬χ‚cj<σ‰h󣈯Α^`zΫPφaόqM―ξΌΫΆ„²’ΦxΆγ xl'‚σΞ‹Νa;pžWIΡKW=RΖ‹m8Χz(ΊόΏ‡i`>_mλφσ‡αvH}PkI›š™/κ WοΛrμζ€-Η’ϋE΅{ œόYpιœ„ιΓb/γi;¦ƒγκbυπρ•΅‡’E—ršω΄b-όixβ4]oΛ‰½œKΪRΖsΡ? nΚ+ΰ(p3ΞΖ‰ΪiŸi{‘xΊJΚnκϊS~Βμ7ξ{ΐƒώ7pΌΌd36έkΑΧCΣΰ¦ν!`ΉρG]7εD·ŒKΰg x)Ϋ§Φ†ώ0~κ71Jtέ΅ΰPX;—΅―gCΪ±”YΦΙ­kΪWΦΏΤ§σrΎOΗ@Ώuw\ ΟxΔΥ&އσΜψr!ό?ψ|œ›^Ϊηΐ!ΰΞxΏ„=αN0Λ² ’Ν±Π¦”mKœΨf}ց3αƒΰEΖ:%΅.η0άuak8Vεw°Ψ~λc~eΪθ₯«?κiΪΝ―”¦o/#η$ϋΫ~Θ₯uHy)C·Y_γ*εήΡ²τ.—θwόrιζN!Y CC)Γ£ΗmDνx χpςz]ΗΪzͺύkΫί\Θež₯žδΪJ{τΨ]τnJΝΝ/ιKΧ9ζFύ™ΆΡΝψ%πLpsmΦ SGRn ₯ΏΤ7›δ}`܍ΰ-ΰΖύ-πur6VŸ^Ξ727ε“ΑםeϋΜ3u-έ¦έΎρ ω DŽGq³,σKXιΧ&ϋѧΟƒ›νJΦώΫΘθqνΜ†+qΚ³Ozε{άδΫτ—vΓΛλΑz{`nΠr<π+¦w½Ψ—Ο‡ÚπFπβζΫ!Eہp.\/„υΐ±}<O/’Φ§”΄%nΒ,ϋ6xΌ\³Ά+b[ŸΆ}KπVHΆqo˜ζ£=aM N˜ΊRΖ‰Ώθρ£Cu±~φ‘b½ΟϋuτŸ mΈ:ά—©œH}ΘβΜB³F±EO-³ΐβΧuSΪ °nŒηΐ,pњοp’²Fγ:g\π·ΐ‰`šΜ£2=ζ:ΜMΑ ςUp<ΈiΊρ=Ό°x—yΰν*ΖQβΆ| ΪFΓܐ¬ΟlπIΕ§½ΣΰR8<ΰλα‘_ΰۈΰ`]ν3λψ{0½‡Ί’ριε&Žeœ φ‘ΚVp˜g$νΠ †Ε^κΪ¬ƒO₯3α`i%mς yq;3Ϋ5’φ₯m₯½,·ΩΏ>5πà ζ±θŽ―s?ρQ;zlέ\Ϋξ8Ηθ}ΰ\΄ υ+Αy`ϊΰ#ΰ…ϊπ HΎΞ#±έΪ‚Ή#¦ρp#\¦‹8oπϋΓkΑΆi3Ό\wο”Σ,ΣϊΤ!…^iŒW†ιXίRτ;§oϋΠυ“ ΣsΠWλ[J·Ό΅•yw‹SζΡΧΗ¨²qQφύl—A”‹#z²Υ/wΑ'ΑΝΡMεkpxf£ο•–(I~1”ώ€Χυiφ₯πp!KΒQλΰΖη&ϊrp“3Ν£ανΰ¬ —G+FοŸΞeΣίΣα‡p=|†τƒ›Ύύ³x˜~.Σ‹¬Έ™ω΄“&ι­·Ν-mnΔo/ζgΫ₯NPGJ{tσvœ泬ăΘ|—₯€ΞqΛΌ»ΩΚπR7m}lΔφλ/α pΎ—}>D©Εq•䑃λΧΨή{Γγα,°LΕ΅υ8pNέ _‚Mΰn°£i›σΘόœkκΦΧ|] ΞΝΗΐ ΑzY§Ο€—ϋwAΪ†Ϊ)+嚟ϊϊ`Ύϊ•„—zlq눣όαΪΏL{9\ –7띺G)έ譐ΦΟΨl__–cd’,Η"ν‹Κd·#ΤKΩ9½μeuŸ*χƒυ ΎβΎn¦RζUκ ΣVΪKτΈζιΖτ98F;‡\άΖ5έΛ@Ώ›…u?ށνΐMY1\"Ργ¦>>xθoΟŸ’}RsσΞ¦nύ|š7ρώ Ά%›UYžqάԟoΗΑιΤ'uΞυiνK`>nψΫΐa`]z‰εFJ=Ά‰ζΪΏιγfέm_Ϊ·Ηωα‘Ώ8O"_Eρb`Έ‡–΅ϋΟrœ#ιυ;Δyυs8v„“ΰRΘόtLG>Ÿ Ξ·MΑΉhΪ΄)εΕ%¨–ψWΖw(0OϋΜόί /Ϋ―?’Όγ·ΎΪžΧΐ`|νÛ馛°¦[ΖQŸ—€ρΌ°|,Η±°ξhϋqjI~zΟ₯M{Σ―­/Λ‘Κ ²ŠλΡξ'|9ι›ώfG5ΓγΧuQ½ KύΧAΫ? γ›…‡©–2}S7Ml₯^ΪΜΔΕοf ¦Δmω.ψ”οΖζΑψπp5Ύ67¬Χƒ›‡Άρ“¦©ΫΖiΰ“ˆ―"Ÿ?€sαSΰ%Β'nΕΝΨ|χ…}ΰd0λmœlζ) Ӑ²mΏXΗΔI}†σ½ώœΨ§·‚yXξσΐϊ§/Q;zϊt£'žξD΄ο *ϋ~°―mΪSΗ¦€_Υ=T<|}γ["/OΎ‚ώ<˜_·©λοζZΚ0 ezΗΫ:;n«ΐεπf8ώNA±Ž{‚uϊ%|<ˆ=gƒλΔ|’Κ5νz(Οης}ΰy 8wΦηz§οΗ₯β™Ο%±lIόR­ΜΓtMΏΆˆaΦg8Ώ/ϋGϋt°νΆ!y vΚ.mΪ#±Η½οŽq8ϊ²βφ€ "XΛRΧοFα’;\βθbΌ SΚ4₯ή ]ψ³ λ¦7mέ6€…Ή-Τ/ŸO=gΑaΰf‘°™θ7M%vu7e1½r<ψtύEx,¬ ŠqάνŸœ|‚+x`δΠ0NςŽ‹©cSWRfΛ·θOΣΪ/‘δ₯ύ t?ŽΡdΨ¬G7I^ΊI_ΖKxi[ΡτΤέΎΫ^CΖ-mˆKPGJ›Ίcκ!σ΄v /~nλιέnz;Ϊ"Nβ–₯­©ΫΧ’sΦύlx%lΫv/ΒMη›σξΛp'xi0­αζcDχ`wΎ»?Γ,ηΉπiπ²lš²_πvόM»σμ»ΰεΔΌΚpυψ£7]’t•€3Π|“?ƒu_ΎέΦ½PΏ Ό<)eΊ–eαOσ‰ /qϊξτ€Ψ—ρι,€LώΈ©MόqcλΨΉ9ψΪЍΗό܌„Λ‘yΘ”ω˜VΏDGνψ›Ίy%}ΣM\έ^’Ά–εž3α¦vp}Jψδe|7•[Α SϋΧαx#ψ$―X'7€χΒN°œ>a™GΚEν)K'ιšisLΞ7N7w_7ϋ½Ω―₯?:ΙΖT<¬šσfI ΄ΎςEπ@»ΎνΗY,y±§Γώ'ξχ η²—>Η΄XbΊ%ιSΣ‰ύδΕυ³°/Έ? »δkΓ«αKπDX<€Λƒς8œη¦}*| μ7ηΜHbš’Δ/mΡ ΛΌΣΦMb/Σ/φ€ρΏœ/³Ϊ¨οŽWΖ&ιβ΄H^ΦIi¦iYϋ?Η¬ρcV@?γž=…h„rq” ΄—$67ŸHN‚l?AΏ ΜΫΕT¦-u‚:aκΖO}Œέ°”c~±'Rκ±Ε5M75mσΐ'Ί‹α Έ σ2σ!π°ΧΏψTσk8|RΚa`ϊ/Α^πhx ΜŸJ܌ς$„ΊΜΔΊ+₯½²π§v/)Η‚ύθΑvπUΆ}©”ύ½ι–ρ¦Νa;§GΚΊE7ίζ{©k+ν†9Ζ›Α Ψλwψ6Θω’|ΛΎΖ<*1­}gΪ²όf=Μ,qΤΧ0q˜§OχώΟφΑυΰœV|p Ÿ‡m!ςP”?ƒγZΞ_Γ-£—€ώ©SβΕή͟°.ža™κ¦Σ#ΜnλΞoߊΨΦ‚σΗz{ΩυθϊTwlj-ζW tϊ·eνσXάE>ζϊ. žδ΄/nš:08Ήœ<΅œ4ΉΈž˜λ˜ƒ,ŠI+―QM7§šΌ`~5ώΌjώΌΉΈsλ|ή@,—‡Μ©mύ\7£ˆ Υ ₯\ΐ Σu‘Z§Έ₯ξbή6†πw0Ÿα6$‚»†›oΉ±ι·žζη“’β[ᐺμŠξ†¬qΎ§ΓΑΌ|•j˜q”²ξϊ“O©7m†5Σ%Ύb=³‰Χ†Εψa=Χ€—ΓοΑρz1|ΚrΣ§Ϊ”Τ³΄GΧέ ~ί‡'ΑHςύ&™x‰t£.Ηj€ΌSηΈΖW?ξpvΓγz@μ[ΐF 8ζwΓ^zΗΔz?υ/uΜΡ^Κax|>΁μƒΖ+γ–ώΨ-ΛUΊJ‘.εgŽ8§½τ½N‚ύαι°lιΫΤ7ΨΆ„KΑƒω’˜_YnmζGβ%u*Χ«6ύύe<νϊΛvkKΎeZms««Ω£vc_ϊ'ώ3ృŒΜ*kW'l²}u{ΦτΑjβM›ϋ`΅ΞχVλΜ›WΝY° ϊΫmΧ°Έ2Φ6WΨ¬»y5εΎ»«9χΊΪϋ2ζ=σ‚ώΝ (Pt]PνgA N]©šώ©­›GΪSkLUΣ(qη%Ε΄Ώ„½Ay6|lK³N˜†e;Χ|½yX—ΧFΌc.eΫSXlΊM=Ά¦=iKΧ~ςΒωx'ΜmO€CΖΎ[Ÿ<’XϋmΈμΏΤ΅£—Άr,}λτY8Ξ‡„‘."ΦΡu³-ψΖam8ΜΓ0Σ:Χ] Ζ»ΎΆ[ݏ²Œ—υ€:D¬£αΊMΡήK† +Στκƒφ―©+WS˜]―α'­Ίv΅ΙzΣ«™<Θ2m₯jOσƒƒΥΌω<ΐ˜a] ΉΥΕ3Έ€‡˜ωΥΰ|}.wOšV}ŠΌ>ΕΥίΩ§¬½I5pΫuΥΌ9χΧISίΈeϋϊRτ@·‰³Ωυ“Ά{ \8Ρ|Κgπτ^o:m[m²ͺΥSV©6yr΅ί‚jWfψn,†zρ€_ ·θj`AυΰΐόκτΏ±`Ξ»φ―Υά¦ηΝ€ς18ŸόΏΟEΒΓΪ 3‹₯ι΄ˆτš±»ˆΛ<Λ ’i‹^†ΉΩyΣ·U›Βtx)<[βA©\Ύ^τΘωt7FŸ4š’r¬―‡‘—%ΛΣP;zΣ–0]ΕpϋΡ|ξ„/ΒQ~@νlΊι#έ`x†ΨμΏgΑˆόž Ζ³O,S)λf%ΆΈΪΜΧώ²LΏ,ΔφAβρš³Ί†=ςδωsͺο^z^ύ0T­7£όηUqM½ϋqFΩΰQFοGedΑ}€E3°ΞfΥ΄[¨?/«ΆέΏzϊδIΥ™ϋ{qθo}?‹Bζ²ΔΧd»[‹cr²‹Ν gΑμwM«&έΖq€}¬Δ‚š{_5ήΩ՜ϋgW?ΌωŠϊάάΆΪΊΥjχήQΝᡚλ-‹=n―κ^ΞυΨF“Ά™oΈiΉΡΞ7ςƒΰιp(l ΫEηΰt_͞·ΒGΰՐρθo„λΑΌSjνχ©ιπ]Έ ”Δ‘ΧjΡ[©{‘–qΈQw»x€ΟJ7Ίiύ±y€]nψφ‹ol‹}²γ¦ΎqΛΊ–:IΗ]Ύ²­κφ—>ΕΆΫf%ρ£§ΝΖ7ήώp2l¦9Ξ‚HΩώ€MX7·,Οpύ±•nΣή-ΜyΌά9Q;γ§n:γ=Άƒΐyμ…0ηΣ£αn°ΒSΰXσΜηΗ9π5pώ+kƒ‡pζ†6%υmω–ΝOσ¬σ]eΝjκ½wV·m²c΅σTΗ20‡qp―~7Κ·WσχΏ‘Ψ|v5πϋψ&/£Ύ&΅3a9XΜ°ίI o₯oZ™oVΝ?wΓjώEkW“Χd5L™Wέ1w ϊί9V―ΏμΌκΊ­χ©¦ύγόϊΑiΩ΄ͺŸKέυΐφϋb™χ@gžσωώΐjkW“ξΊ₯šΏΓΥ³&ΥΫͺ”8Ι…αdψM|8Θρ2ƒν`eΊ¬šT Ώq½ΜβεαΩ".ΰyβ―7‚ηΞ©ŽϋyΥgxυΖ{΅jΎŸ©Υ‘C7§Άi‰δ™ š~νΪ2·<ܞο‡Ν {β\…νΓπypSS’φ^τχΐ›ΐ0IΥ™πJΈJ±_―Ÿ ο‚ww¨!ϋe¦ΎΡK?Α΅Δ¦§Τ[‘­Ÿ©£>υΏ.#QKΒΌ}^Ϋ2W?Ε} ¬ i{κeΗU‰[†₯^q[1—ΟτCΪh nΑΓαυπ:πiΦ1T’εkύ΄ΫΒΣΐ4Ζ½v€©`~έϊσ"γΤ-ΨΊΉ±₯ξϊc‹k9₯?RŽƒνΨv‚Ν᭐vύ±ΐ±ΩsΤ:|kά#ΐΛΐξPŠyΊΌœ/Φ₯,·Wέ/.ΙzJ3ύSW©?ͺœ»έΥλψφ Μ#dΟ[x­υ^opθ+£) sαΟ€αa¨Ί•+Άγ {œdέΈκ-²zΟϋWƒWžΣY 3θkKάφ}_–} ¬΅q5xΗυ­EΊΓΑΥ‘μ*ž3©Ϊ˜[νƒΫάUMΪνŸΥ€o¨ͺνY_ŸyΐΧΒ¬/¦½κ ί¬„³ΏΊ‚Ϋσ™ρ?Γ¬W-ψϋš|!‡MrΪΌκ.nΝoΌϊβκ;χέ^έΛΫ‡)Ό²sσθ&uΦ”Ε%^Ββ/έ2,Ί΅wSr“Zφ†ΗΓσwσvW…sΰ³π+0νκΰυ'u5›δΰιΑiYΪ ―ΆžŽhηΉ£ώφυ,ά›:! 7IΣ§ΎΓιζ•zΩtU«θ6υΨJ»QXΆΓξ0n l£uΪnuJ}SŸψΛΈ$οHΒ;†₯TRχ^Ω$ΌtΥŏΊœ^βώ ~„‰‹Ϊη€—8ΏΒ Ό ΎΆ_Ύ¦KPW)ˊ^ΊM½τ›‘υ²¬Ψ΅5%uΡnwƒmΐ7<^|2χΏƒώ p}8W“§εΈ~ΜΛurχ‘°(φkBω>|.€k€£³σ·RŸδOΠbI'dΎ£4{³έͺ]ΧX»ϊόάΙΥNμAξ}s5υ™¬ΎΝhΕ]Τά}ι$\¬βZιωΎ@~-zπJΎbωυ-˜0VΰΖΗ₯sžvΙ―ͺKvšYMΎθŒeφ±ΧbΦτ_+ϊ’ŽΧΏV/,γΦΜΨ£š|ΥͺΉ[ο_m4uJυC̞|ΞUνr[5ΘΛ«Α-οζ"G»‚―ύ]=‹3υκ&Α$WΙοfK½j5v„-ͺωΏΩ \ƒm‚μ/ΉλΦκ™Χώ₯Ίάί"ΈοΞζ2Rkλμ‘šΆψ­ΆΊ~jŸd‡G›ŸRnZgγχiΰ¦eλΉΖΤι“'ήNw˜dπuώ‘ΰ·Τsp^„ώ"Π57^γΫ­ΦI›’|Kw4z+uλgβ—Άθ–©”zΣoœΝαΕ°)<¬σ·ΐ>ίόΆ¬€ΪΡcK]šq—…ΨwφΉ‡χh€W[cΧυ@σb¨›~A­υ΄ƒ)[ͺιΈο§Ά—8ΐΑxΦMqŒ#Iάf9M{κ§½›ή+}ςΡMΩΊΖχί<°Ž΅ν²??Η@Yή!✡Ÿ^ Ÿ/ Ξ™Γΰ±°xi΄œτΕ,τίΒΙp°ΤkΚr_JΩ––eαΟ^a«[M™}kuΫ6ϋU/]iJuς|NΏρμjώ+.ͺ·cEZ€oze°°ˆΕΣ|ΰήf§ύUρ‰ͺΧ¬Z ¬Μo Μ¬ŽΉθ—Υ'·?°šτ·³κ~XΌΜϋ±‡τΐ²»!™;{Έ₯>]αλπ _†™ϊ’Ώ±“έΜιΕη }ΛtΡΈϋΈhVbΡό…OY0՝S[o^zΩΉυ―Ίω6Ε€½€VϊΥέ„ξκυSμ{qgB673uο―Α‰@tόΔΓ΄ˆd^–‰ΰt°|Ή ^Ώƒ^¬wόΙΓtΡ[ιͺχ’ΤΣpΫ­h‹]›ύεFξ“›―·ί ŽΙ-p\)ΏtΛΊE'j'ϋ°’r[Ύ%9…€λƒυι%iW\γ5Ϋ«-uŠ«M)Σ΅,­ώρ{<lλWΰ9ΰ‘§ίώ²~^6ΝSΫp’ršiRί2¬Τ'ωΦ­κ²ΦΦώθ1ρΖΜυ‚±2­ϊ–όΚΜ–Υ\¦ψP΅ϊ ³Λ‘Υΐ…§w“1«ΟΏZΖΝWkίrmώ‡TS™˜ξ0³:iΚ‚κ |9ζΑΓiώ~žυ K΄ώΰŒjεbρ#oΞ_Ψ–oΣM―ΰΫ·ΣζΞ―>ΙYΥ+WY«Z/ϊτ”M@·)₯­Τ+n2θfϋΑL8 ΆΕ ΗβgΓΟαπUππvs#3m™/ήE$σ²tέΘ¬ϋξΰ%ΐΡπkΰUpXΏRΚrԍoύ oκ 7?λ[Jh3?Ρ¦«ΔΦΝο¦Ό*Ψ–ί(―†S!υH₯?ΊρΥΝίό|ͺό;Έι/­ΨΏο‚·€Œ—“΄΅Σ_±us΅Εžρ(m 3?%νΪ ύaπωΆΝΓ~+ΈηΠ9°+Ψ‡†›—ι{‰ε§―Œ“²GͺW3]·όSnςχΠίŽΗ5oͺŽCXηζ\Β΄ˆΨNί|Ό\+©wΦΧϊΨ/»€}§˜ΏsΛ²Ώ?ηΕ°ζόOέ“/¦…}ΙοπOγχποδπψ”Ακω|l9ηeWSΉ‘ΕO~[y εW«Sζ™αο-οDΤνψθEΏͺ^Ν^;ε’3κΉ±<«τ/SV9ώe5 Ωαjπ’_VσwœYΖνψqχpΧ?φμhd΅ΡΛYqΛ«nό:Mu1Kών{qR²mποχ|~φΘi«Uk=pO½Q”U*u«Xϊ#n>Μ7'άι *Ο5Ψή§ΑM`ši ¨{h—yk/₯9υ—67f7Θ-ΰOΰV΄ΈΑ½~ ΖιΆE₯\έRΗ[ϋ›ΆΨΝ/wβVJYΗθ¦Sτ—˜‡mπIΧΛ‘¦λ`'°―<„•”₯›φ¨Ηξ˜πaRν9ξ ¬+ήΕσ^ց+Αz–’Άi‹^ΊΡ›mOό„7σ΄ά‡Β ΰYνΐΕυ0]μ_~\ζ•Ύ@­₯™όqνkuσ‹M·Τρvόκέ€YξL"­O‡ΓΑρ²ކ―εf Q‡ˆy₯όΈVœΧJ3LΏyι:_v€ƒΐΎΪ””oή7ΐιpάφ§iνƒfΩΣψ²Ώ^|ϋT'σ›J/γWφͺχύ†W|Μ4βρί:\IνίΊw΅€)_€~ςβ3ͺWμtP5pΡ™‹Μ…ρ¬κ„){œ‡tΒτΣ°εɐ'oρΙސC5° Η₯μL]ήb‘~©Ζ_ΡyσΓ«ΈσηUΊψΜκώHΗd~C@ιV3mVΫ§ΐmpσύ/0ls-·σΣ ω$8Όfϊδu(ΊiŸ nNΩ|Pk1Ο¦ΔΦΝ5ΐΏΐ†`žo7\Σ$]³ρ—n©“΄σ{>όnƒ^ωTKΒK·©λφΫε°)ψ4λS£΅‡…υ ¨uϋtK›Ίyy)Sr`h_ρΰ²½ˆtΛ«l“ε”ώθζ‘菭ιΦΪαΆΩΜΛ㚠Έœk©‡σΖΊι:ζMIΪ£—yλ·ο?κ Gν©I],ίΊμ λΑ³α ΠnžΞo€}‘4q1(e½Œάτ›―Ά”η:Ψ\cۚΎ²­ΚΉπψά¦Ο›ΤZξέvίκ•ό-’“œ{ΥdΏαΏ4_πKΖΛΒυaκr.οܝίr’Z|'ΰε9ŸΓΣ.ΤE6Γ‚ωG7u^fΑ§ΑΛ‹e§—f>e]‡Σν7j/,^΄n…MΐΓΔM=e©GΚ:&Žc¦=}šz$ΝXΊioΪιD’'Žvυfύ<ΰ}ϊ&ΌŒγœz (Νv•}ЊΡϊ™|S—Έ†ZΓοΑnΰαΈ6DΚΈΡ“_ι?ε;ίgΒͺπ πγ Ϋb9Φύ»°_‰Ϋςώ§υI’*uΤ―.Ξ±>^ ‡ΑΐΛσ:,cr=ώŸΓΐ>™kπ‡ΔͺMvͺvXc­κ Ύπ7ο5©&?βf€άΛ‰;2ή8—½‘\ύΑj„h»ΉT³ώvNηνΩΈΦo"Ύ"λDκ·!uευxΚŸρΟΐoͺΑMσL6$ΦψzψR`υ²ύ«Sω«\>P½ŠΏp _ξ™ΪώƒAn n>υΊ‘½φ7Ε0γxPύ7|ΨκΧ9dX· .6]*ΦΡJ97£—Ίυ}.όxPxθ[Ζϋΰd°\λή”ΤK{tέR·M«Ή,/αΪ"ΪR―ΨτΗΦKgDκΏZψ\7hγyžφ{Κmκ½όD­%αρ/ 7ν1―¦ή­ΪμCE=u*uÌs$<φεyπΘ%.iγ§›˜w$zά pό.ΛMκ])ΖuYΎΊΞι·ΒΦ`˜€CΰO`ήΝΉ7RύI2DΚϊ% i+ύκ)Γ:zΡq=?ž;αIγšρβωρ•V―>έΥμͺ.˜ΗϊΨYΌς»Œ/$&‘V±|Ηͺϊξό~θ6,ΉΥu|°i7„\`}YŠΨξΰκ!tβoψώ9όzΜ”ooeΆ’­?–ΨρφjΰŒ«¦LͺΏšΥwοΌ‘ΊŠΪΊžΆ‚§Β·αU° 87lΖέπ8 ώ|%ξ!k:eΈ¦²‰Φ‰FψQζΩΤγΧ5Oλ²\ Ϋ‚›ξΰ“Ψ―Αpγ%j§NέτΨtΫjϊδSΗ=a₯«.YoκζιAοEκq ψΔθx°(e©C+daX3^ό)sYΉΝrΝ7ύΪ­ γ― Ο/9ŽΖS_›OΣαy xΨήH3]μq“ίp?οζ:γiϊ€)υΨtuΑ•νψω–k&8ηŽƒ ψΦμp­Ψ/ŠιKiϊΛ°θΖ ±•n3ŸtΊΦΑΊ_ΏƒΣშΎΐΣΞ{έRM~.Km,~/vΙj84•‹fΣϋxδœ\Mζ‹4 ¦L«φΊνΪjuΎ ΰ&φvpσς5w6«Ρ=τ‡ΐoΑp7 ΥrƒΖ»Θf§­γ›χh(Σ?=yΈΙέ_ƒυ^ΰ›Šύΐ'λ3!ObI— !υ‰?u΅±™F»e*έςθf―#·γ·―‡υε$q½ψμαΏ%όΌt₯¨΅X^SJ[·ϊΔ6VυIή₯Νϊ Ÿ K ΤZœO^ڎ‚ΝΑύθγΰX¦˜—«Η#aq΅•φθ–ο\ˆ?n4DlΓΎπ3π ½ΧΜΖ ά‡OώΣEΜ»””ΥΛ-γ6υf^ oΪυ[±ΆΩ'}/b?…sΰj0|: ςΓ*ώΔοC¦π?ΎώΒjp*ο&Ί‚Šςr²1Χΐ―7¬ίών²ΑŒκ«7_UΏ­[Ak½βU«X‚1Ωvf50εκ~^―1 ?μ‘[œ/Ν•ΈُIλe=wgψΙζόgCΣͺfίQΚΗ9ƒ ŸŽ\ξrΌάΰάΈoη‰OΤJΆ…Έ-λΨύ,»4zιZ7 7χƒm9”έ`38ž¦˜Oς2,zιFο›q‚6₯LgύV;αχΰζkΈ}θfxhcZΥc`{ΗΙ+.A΅4ύ»ΩZ±GχΣ2»ε‘-φξžœ8Ίϊ=4½Dκfώ$>¦ϊ?ΘqŒ^>­:–€°i;κ)ΛH^M[7»™$^τ!χπp֟£{1ΫVΫ΄6XGΗς@π`›ΗRne4ΓJΏzκf{‡σαΧp\³ιŽΥφό†ΠJ{ίR Α5§Wηw…Ώ8ƒΥrњΥΰM«pρ¨ξ»εΚκ—|/kwe"4aάϋ2“bά+2‘*ΐ‘?ψώ%»ΥWξβΈyυ_Y0dΊω›Η\ΘϋΚΑjΑΖΫσdΰ28Xύ‘ώμ ‡‚‡”Ά‡-kΆι'Κr•”_Φm5psφβr\»6Ύ x|Φ‚ωΠMl§(ΡγΆ¬­ 4Ά¦›8έ\γζ5²ύ|%xΠE|kύλϋΚZ[Έa›^‰Ϋς΅όΖ7ο²>†7γjIμ›ΰ› ŸbΛ½‘Μ/z\’v-/Ṝ/bގOΠOΛf%Uοn»†EŒ[^†b›rJW½τ·τG/νκ₯€Χ`|l ΑΑ΅‘°ςλΈΘ·8ΞΏδ›΄˜Y;Ϊ–TΚ|›y4ΓτΗΦΤMkΏ^=mΥκ'λn^}u­u«ω> όΗ¬VCŒ0δ.κό‚Kω2+Š7±―έαIΜ•yρ™MΧ:–‹|\+2‘ ηχιην<³z f[ώ¬ΉώΏ+Vxρ½ΤΉΪΓηΞUψΒβ.Υσ_ Θ+ΐw`q³[œ,nΜΝM$~‚:Rn0γ)™³e=’[g―ΐ‹ΪΊ‡δcΰTπ#ΆŠžbή¦7ŽΓR&¦Ž-a₯M]1,’Ύρυλ#aψ6"uφpσbpXΆςv`”†ΒΪS¦uό.|ό\»)ešaMΏqkϋι[°>X―^’Όu»‘tΆ1σ(6]ν«ΑCa0ϋΔΆΨΦ%β=ν‹Τ§[y±9NκJlM·ΊπgΖ!ύ₯mcό^R¦Γ;ΐ‹Š€_ΒaPŽ“ooίφ4σΑ΄L$ω–uLΖeXΒ›6ύφο<ώSŸώŸΑυ6―v7©Zg>SίαžΦ·ώ“αŠξΊοnH―σWVπ?NΌ£zΖΕΏͺΨιΰΞVR–½tm‹D «mήόοΉ­Ί‡±τ1ώ€»σ1¦oύΓaMψμΏZ“}xΣ{Z/bρ?q’΅aΌκλ&—QφŸϋOzΏΐ:9˜ΧMΥώ7Ά™F™|…ŠΖz©|{αw&T»―ΆN΅Όά@ˍ€ΤΗ««Rπ ΰ+z>©μΤ5[VκHPgCΧf›/·€β+χΓ#ΐ§μ€υp:nΣ%oΤ%–2Ÿδ§›96 πΰψxΈμ 3ΐ5š8q1 +‰wΨΘΓφJί˞¬Κ6Η–4’Žε3Αvz°Ύ΄wλΣyΰšηhΕΈ^,|Kbϊn`^ΔξψOGΓσΑ|ΜογπrπΙίzšg€Μίφ'R†-=ε–n³\71mς7Aφΰί1Α„ΌPooŽ[άέZ+4dΫ—‘{ΐΝ₯/£μv«Av?vΪίΩΟ?“ωΐνALρν[?κ.ko\­>΅u¨ŸΪ]ά8ΖΣo½|Όz£nz‡«—αnβ¬ϋΓ•ΰ¨yΰ|ή‡ςTg^ŠbhϊcoΊeν₯{i³ίΗAΖΆ―™ivΏ”θG+Οηœϊ›Α7λ‚’²RGΗ§i+Γ _^”νμ¦·κΚbCΈΆΕυ‘”i[–ΦOν½ΒΚxΛCoΦ£μ«…“i Ϊb΅9Υά5ζtϊgyΤm™—αΒΰ;Mώ*ଳνΥΤI“ͺ9όΖV³–yΩ9Γώ`1F4lώͺjΛ»*Ύ8_ fU-F6+DTλνcρ6l•ηoΔW–οͺΎϋ–ϊ)ΩMz$1yλIbα&ΟΟB3|Cl;Τ1ZSπ τ=Αφu«§νLY¨‹HςohΌ’wςjΖΥο%d}π @›Πw‡Ÿ€γβάρ"±/–V¬SٟK›Ÿιοe=ΙsξάV_σ¬6i^58-WΖEbN ƒgΥ…;ΧΚόΗF+ΡB·ΈΎ ΣN辌’ψΣΏεΰnεοž?ΏF7Št+lVŒλΕ7σ§T ψ@>ννζίHά3<$άϊΤίc!nBo[‚ŒsνAZ™HβS“F¨°}όgΛYa~:^ΦΝώ_žc-Λz+±ώιߞuχΛLĚ4™˜ώ_!Yl,Ώ••Ϋζ4ώ―ΰ©6―/Γχ@0|tB󨏻ΆkeUΆ‡WX'υŠ©XΥX4΄g€Χ+ŠŒU·&ίΦΆ·πIΌΫSaΆΓ^{HςMŸω”˜όFŠ_ζ[–­=y˜_ι_œ<›qΝ'#ο7Z)λΩ-uοφτe_η©9γx#εΩ­œ‘l#εi}Κ:™ŸυJ_Ϋ'eΏŒ”Ÿι—DΖ"ί‘σ΄₯νο,I₯W΄46§-ρˌ©όςt³ψ—g™²¬¬&άΥόό|§ΆbMΈvY•Ψήψƒ@Υ*kU?»νΊκlž ω›iξHmz€ε9RόαΒӝώΊ½»Τ’‘qƒχ³Ϋ΄I»e<€`x)Ψ¨KΐΟνΥ»΅ΟΌxw9(P‡θϊSžϊp’z‡ίfΕΘΟώ?Qϋω)ΰλ|_Ÿ_ ׁiSNιF7Ÿ»ρπvΔΓwW°Œ‹ΑψI‹Zη_ΦOέώ°"ښqΧ|·€£!oΞBχιΪίκ0OΗΘzX'Η¬))#aρ'^Σo™ιOγ€]‰ηΗ[ƒ_ξΫŒoήΓ_Ϊ~œN:uλ–ο4θŸΠΒ_υKMw°ώWβχ‡ύΘ`Eo¨ƒ:›Ωγ £;?eΖi,ϋήΧ—Qτ_,©χυϋθ9']{"‡/J½hΪGά:UίΎφ’κ&Κi° zσ4ςπο πUψ;ψ½‚ΰ p$ψΩ°ΏέUJ†_[{vŒθ6ΣΗo^%ΪO‡ηΒn`ցoΏ/ŸΈ¨‹‡u₯½¬§φ¦xΨ 'eYΡ=(£›6‡²ΊφH–‡ϋŒ‡μΑ`_ϊυ ΰ6Έ†mΧCΉ7%Ο²Μ؈:€LύΙΟ8Ρuν ‡Βζp8R§‘ŸεΕ/ι1Χb˜Ά²όVȊσsΔΊΥxnk^sψϋgt'΄Ψΰ{ΘϋηWψ Φ—α{`a— οί>΄ώΌŒ^ΐ½έέΞΫζDŸ_u;؊uηΜ©ξY}½j'‚;oκlšaάG3 ‰7νς›ωSAϋGΐ=—ŸTΟƒ#ΪφαΆΘςPΏι4DRŸnΪχΒ·Α7‡/ƒΏ)ΰ%₯YNόq‰²HœΨt»IΚ6,ΊnIΒΊΩ λ&`΄Χ§Ϋ/ΰFΘajΗγ:πΖΜRΌCό©Ÿφ¦$,ωnΏ[Βρΰ—λ²5x9`ew.U¨2“WΣ5Ί&©cΟzρΖoΰnΎ’IΔ̞Rνv—½=ΕIu£Ι‘ο3Μ­ε˜ΐΝYnU·ίϊ2Šΰ € ΆŸYoJ—ϋωM+σϋ¦£H·ΒFa‹tχ½žEγ6ΰbsΕZUSψ2ΰDjί9蚺miΪrxϊΪُ Χζaΰ%ΰΉΰΠjσ›ψ>‰{pεΐkζ§ί0_aΛβˆi•nyZŸοΐ,π’βΑXF‘vτ{lM· OXlΊM1N(ΓJ[τ΅_|Ίήφ„,™O£ΫΏω¨ΗxnΨΎŽ#Ψφ‘$ε/Ί’kώρk³†αpL=μΫƒu3N―ΌVΖ›pz>ζγΐΌ€Ώi2ιVΏ3?Ε½μΊUκΓί/g_9›²\«žΉ\ ¨…ΡYƒ|nv8£ΊbjΠέc’λν₯lƒνo_?mUvα{;§Q&:JΪQϊ΅λO˜ΥJπ5˜ ^΄ωk`€F£vΔ5δgΝ†·{ΡHλΚ2#)_t]ΗΥΰMΰiΩ/§έpRζ]Ζ+ν₯nύ±υrΛΌ’ΫΞαΪjm›ΒCΑ>½N‡²u?ƒώ2όΊυ3ζ’|XŽ©ab €γ€™^Ϋ‹»άΦΥ>OΤΞόPΧώ/# ΐ‚κξg­έjŸΏα4ΡāρΥΝU«W|ωŸ?0PΏΉ›hΝ—ϊ·xΗ₯B+r‘¬ωό©©όΪΜ₯·±]έΙV<ΧKέΕΎΕΈ νŸό2±ΡuμχaZœwEͺ²nξA{τέφ{€Ÿ ϋΒ½ lΏ‡-ΐ½¦vuΣΝ„-Α|G##Ε3άΓώ˜UŸ’=Δ,s€τD©₯―ιO<έ„Ε-ΓΊιkύφΞΞͺJχλή[C’„$„ 2fQ„„8α*νl‹Σszm«­?μ§ΆΨςZΫΦΧΨ€Ϊ έ`~(BgŠ 2e$̐„Μ!©Tέ{ί;U«¬ω»l™|²ωS΅έFYΚ«ŠIσΟ3ΗJ^’Υσ+ΖΠΊtΤτu2Νr]VG₯Ώ‹ς«χOΊχ°IΆyΠ°€‡ζ__rfΏ'»_žbΪUoτnπ*πb‚­Δ08W=FΥΕρžΊz’ŸJΛœάεTQ|χΛΥυUМψ‹ΰ QžοΊΖσΗΫiI—Ci8:@Λ…RδRΤ?7J^OSˆ3υώηŸ“ύ:ώΞ ρ§ΛJ”}DωHΑοž§\η^~ œΞ_zV*ϋUΰ\€Ϊh}›#ΑmLŸλ~6*Φ΄ ϋ³`ξΎYŽQMuͺNτ@˜’΅§'ΟφΩU·Ϊ¦ ’ίcΥέKoX•6€“ ¬^άΈ0|ΆB;gύ~!Ώ»ͺi΄»Ž°’ζωΧ=WYρ‘›ZΟͺΌ§ύ:έhλ2?v©+Ε"επ(P―ϋ ε‘yψ[ΑB ₯§t$rE©+9…•JZ‘ɟ>φk•ŽΒ5ύπP“|PΪ}ψP^Rd/ί4"Όά?}œφϋωRWeW­›1V~Za. ‘QυO@ρΪ“4ω=žŸΧ΄ΒGZέΡ‘žŸ†ω_ή> œ•χB ώJΛ’kxϋΆδsΦ4η ¦5³φ!ό²9†y.ξ^T‹hΚ’ž_ލ’ΆLΝΩΕ-e―¦Ϋ(έatƒzΜύ‰ίέζ§[ώ1ϊ2ΥΈιΔσ¨·Η†ρ1@ΆΛ€έΠ ͺ5Šoܟvu_~,E³Φ) ϋx+ΰaxΫ₯α’φ+¬τΨ―Sšκύ―R¬Κϋ·@F‰δR σνΕo/LeVwƒΞŠβ΅'ώ N^ €ά=LΧ« ΊO1qγ@λ$k)tο>Rz/ž‡Βe ύoπ  ΌΌ-“Rυό/2¨tώ+ΰs@v»Κ€τrŽ“φ+VGΗ₯αižΟEם΄`Q"d0© I:ν΄Ώ£λϋίTε!εΏ|ΌHΤΫΧhΝε@Όι9Ίψ3φγώλf“W#³wΆFζ«b1 F^Wb^nyό;x°?Σ\Σ2UΫfηο\ KH˜³ΠjV-΅% •έ»nˆΥάF³£Ε€ZZΙ’ΦNεΌstRi²ΌΝπCeΧΞZΨ:΄\ΙΕ?”esEΰς’ߏεJq¨'~ΈH™¨§ρm ž₯¦ΤΣܟ”ώBtμHΗσλ”―όRnΚO‡ΛΫπ0ώ”τh=Μγ΅ηz:~OŠ£0ΏΦεΊ€Ο₯Γ<ά]?'We&‚)@CυkΑM@όΈ Έip˜H{iϊΉ΄«ϋpθ9Lg‚ ΑλAœ ιΌžζAΗύVζ°`ŽυMO@ΐχvΦXνL³Β`~νŒ T¬4ρ+Ρεκc­°;ΗΪŌύ'#«+ΆΐZ0q ¦Ÿe΅|rς8š»•l€a?ZΑX§š‘ =l*‹}r^³…O%zZeΙCΌΩPΑΕξΝ’₯λƒϋε–ϊ₯Τ~>τΤ₯\~ήθ?uJΉt€€UŒn0P“eέ&[εωc->ΉŒ&Hγι•**šΚχο3°μ©@LάΌύE»Ή±¦ƒgζOSn)4¬όIπ€”ΏΞΏ\ :+jWΫC:ΎΞ»(@yxΈς—"Υθ€DαΊΞΟ»«s?v·9΄ν_σσξϊιcχΛuΏΪe 3ΐI†θ―ΑΥtΖyΰ> Εμρά%θ€Rϊ4Ϊ0 œ>€ό%2Π¦ƒU@F“βΉ€ύΦοέUΛ­1ΏΧx;Π>’φμ¦4’‡VliΥ?Iωې&Λς…ΦΧΞ<ΣκBωwύg¬Κ 6¬7Ϋπ/:c· L?½a`2U;s Κ•fΠ[Δn$έγQ0R’•½ΛΗ±·μŠVΛήσΦίc;7q!ϋ0~|ξwW«g-#ΰfπx3ΠΒ2υΜ5χ}-2L‹Ηw7}Τ―kJ―Σ±Œ υh?$ΐ€_b»Χ«ωn/‚“pΟΗ―I»ςΛ°βΦύJJ―χ0w•Νr²―¦'ΔΑΗΐ: Έƒ5ΰ ½ΓVρ2΄΄x\ΙUΊήσ?—ΐT€κ²m@ΖZ ρ²ΛοiΘRΒΐΖ'Ψl½=9rŠm¨7ά3ΪφΞ}ΙrΓ0§ΤλξθΑ”$sΘύαiΏ‚oždMuΛ™ω ½Ε#§Zaει₯TΧbΗΥΆr‰ν`Κ<†€ώόXkbAJ’lύΗZ is§‡0f=Λ XχΩΞψœ΅+μ…™g—½NW=ν•!ύψά_κͺ—)…|9Π"³@CΟow΄—Ά™~μ.—΅Qͺ OŸΣy‰‡ΙΥZƒΛ€ςSψ‡@aΥg!}½Β]<܏ӟ“λ~)Y-΄σ4=άγ₯εη€)@qž·Άψq~4€™άš‡Β%ŠŸN/ lωSΚ½Œ‡WƒW‹ !Ι0¬Κ?”?$tEX”e]ΠχθU_ΞWλΎrͺ7bκVΒA2BΆ°φk'[Ε_SΘΪ5*«ξοαώ»ˆΉ+wŸkUIB‚Y, α΅ΐϋi‘ήΓ€U˜:HΛ*ͺ0Β{|KΥO³Βa–e4ΰοΦάbK§ŸΝŸεe/bYω9@ζιΗηώ΄λ~)βί€ΐλΣ‰ψ—~ ‰’–bσλρ&’0aή5<ΝdΎ]ΈΑ›€eLc€$v©?n©Ώ9v³²ώ2jΖυ%ž–»¦ό5? V "^4B"CE’8Upž<οtZΊ.-Ξ›\]§r¨‡ p $+Αt £EŠίγαmγΧqH °ΈΉ0sΥα~”ΆβΌ)”ωμ+-Ώδ0ͺΡΔή=Hεύπα }ΝKΆ,Ζ>±k)γ;)+&AHwˆ)€ξ2Χoγ:+ς#”Υόΰˆ) KεmαGZΓπ«9nγœ0\Ž5΄‚wΝΰ Δ^z<Κ‹™©‰ο­Yj_1ί2ߍb'}ΊΙsΏ»JBΚFJν1°|¨Χ¬^ιωΰwΰ%Vzv(JΫ‘Ύ(§zΐ4‡Ιkvκu« RΆλΑpPέN+A“tεΊ€Σt:οΈπ&π8!Pzιkq^ŠYsρ.Ζ³ t₯I—Ωύr•—Œ υό_Ύθ›&eΊw>Ψ άPΑ›ˆ§αΗα€Ϊ΄„ΓšSμΊϊ=6βΟΈsŒν‘-«9u3mΏ‚τƒ?@rέ>­§vLοϊμ¬ήιΆ‡uL΅LΉώˆφφ³Ξ²šΥ·$ΏΛnηΡί#κ9†τ³Yfε$Ζΐ₯μwΡ6 zοc–9?β2°¬7§Π?Δ(QΎjΝr»pΦ9–[΅dŸF²θΣI€Ÿ ϋΣϋ₯耘ΧυLΤ†Ρd&Κι \‰_Ϋ|΄οίφΞ—†)έΑίT0<΄N@"Γ@ββIάm>ϊΛ_ΟGCχ€ψ4Ε­βη<@ Cω|°%pξBΐδS›ό:Κ[ΡηάοΚpPΟλΐεF…%§ϋ?:Ζtτ#8”$+}6[³Kζ2§3΄΅β|iΥ2»ΔΫ[•)€ϋ ˆγb`ζB«gg­† νBΦ\Α;΅6g³?ΆΖ2“˜₯έAŠqp@ Πέβhxξ0šΙηQ=WL³βŽ΄β†ύ3vσw™ί«gΨLC€!]g ]WܟvεΤ#—R–> H€˜Ϋ€>PSΪnz:œj#ν…{˜ž£zησ€”τgΑΏεο’|PΈϋ5ξzΊrέοJ?–φ+ŽuέιΰZ Γ§žRά₯χKΠ~ΓΧ»4βœ>4’©—kΐ;ξ•Τ*§5 <έc`Φ|Œ€[0Ψ+ybΏdq"½ς¦·=a5―ΑΤΗ―¦­yα3¬ϋ§«ΉιiΚRŸ&VεyЧ»tΌ=Ες,φ«aΥ³œ+νλ]Ϊ·„v,mμu5»ΈΎ…ξ>― °˜Θ°*΅8}‘ΝΞεm iΖ°ŠΆιΜη­ζύkG…qνΖΧ“ΣJO-π@*Ο―&±4ύ(VΔ°,=]TšE”η7‚»Ζ@ΊΎΈ?νΊ_=b΅cš—ž!τw€ί),Ώo{Ην…©\”žŒυ€­pe˜΄«»‹·_Η.ιςθη$QXVκͺ—1ψ°N · Σk-€Κ’όΫ“tΈϋK]ΕΣHΒΑΗ€ •ο»@Σ *ŸΗΑΫƯ㐃d€‚2μRœ>ίFζ²φU’ϋ {4Ψc™xK@{Lfk'O‚φ΅“³ΏΞŽ˜Ύ»RόC8XπΡS¨8#,¦$―ό©©Ό¬˜·Xs«mΓΙ0 h!=ΐ€*OΘ!``φ|«_y‹5πƒ]LEx“沆ΣoΉπQ3½*θChκ΅wηΧ¬§­0%Ϊάg-ΝώOŽc΅?ύΛz>υIΊ+‡ΫkκΘΧZγΓ|ψ£ωκψ{ €λŒϋΣϋiΚ’wτοΖ=Έβz ώ›zν.§³ΗΊNJP=α;Α\ ωpπQ=sξΚίΕΓάMη/ŠΣή±‡ΛΨ9H!OW ςfpHη©pΟ―#ΏŸwWχ8(½”—x»| DΟzKψΘNŽiΔ<=πcω"κ/iζ ΔλΥώhjΰ-O6·m΄?F―=ιΝλaωΓτrκΗ€©6Q›¨aLΨκΙφδΖΞͺ6“‘kvΣ†­’νΌ€iˆυL]Φ0uι†­'ξA2 grˆΐΘaδXHeω2•eΑ6š¬I;¬ρΤMV3η%ΛΜάΚRqjƒΊsκ"&#jφJD―α°uoβiŒuOnυp³‡FXρΟ£¬ι‘Γ­v(‰Οέ\ϊ¬πΏώ„“,sŸχ©%)Ηa7HΧχ§]ωΥξιQΡ7JεI‰Ι/Γΰ“@Γυ¨ρ&J6νΚ/ρtΫσ+sΑΥ@γ: ΅Η°“Θuψ5ckϟ>§8~¬^Ύzζ_Jϋa0hTΒσΒ›ˆŽ]ά_κκΌΚ=œ€ψΟ2vφEπm ΡιOǏΓ= ΜΖX‰ €iΧNεGp!mΞXη4qm~;f»ν=ςeˎh°Œ€A $?)ϋέ¬ΪRgΕΝυV|n°Φa™ ¦€{ΕC‘h?ΕΨΈKωΜX€α»•ŠŠ―Δ=žp$Ψ–Ygπ*mΝ‘‚|Ÿϋ<~δE†θσΌ—Ÿ³›‘΄–™ΚR.Y£Qδήr+Ω›¨\²ŽΚVjΌ’σ,‡Κ$δH3ƒ₯ό(ϊΣ|££mξqtHΧχ§]χ+kΩvW‚=Z=Rυ`΅°Š'ΫϊΈ'/νηT›s:–”‘q ˜ $€ίΕ•RLƒΓδ8νΚ/ρΌδvΖ―8²[ΟRΚσςϊ:ψ γF’0χ»«pωύΨύ2*”ξGΑλ[Ξ«Lο?¨‘VΞπΆΖ—?€—`ΝΣΰLΦ¦fςφ>χΫψuOԏ\CόΪ¦"mTςŒ ΛΠφ%gO*ξz*ΔuM»’tΦ£μy*€7hyD½‘UΞƒMwrΥ–›ΣζΫ8ζΎH₯xM…Ά—&M½ ‹i'AY̚όβ\ςڍόΙ5K«ϊ‰·ƒsΑr*Υ%ΌήχyΤ¨j1ηCe½σsƒν6’csW¨ηŠyg> τ|€ΌΏΎ €Π$§#7}ϋ•Φί€―)ΉHQ*MKή7ν'hQΎιΌ;ς+’άΏζϊ―κρΧΜΣD9·—WiXι± W­˜.σ€ΒTŽ‚Ÿ€PώP)2›ΟˆX€—i΄βͺΝνΞ΄σ­¦v«LΫ4—_ΙT:&ŒS&kS2ψ΅ΛΰŽ₯#΄ςα`ώ΅ΖχtάεTλ9ω%ϊ-ώ”F-˜^κ‘Kτ›πίE©›\ΐΟCnΪ―σιcχ+ ΰ_ΑY@ς=π9ΐŠ”Φίxi~λήΗ‚SΑWΑ ΐ뉦9n2š< oλ=ΙRf΄ O$›έeω•wuξΩΜ[h™ΝEF11ΩPM!½Θ€Wζ^Μ2²κˆσΚWž93]£νZkD5ζΞXγ hGΙDxyHΧ%ωΫ;φ0½ πΣT1ΥkΠpΎ+ΈtOQΪσ˘Έ¨η/ω/πvp8πτόw#Χύx[₯4έφςχkδΚ ‘bώ!PΟ_Ηγ€FdxxνΉι0ω…ΰΥΰ[ΰX ŸΎ ¦³Αέ@βρJύΙΙψ ]c@½ ``Ž”&± s4ρY Q5ͺBσ1Φ5ΧΕ3«€ηΥNJ•SϊX—λXΠσϋx/HYžn:'*)―0Wΐ₯~υό΅ϊ_’Εro”Ώβ8πΆϊ=Μ]?§2(ΜΕύiW~ Ώ hQ£~£W)l/?ή}$}OξΧυRώ―ίRώ’Α«ΐŸt€ψυ₯ώδdό ‚3ΰ•Ίλ1#F0 ΄Η@ΊNΉ?νΚ/Hρ/K[όκE―κK‘J™z<)e‰»λa:―τ~ ΄PΚςJπΐ²ΡδΨ¨»·+žΆ\χλBχΛΥp½Œ–―ƒ‰@yΏά t^yx>ξoοXΚ ’Χ΄Θh yœ ž1μ !Αΐ‘`ΐ–C‘v€ τG\Ριήݟvε€π₯05ηνηgβ$uŒ7?Rφτ℆Ξ~r•Ώ» κ>q•ώX ²NRβ*σr |TA’φ—λά8p6Π+‘£€ΒցSΐ3@ν“OaΰmMWώ` 8HΒ8H#z0ΠRd.ξO»ς Ržχ€ΣνI4ώΤΣ–R=xΪχq‘Œ ΙΡΰ, ΡΥσ~½·₯e˜@zd ¨‡ώΰΧβέGό~ύ„ξKiΌ hκB .p•ΆK:  7z€0z€ΔH"Ψiζώ΄λ~)μ`Έ θ•@)ΔہΨI‰«§μΚΡγ”ˆ+ιύ¨ΧΏ|hd ­ΨεοJ§τœ‡q*ΩψgrΛ5p ”WiΉΌ|r?|| hϊCΛ•ΰœΏ_Λa": ‚CΔ€*zH0 ZŠΜύiΧύξ—ΧλuΏ2$Χυš5±’x?*“ξ'$ΚΔ€WΤ2eΩύš―i·Τ/#@―ς} \ €H₯Δ΅ψξ0hx^ UqΊn>ψ/Bφ’Ά50 dFN°‰CFΩδΊz›œΝ%8–m¨ΗqΧζZΎβn;1ΆπyΧ'0Φo|ΚNήΉΩΞoΤRD³――h£¬λ”ΏDF€”ΊΚ’Ν|Ύ N WΉ4­±xΟ_α.iΏ‡… ‡Uʐ` (^Ϋs&ȐκύΈΈp7~ΝΙkZ@CιΊΆΥΘ1ŒO/~1 ϊ”,&Γ‘SmΩπ#m0Ÿ’žΘ…G4ζ,§ΟOλs“ϊuΊ;ž$„J%°V.9bΨ^ρωότ φOOέg+!οŒvoo“ΏF)Ύ¦/Α8¬2^ά`ΐ›rC‚` —P= ‚ς2‡ςϋ±ϋ₯Τ%šCψ)P˜ §€”ͺ …δ£RΜΫjZ–Ήύw>Ξ.~3˜Ÿ+XaWΦη-;c«eŽΩΚ»yΌ$85]Oj5(|Fljzc³aέafŽδυπχ+†CsF[Ή{§}|λ³ΆfΛsΙt€”ϋBp˜TF•k.XΥrŒΣFιGΟ_Œ„e`ΐš2dYΑ@Š―‹νΉ “2•‚ΥΌ»^λ»h €dX€Ω°cΠP«y›mjΩ=9-ŠvετE6~φόΦ―UB1£ Α@Ώf €~ύψγζ«„ιS;Ν2λﱆ™gΫE ­Ώbόd΅Ώ½L-$εοœξ’\ ΩΰΌυf;k­.ΛΆΔ+o±½³$/%ψeαΑ@™ˆ)€2Ω]e`ηKVœ:ίΖΦdν&ζΪχ~ε>«Θκ{½ί_‰"£d/Fΐ΄mfχŒ²όΞ:?z’m\΅άξͺΔςF™‚ώΖ@Œτ·'χ[Υ °ΓίΏ οσ‹ž΅ΊI¬ΈΧήύ•,2Nρ¦)rψυν‘/Μ<“―†Α@Ω μ  ˜†ύ³3ηΫ4ώΉτ¬³V―Ў>7£ο ,xΑlΤ«Α;1SkoΧE'Ό"g.φ)}•0ϊ꓍ϋκ3 Μ\`ΩΑ{“έτήԐ³A§Ώ`™‰»›?ΰS-7ΙΎDZ˜|7€}RεζΣΒϊΈQH0 ”‰0ΚD|d t–lΑjξΎ#ΩHχοYLgο~œOυRs5Η^-’oœΒN|vΈΐˆΐi³Ξ±ΙΕ|μX-Ο/ΚΩ7 o>ΧΈ«>Δ€VΞϘoΗ7εμπ7Za8έιjώχΗ ΅{°XΞ}Κ2{θχ³…ρ'ΦάjyF7ͺɎρΫ 7θ „Π'cάD_e@»ηιήψθΟϋΥ‹>}“e4§^έώΊΛ³nBεžϋ’eXΗΠ„=π₯UdWΓξ¦ρ‚`ΰΰΰΰψ‹ΨΑΐ!e@šRΒήΙwφ»­YaV£ΦTc3ŒΩΏ@ϋ~Ά^³$¦’‚20@H,ƒΞ2πΘ2+^Κt? sΔα VΥ+3CΝFξiώš07uBυήL”<¨~Β¨ώgwΠΗψΙ›Ξ€ΝaM–•­ΖήΏ‘Κ=#€O'νζτρG· T4aTτγ‰Β̝m<Κ3Λ†:™ΑlͺSΝ’ φHl˜Lюζ{‰²ΥΞ@Υώ£ό}žbΖγ&΅xŽ=tͺ[d ĐiΉ‹aΥ}7Qϊ` Ί ΊŸ_”Ύ0ΐ+tΝΪ³/|ΌCί/`θΎB‚` L „P&β#Ϋ` ³ πͺά-κ5Ί ίϊΏS·€΅ˆξ+$ΚΔΐ_ͺb™ ΩΑΐώΰΐ\QlΒ¨ς%Ι؞\σ>F hlߝΗΩ` 8” „p(ٍ΄ƒ`½ΏeY”βά]ε5VΕί< Ω`3€η{€žH"ΊΙ@•7'έΌλˆ TΩz{”sžοΆΧa TQΩK‹ͺm ^j10lV•žγ` θ=Βθ=#§` Λ Μ]d™‡nL¦φ ό‹;ͺψ5.»ψΐ–Ίζ₯ Όέ°²Λ„D„` θ1Βθ1*#‘` ηhlY1²Όε_Ώ~HλόyΟgvˆSδlK½Ω3ƒ“{Ψ½z©=zΒόΦWqξ‘|0 ”2@)#q T«Ω Έ₯8Ώ¨ε€FZQ_Φ“2­&ΡM¨ΘOΖ(F1‘a‹U~}ΰ($ΚΓ@TΏςπΉf`ζ9VKoωwuŒά>¦εsΊŽ]κλ?0`n˜hE6’σ}•lΝ_ œΚ(h”"θG „Џvάju2P,΄ΌώŸ±§ωύQfƒͺlC}ΎψΉf+‡[–‘ŒηΦ,΅;f.HFͺσ‘D©ƒ>ΐ@}ΰ!Ζ-τmθ%ηgm‘Wζc@Ώ™dΖWu«jς|…ifC)?ήι‰e ΖQH0 ”‹Ύ°³hΉΈ‹|ƒ^c`γz+n\gόšb‹Ψp<Φ@vΞV>Tk΄fα‘‘fΧcΚ½…2|Σ:ΫΖ=…Α@ˆ€2’Y]e€Ήσ$Nξζ Φ΄υš[―dΡbEmύϋλΙΝ[7eνzF4žͺδ2GΩ‚ώΒ@ύεIΗ}V=³XV‹Ή‘λ7 °š«¦YQk*u@Ζ‰ή\ΈsŒΩŽLή^ΨΝά‡f,΄hwͺώΧ7ΠˆŠΨžbάCΏ``Υ2+Μ8Ϋκ2Y{ο€&Ϋ~ηhΛόz’Ω”,£μ'|ΎΨΦσ½ΏΝ°βΠ½–-νu*$s•X܊γ/  jb ΐ‘f8zMλ-Ώρ k}Œ=Hοϊ=k†[qvˌݝΌZΧƒ9\Rš™ΨΝ…_;™εΠωϋη5ΛνnjbdV/ΰΰ؍ΨΑ@Ο0@Οπ©½Κ ׎dQμm{ίlΉ#ΨhŸ9φ²Ύ ½ζύ5όρ)VxaP2άG64z‡’ά!Α@0P! „P!"Š t…™gZvΝ-vλ˜Ι6 e;οvζ؏έf™ρŒθ³ΑꁗC”―ŒΏ?ΝμιΓ,SS΄ΗΆm§ξΈΏe/ƒr*ς ‚v ]Z"0¨l6>iEΣΥΠ³Ύ‘W' xOZ>Ί£φXfςΚ^ e―­~νο^‘lχ«<Ϊ8Δζ}Α2ž4φ ‚Jb €JzQ–`  π.}a:sκ/³Ε#§XŽΚ|+ξωΰNnφ–ζ­wυ”€†ϋ₯ι’όmφƒYΦΨPczυI1k―y„/†ςοΒCKƒ^d  ύ„^Ό»Θ*θG ΜZho.ν7L δ‡ν΅άΗW›Ν}©ω=|m#ΨΣ•]σόJw;‹ύ.kΆn¨εYή—ΛνŸk²φΕ—F―ΏύόβV«x ° Z9(e`κ"Λ¬Zj‹ωnΐ”ς}»kΜΎzŠΩwŽ·’†ε%κ₯KaσΏΛΛπ=ŽŒˆzκ{Ψ―§Xρƒσ›‡ώIϋyΟΗ*Οη!$*šžξTτΝFႁΎΜΐlΎ°ryσb;^·»€žψΏ6δl4χΌwζΛMΫjΉωΟ›M}Ωl ΤώF½5€²ξ¨!P/_γωυΌn€Ύ·M·Œ5[5Β «†[½ΊΑΝ_χϋ2—^Ύr™mb―‚―όρν` ¨d:ͺχ•\ζ([0 t‚ΧŒ³ΜΣ3μ (ψ‹Ήœoρ5Ώžw$ΐ©ΜNΫd6‘ƒκΔ«POΟ£ΜxέΠΦS*‘LΖ~ΑΖ>ŸΖπ`²!$ͺ‰0ͺιiEYƒ.00s‘Υ±u°>hψ?„’>ήόIτκΗνaΕ  ?~—εG4X±.o™,=}5 lβSάΕ>ΟfΉ=τϊΩyΠκιΣΧm—<@KšŠφƒG—Ϋ&"ΦΦ°ξ`εxΥO\‡ΥΒ@Υς€’œΑ@7ΰUΑ ϋο«3ŸΘΜ6ŽƒqŒόŸIε?‡ΐΩμ0A$΅΄>*ΐΌώ6°«ΰώž•ύOΪV[·ϊήζΛYx¨΅­ι7烁`  žR”1覣¬QμYF2 Ω·y/”7ZmΓΛvxCΑ†ΧΥΪΛvΫ–{n·]ιlE`,e_°Blη›f&όΑ@u2@u>·(u0pP Μ<‡-z –₯`f€e(υaΛ pHYs  šœ5IDATxν˜U噀)4i ’)’  (TDQPƒ-ΆθƞhL6φ$5jb‹Λ&›Υh4Fc4&–]»bΧΨ5+v fξΎοΉχγΜ0Ψg(ίσΌsώσŸΏ|νΟΉwξάIi΅¬φΐj¬φΐj¬φΐͺε^cR³Ϊo°Sj^»ne?/_Ω ¬m_ŸR™uο΄MGνΌEΊλ¨iΞΡRαί6NΟΌΡ>ύΔkύwXυA»W ΉΏYj±ίTυDEšΟ”ζ?UžͺŸ,O…gSͺš’άSϋ₯B§]ΣO7ί&U ©XŒΜΫΨΆ_ZpάΛ©Œm |^Y*ηXζ6X(Ke_”₯Š>Ÿ§4aRΪςΦ^©εΤw}ωΎ+c9ΫWFΓ겉νΏpνύ)5―λκΒ:’!½²všuθ”ΤzaνΚYZežΚ&€Φχή“ͺ|Γ\VH©ίgiM·K¬œa_hΥ*“¦§ύ¦²Ν/4½αR3’`nyZ―αV+ώΥU&* ιŒmt$`Ψ§+~€gΑͺYΠη§teΟz‹sHΝuz=έ©ζl₯-¬μ `πΛxδoφκ‚tkΗBZΜγίΒ8σ !5―J―-¬Y9K+sd+Ÿ°• ©<=’ζο8&ȔΪβYE›Ί€yΟ?”T4OΎTnD―lΒ—VζΠ6ίΜΙpxχξTvε ”xΐ#'κ–yτz”~³a:•-«ζe ΰX+e¬Œo(&Ϊ”•U¦²η'§v O βe^―iΑLψΊΏ·‡·A*?{­τ·^IηTΝO<:¬ά²2fu>π±r=Ξ‚{αvΈ°¬{ϊhHΟτΔπϐςBj3΅Ezχζ΅;ιώ΄/o ώΉP½ x#mηϏΩσƒG7F?KΠv΅ό?yΐdv΅ϋ[ΎΐFž½“Χ†£υ―ΒAΠZsK(°βσοχWxƒΫΦ‚Σα,X«%ψΫBΗZf­Έ’Ύ2`«cΨ :–κΟεΈvyE2θφ­$!μχ3πά€±νwα—c΅’l˜8Ά“Z–»#·Neμ›eΌ½ZxόΡ%xήx·Ζ6οΚ΄,!nί `8Ό qOŸAΩ@zMΡ±²νc Ηγn°'μ ξ,q;π6`Y©ο™²xυ_ψΉΙφ©ςγV©¬3tΙo/Y>·Ό³ώ5λοelρs*S—χΪ€xΨ­4υ^ψ»vώ―“F_mΘίs―\ι Ύž`oAω[χρ|π\ΩΟΐp Ÿώ Γc`½sΩ.ˆω¨Zzi½sΡooΆMΫMι˜}·CΪc~eκζΘ}Ggs.ύ$Λs„c‹¦h?>]r ΄Έ΄k*\Σ!.^/ΎΫŸ•2!οό}Ϊ³ύςλXŠKŠˆ ΈŠέΊ[ƒΫΆχοv₯rΦUμ5W―Χ¬wKOΕxžΫV™Ž)ŽoΏMαPΪƒs9ŽγΪΧ„ˆ±–δXΞm¨’o―’ίψ0ʟNλ“ k“ o°Γ|¦,UίΘBβWΪΏfόe&nsΛDΆΨ&•½΅VZkίΧΣg“ΊΝbƒ­5ΊΆ(€ͺŸm’*n|!Ϋf †ΥαΨ8Φκ™΅±]ΰ*³l° „eƒ|ι\ ά]’ηSΑέΐΥκύΫDš^‹q}>πϊυp4¨ΟΉπ}p₯ϋŒ0lοo Ηώb[J}v―ϋGy&ŸR*όψωTν‡Tκλ8Kxp3r\»φμτθ§wΦ<ΣΔ8K|ΤAΛDž|8ϊ~–8α΅T]_πHγζπΙ›½΄ŸžnΫ5[AIs;08νw-•Gp| ŒAquE›χ“R½jX7φϋ(M&ϋO“ΕΊ#Αΰ\Φ9†Ο‘‹υa‡Χλ#tρX±F§ΤΆΝΈtΧΙnΑ§mςpnujΉ[ιe|ΗT‰₯–-y:*΅έξ“΄ρ”ςƍI¦Wώό™4¬γΗΩύ6ΏeκΘrώh›―JΧl힀lΟ„xp;ώ"(¦‡ΐ"‚Έ†:ƒΎ°|"ψΚa,xξ-ΰ °η&‰IαnασAŒϊΖΉΗΫr$‹Ηf\i^55Ν>β4’‹Ά[¬΄γQφ„—Xω€ν»νœΝ»Ψ> 5P‘₯–ImRωΗ뀑Χ?˜ͺgμ;f5ζv`3Ε«Έ{©ƒ+L\]b«ςdΈb›υφα½p»ήl«³Ν­ή9“Δ`ζΑ§:ko’„KJηŽs\ φηoΐΑ„τ•†c…ώq4yσσYnn+ ™΅ρτΤΪί>ζp½^ιV•ͺ[ΞLΫxσZJY& `0±€νΪάOg·ήF©Υ°Lw§±Ξ ΡiŠAΦ©5Υϊ‰ΰΧz Έ o†„.Π άΦCά ά¦ƒδ–n|(Œ1)."ΦϋrΟ„ϋθ#_\Φ‡L‘Πφ‡ƒ`ά>¨kηΤKyΤ³ §s\"q~cιψ—J–Itž› Sg¦η―οšZlω K@Υ!< hΕ94Υ‘:δΕzΫ;ŸύΥΙ2qΛs1)ΫΫξΟ`φ…Λ Ζ1}hŒφk€ΰΛΰY…τωSΜM§€5ψU΅s/Vή¨δΧΪk€Ι3ggσ,Ά}C 4j©ε₯ϋSuυΝιΓy‘4KΧ5BΪU§ΒΏ&‘Λ²^nEW—Oτ ˟+Uq‰Ž=„ΏΒuΐ¦“εΏ9š&ŽmΗσpX©l0¬7ή.-dΫΩΧωœΗ9b~bŸ(Έο韽Œ΄Ÿž8b׈Μ>ΦUσ;銲ŠτΫί΅J­«Κ3¨nXΎ"έ.L]Ÿ&ΎwWγϊ44β2I'θ±sͺόKο4b^‹T=g1Iΰ-cbηTvέ›©3NXŸξ›ƒq•Ίλ\ο›ξ &…«ΜQuά½πΈ ’ΏΐyμS:·ήΥ:·UHυ"brΨ.V°ύχ,«“Χ7ζT―Jυ^{ž‚+ΐ$:bχP7υmγΰixΎP•˜σ\*;sHͺδW~₯^ρeΰ<žώΤ;mΙnξ.MOϊξ”~υΧΆ©πh%of”ήȈ74|Cθeœx1οςΆΜήλ Oν+ω=Vψζsΐΰvͺ3Oε}\Ρΰw`˜Φwƒΰ5±½Ο²θό_ƒmƒ ΅eχΪœχϋ9ΦΫ₯£:ͺλΪsͺΏΊxΝέg=˜&žuΆνΰΰ±'{˜δI ΆΉΚ +:ΎAφIώˆo•|ηρ1όφ<>ΉΊS*τSόσ΅θά$­χH»9ˆLm— aΠϋ¬(Χbΐ>ΓτΑ[§mΪN+šΥάΫ΅#cΠ”Γ@g=Wƒ·„AΰaPtAΠωΕΰ[ΧΆ„;ˆ+Y‰±‹g_ΙΪͺΗTLλ[y,«ίΙ霁z­ | xNιpάŠ:›8’nΝΧ¨ΘΖHέwJן>8nΒw/τ‡š§κσz€Β„‘ι½vγΣPΪ6}YwDΡimvM;²AžΦl +ϊ€τγvΣh΄£K—ΤŠχΏu ΞΦ’³uΊ’C;BΞ@Ξ„KKυ²@ΨN§³ˆ²_ϊΈκ €GqΗΠΩdΡαy—?)•O.ΥΗ>έ±Ύ€Α₯²s˜h‘ˆ&…φ;P~^‡HΒHμΠ'μvϋevΰ—Γ)Om6>HΫρ€»eIzm›ι§Mϊ¨£+Z―™ΦθΆmjQΥ]·ΞŒTiWΕXt‚I ς«™ΣΜΑu@¬ΠΡ”οχaP\ιD:ΐ:₯ςK‰¦^ω$ˆqm§Δ5υR'Η6Ι”ώp1DrΉέT§τ„σΑ‡<Ÿ’D“Ζ0ΰa³eλbœ])ΌyΛΤ¬’[Iρε^b·T§Ft€κ|Ε{Ί’Γ6„ŒveŊ’X#^w ₯|\TW“βJŠsŽH ˁ³Mœ{TO‰±ΥύHP·`Pœ?$KΩ *9ͺ»(φ κebyΫ’MΑΰχƒ˜wwΚ΅}AUΣ“Ώ„nα$λ€ήtˆκWLmqW­Nσ9@GŽƒs`&\&™’£ΓρΆ5±Ί=Z§>bR:Άγ= ‚·—ΑΉL'G·u%’·xΆπ–γωGp(¨‹6Ιφπ(Ξ­+Υ C4Μcg :΄ Έϊއ) „3‹g‹ώŒk<ΛΑq  bbyΝ)ΰf°ώh&‚σΊˆο'ˊΫσOΑ>—€rŠs˜Φ;·‰ νΚUjήΊΠΩΎ'XL„ΛΑ1a¨·φ˜+Uθ(JsΠ‘q:„˜:­.gF―9¦γ0“ΘUs½θΜH†£(ΏΎ&Še‡‚mφ†wΐ]h+P¬w9°tώ`ιθΨ&`Czr9.Ϊ™ κώ[Έv}‘hKμfωϊθ›5jκ?TΦh¨Ž›_:κL3[”“αpeΊu¦’#ΆSλ”pBcΗ5θαDΛ’€pͺs(Ί `6Έ ]_Βyΰ˜Ά7‘Τ?ˆρΌ¦ώS!τ5`‘ΕL<χΊνC¬3±3n#?’ό˜τκe‹»Τ6ΥŸKK/*³Χ(f}ηqΤΡΣaΌ}ΐ±Ÿ…`ΨΟΐ¨Kΰx&AŒ«Ž@ϋ·Ά/MJδ%Ϊ©³ ’½ΰX0 緝m–«„2Λs’Ο\Η΄0Θς>p|ύΐUαυhC1“Gω’xΘλ _€mu’r0τ†jˆ16₯¬4αΛΞ£α#08ΑαpŒwΫ] Φ[6Ιuv…:ΆsŒ_‚ΆEΐτg΄„¦ͺFμλυ9 Nγ`.θ‡Ϋΰ50 ΤΛ1ΓŠ_K0뚬θΣ,λΌώ •WΑΰθp·ω7`p5)φΙK8Βzc²(νΑ1ΊΑΫ₯²Οn»n£!nσκΰV―s•HΛΡ9<*ΑΆφ9 ―9¦γ»ch‹+]°ή[zuI~Ύ iΰ8›ΑG₯²:oξŽΚz°¨ƒ’έ+„D–«¬FͺΈF(Ε‘'˜a”NΠy:q Dΐ£Ž3a\=ΆΣY?e .„α` "π;Rž &—ΫΎβXΌΊ$ζτš‰Φ£Τh Η·ΰ)pLΕyΧ†CαXP‡HŠ™žΞγ˜sφ‘¬ώ&Ί6x ΌηΤώ! ΏτΝ7Ke™ξ΅†υMJ46”Ty€Σ#‹=Zgπ£­+«/<αΧωΐΫΧ•οΚs,οΓnη:Ο \nΏg@H$Zœ' ΉcΏ\Ωb$lθaέΟ`\@΄ΙP—Γ@{ΤΕcτ5πκ`½Χ†ήπ<τ€π…m«'8ξΈ‘C–8^·m“”0B£ΝςpŠŽ0@δŽ`‚Έέ{¬€ŽΧu’mL„pbμ :-‚ο< ώ(ΈΎ‚»`0(Ξ«œqΪ ¬υΒφ έ’ΦDT6€›ΐέμ―° Έ+¨I‰vPUxνΣ7ώ§ du>œΧ—§ϊDΗςΊΧτ]“’ΎΚ©€Κ€ hD$Fή©C²«ΕΎεKΑ~:Ρ£βŠqΌ^`t΄2ώ ŽύSP"©Šgş± σu–M¬ΌΨ7Ÿ(– F^ΤE1qCŽ₯ WΑΠR₯»‡ΙΊ+¨»AwΧ&ƒ―ά™•Šv•ŠΩΑ>ޱ#8φF`_ηorIΑk¬Ζk€NΦ_ZYΆΞ‹>3ƒzZ@l»ΈhμI`28fΘilw1 „σ ;o]2ΈΚR]>θΞ›?o [–Œ&³}n„#ΰgΰSόbΐ Ύ·Œ.ΰΚΎ΄A‰mJψΡ~ξ”σΰψΈ+κΗ&“ΘPΪ m΅!„‘U:χΊ}<Χ(¨“&ΐχΐW:`=P\ OΓλ0ηŠρσAΛ—³†όΠ±υ‰Ί4VΤ9Δ~&³6Έ+©‹:)›‚η8λΦ‡}Α~Ώw<“Ϋ€Ί³Y/–υ‰+~<„)΄₯vΤe3Ν–―8©Θ?°υSsΣkD¬RΣiaΩνΞ•kˆmu„ΞρήξkξŸ€βͺ71l―Σ φΟK82_eS[trCΙγρϋ₯†Ϊ~@©μ|κύ ˜°ϊ@ϋB~@αsΈΤΫΥέφφ·Ύ4τ•υ»ƒmνΎn%’ΐ>κφšυί„ΠΰgAQI%` Κ]ΡH๎q‹TJΗaV y§Ϊ^§ΏμδΫAΚ*ψQŸ3j·³½:Χƒ’nyɏ~πϊPxΟ`ͺ£ΑΡNE[_Rώ―iw$Έ‘ή"¬ίΖΓ\p}`ό τ‘’/"q΄-―›Χ—‹„Ρ:Ι«˜(Ώ…k³Rρ#Ρ>(&€eueoJΏβ!{ψ'Κ‚«μ`rh\8Σcμ8*Ο•Ί [ͺΧyy1@‘oΎ>ΖΚΧ5¦μάϊΒq§ƒ;S/p^ΡGNύ}ςά=^Ή(>KxwΫ{œ±„νϊοˆEυ&άrO‚|π8Ά0'Ύ/)Fk”’–5NbUEΩkA@§= >09Ύc‰Χuτqπ+PœWρώZ[ ˆςPW‚d—πGΜg·ί:―Ž£α°ΞsƒXm7Χ ζ ψ ΨGϋ΅½vθ } Žu ΌΪζyψΗΉ—•½ ΅PΒP%œΨϊgΰ›°?˜‘Φ{΄νwΑ we,< OΓn Ψ|ry/up%hψš₯£ee6XCγA±Mc$ζ_\ΫΠοΘRCη‘끳Κg–ΈΙ~:˜ υ θ ‡β8ϊRι7ΓϋΧχ‘;€>ΥζΨqνw(LE_Y§>‘Έg‡Τ¬ΣπΤͺΓ ΤΊνΊό!GE¦D¬d {άͺ ΈRA•ράνPш‹ΑΐŠ™nΌ¨΄Κ+ƒe9 "±ά2G‰W[φ ’>ΓΟ/]Λχ«―­υΪ€ΤΧ¦x΅ψΣΐ„-Ϊ·3¨―xλσhΒ< J΄-ž-όι‚Q.€ΟΰiˆΠ―Ξ#&ƒ˜xϋAθ+wΩΦΝ+SΛ€5:σρόυwNΝωn!}½„2‘ώΡΒα¬νŸς»»3ωΈΔρ‡βʙφŒ† bqo=޲r0Ό>Θ EηDj;"Vƒνώw€s9ΎΙ₯‘:DC’Ί‚¦ΎΚΕC½?#λmPΗηΣ&}ΰ<κθ8ω…bπ/ƒΊΔΆ#A8†ηm˜οÁb½γš.η3ρ“οK]i؊_#—ΕΝΨνUzCλ›Ε˜nρyΧtν)}S¨΄ΰ+~}9“ ΎΘ›gφL…A»€λIΥO**²¬Žΰ ₯'h¨JŠΚ©tdΈu}ύQ’=9κΐxhΤ:Υ~hύ:°œ ŽSW©ΞV…ΗΖHŒqDcηΪ¨kmq¬Ξ…”7oeκ>΄%/ΆΧΞcK•=κ΄Oνne[x^…  8ΆνšσϏF1y‘ψτίgτΜώ”lž1›Aμψ“Ό§φNΎΉνzΪ¦}‡Φλ;/§Δ7~LΏ¬(ώ/)ΚΚ~ΡΣ»$Β>[§/Ngub™¨|\ι—‚[“²eρο6h05TΡؐphΤΩΖέ`8œ ]@CΓ‰Ρ~uΡ‡β"βJZœt]\Η\ΡΤωvŽ“άQ ¨zχuΥr9XηυZɏοΨΆ±ώ2ζƒbRDΫRž WCφlu'ώΩcΛ΄`10FΖ-bfΩXΎOLGŽΜ~gB·zdΠφιQΞ|"ψ΅ό:I°ύΦٟv?ΜP/A8Ε «¨h„’3j'@v‘ž:Aƒέβ’―|LΈp„NoHBϊ’$v­ϊΖ°Ÿ·˜/’=Ÿ8Φ©Ηχ@1\™ΎΔc 7τπΨ j‹υΪξx&ό8pQ)Φy]βV©Ώƒω#·M“ŒICqσΪ{|Θe“QΩo!ι–[=όƒ€ςΟ›§A'Ώ˜^2•ΩBε¬ΩΧψ¨ϋyΊΟiBεSι¦Ό”=ύFΆΪA§Ή%ΞGΣQ^_v―K4ήλΒ; Ρί‚)p>˜ύ1δeSN°―NQΪσιDuŠcνŽΦ»Κ.¨}‘t^_ΏΠΏ;νL‚;ΰψ nνwΜƒΊΖPW―ο7ΑHΈl»ΌQ*{.•mΪ₯ΦφHCγ/ιήufQΉ˜Έωχηoφί™Άε›ΖψΓγ’|Φ2•On•άο“4wqΑ·‹{ΟNUίIί)ϋ0ϋ]l΅[›jԌMYΡ9j QωΌhε@°μ+ ϋΫηΠŽ«S'C΄qž§N±}mΙλΕ₯cΈ+Ζρ»N}Α·kτ+ “b Wηπψ|t ĐΦ₯BŒ‘χΣϊ\3IώŽγCuμRvΫKgΩ7ΏύYϊήΠiΑβ‚OŸla–ζUV₯ρm€²šΙί½+-59 g–f6lŒ`eΩ€ΑŸLΛήΘ0ΈΖΥi«¬u!žkΜ?AγwΫχ„xΠΉ&W¦˜ΙL~Ύ$wΗY»Tˆ ;GCςXιβ$Žy;ݝμ«^Φϊ€Ζ_Ήω9-ΗΉΆuώ²ToΟA ;ΤέΔV>ϋΗ5λBτ‘:λ3έoZ­l7%ΝΩwRφ­bQp©aa’Šˆυσ¦‹ΔΏI›Η(‘™ ΒU΅ψ²yšGš·ΰ›>†rκΦfζͺŒεƒ  ΖρAiΤ»u’θg›ΎπpJλγš‡Q¦˜­Κ€βa‘Ÿ‘(Vώ{ξŠA―Οξίγ7J}\™κ’DΪ_½=ΚΈl7ς6Yw6„L+ς:Dϋ¨Σo‹ΒΏΒaΧ―$n|Ε`½ϊΗ5G²«0«²ψie'Ι€η˜ΤβΎ^ι.RqAXΧκ;Άγήzέ~?ΛΜGh烋cͺ¨ΫŸOΏa€«Μz‡—F\§*―…^–O‚ΰ“ρgΰ5W•Χ^ϋηW3§™ψr)€un\¬γθΨ!ΞgΐœΗ€*ψ½F‡©”½~&Έ}»ΛΩv ˜c‘7h―γŠϊŸŽ­„βάδQgϋΈmτΊ8F‹nΑœ©σUΣƒk-ƒK Kgήέ ½Ζπς1šΚDL_Π'΅˜‘9‹·Ξ#kσ½6·¦u§½–}žo;Ίπ²3SΠ£F’W‘οSΦ(λ7?KΎl’Έͺ|™γΦy&ΌΫΓίΑΆΔ|Η¬+ΘTgNŒs#σ'₯²s+$n_ξ@Ž_—Dΰ Τy`Ÿ ξπΡΰΓι―Α6Ž£mP±Ώσ~”](?w%λ,“ώόT?}g‚ΈΣž8gZz¦ύΝiϊ}RΕάО υ‰ί3tIOvμ›Σ†~–ζ;X&y.ψ ’–ΏNh_ζ6τEOφZ«*U6,έέωδ΄=χ’mδP±Α± ±NκJΩ•γVξΪBŽ‘`{ƒΑϊάA†ΐΟ nž›χ€ŽsŽ|γΌΖ6Φ!ŸΦQηΆUμgpMW½Ύ\Τ[?:Άv»+όΌ¦t}Ϊvμ>4ξj• dF΅±ŠΈΓW‰ε kρmγ£³ΟQ|1B†Œ»yzζGΌό»Ž©π{βŒ^©ΐ·ZOβUκ₯ξnk{‚Ξ(·f ˆ zŸ›*>4Ψ-°-htΖ‡4j&χpˆ`d₯7\ Ϊa›ώ κ>0ιΕz“½ ήNŸ…}AρΊ~²­ΎξΑ»ΰŸ·ξ³ΟΠτΦιΌυǩݏ‰αnΓΣ Δt›¬Mc~π€ ™πtC:ν––σMV~Ρ±«U1ΐ*๎֨ŽΖ*:Δmϋ8tšՁβΡ•bζ›ΥŽρ6(:}8ΆsŒ‚Γΐ~ FOΚʐβa‘Ÿ{rζ|y©έ/­v9ί7ŸLΦdΗR³ΰπ–h½¨³AΫτƒύ=·½4<ΪN»GΒmπ.<σA1Ψ^·}CΚϊ‚ΝΊ(έΗdsf'ό>gΨ:ck>©œzοτ5_E·†ΓΆMεΎSXG«pβ›\S‘ΘL•\ ¬’αλg₯βǜζP6ΐ»•κ ΌNqw0σ‡»Vφ?αgb‰γΕΨκPm=σΧhZ#»Τ”Š…ΪνjŸΧjžšΘκe` ŠRUκ‘­;ΓEππš6xMŸ˜Ψ[Γσ`ΰpx>‚£AΡGΪlŸq0œΣq‚)ŸχB“’ΗΠζ4Πιfy>| qKƒ$’ΑlΎL”οΑd°o{Π‘ω&Vgp|£S]Ž«46Ϋ @C’n:_±ΌUV*Žr©μdƒεω—ΠΤ]έΞ­7τ‡ν'Α`β_ α#ms.oϊΕφγA;ΗΠΦ!ΰαUΠ$ε΄Ί\ρH1°ή4 /¬St’ΑVμϋ˜έ#@ΡxΥWΒΓY©ΨO‡νŽ•Oϊ―Jμ*φwLu qΕ€V―Ž0žuQΏ°Υ£uϋΐdxBwΫΕX3‰pΜέaZ±:ΫU7£όœTͺk²‡ ΠμN0t€ ΰΚΠ‘{C>PΦΉ‚c%ΨV1xη€η&EHμ,½¨ψάt° δ8Žνœ΅ƒFU&ω@ζλl_ŸΨΗλŽk€LXƒ§Ξ^’] ϋτ„«Α ώF58†z{λ @A»‡ƒ·³]`6(Φ½;y²"Θ‘(9΅€¨Aμ d·F©| b₯œKy;8 ϊ€Œ­w{Κ ŸΒ 8֜¬Ttͺ ΰΨ:ΥD°¬ΤNΗ]œτ§ύDq<op7v$oE&ΰhΠFW­r<ψ0ψΨξφ9μW*ε¨Δ|ϊΓρM”έΰ+p Ηοα;ŠMW4F§mΣAε7EΝrWjl¬Ψ./Ï€<nι­ΒέŝΑ$ˆ•ιό&ƒN]β"[ΉηJΜcΩ‰φΕq άy ΞΞcπέα Τοa4<ΜΔVμλΨ1Ύ68–mΓΦH υτZ$—;Γx˜ ΆΥgΪ΄Bˆo >­ ΓΎn™:Q‡jΌG6Λo+Χ΅J£NG˜X:ίpμΣAq>ΗΊLηqk?Λ‘Lκ蘁η‘d#Θ>Ž#Š»€ΧŽs)«ΓωΆθK1“ήόtόΈf{}»Šσ¨³Ι₯Ξ1άIL$――0Ζk„’ƒ€/θ8ήlgΐ”IΩΟ’5ΈΆΔφj½eκ8½ΰxά Fƒβ<&W>x&‚:ΩΧ@(Ε:―_Z:Fΐ£Ώz:Ÿ²/L‚GαΨlvS\δήξΉΧ$‚9Œ²>Qƒγk—;ŠΑ>Ό>”HβY©‘f{>\±4Μpn[·‰ρOp(GC¬Ζ)‡c£ΞsbΠ¬s\S_‚+ΜyGNv19ΞʝG½ϊDωΫ”ν£tƒ«`&œ Š:8·A ).Rφ\ έCoƒώ?°%¨‹:†^GPvάνΰSPμ·ΒH$€R4F*ήΏ·mΖkτί@D@).Vb/ƒ;JlŸvށχΰXPLηυώκ|­ΕΊ#ΆάοP§ώΏƒ! „ήγ(ŸΎχΡρvh"Έ0Γ[ΐΗ έΞ-ί…@»ίλΚ ™*>t`wˆΥτε ΰ`¦[¨ŽhΘPA³β+θ6nN6ˆύa/xn‡ΝΑW&δΪ O† Œ„»A=χΕ±» Έb•ΰΉ’Χ­X³π@œk›»…c™hϊγmΨΤηxm &ƒ_Š-šΠOa¦k¨ ΰy8ογαίΐk:Ψ~TŠ‹ˆ«7/Άκφ¬˜H:VΗ1½w{Λ1).„χΰPL$ΫΫο\PΧΑsE}£―A00κ±ψΜ‘—θuκ yQΗάLΌQπ0œ‚’/Τy,|Κ ™«θΤ ƒ‚bpn†/Α  Χa:ƒͺΤzΤ―γXn©ξTλ@ΗsΞ8w,Œ«α0Χ‚Wtv¬lϋκh €N ξ ‰sj“ΎΈ¨ΤΠρϊΊlΟΑ-ΰnθψκ πeeΉ$ΐr΄¨οΧ~šOΗισx.ƒŽ2(Άχ|δ₯*w’σ«Αv3ΰM˜Xms>ΗQζƒ[φ§`ΰφ†}`"όφ„ύ 8¦Iu1ΨίynΩ:ΧΎΠ±B,Ϋ_‰δ½€rpεψυ2Έ’ά ¬?΄tδύφΝD»Φ·1 r,¨ΧEπ%œŠ«ή± ‰γ[΄7€ϊYoR¨wθC1um°v©£‰― =ΰ/p> }a[―G›±”ί₯φψΕΪ&ϊSΓuJ>ϊ•tΥ833ψ=θtΫƒ§ ΫEμOYηθۚ4N5Π:Υ`ιHΛ&ƒ2\α:ς`PΎu94―³Ξ €q tƒžΰΤy=ͺ§AVGΕqb|ϋΨF›/Ϋω°ω$ΡΗ„Ϋl§O΄Aή%Ζ+ž5ρŸu%ΐθARύOAC]Qα°Q”]‘Š ­@§θdƒξΡ@λT―;†b0֝ϋK8ζΒ9ΞΣωJœΟύΉGιΤΉ"8gQ6θπ ξΪ€.κ(κ6œΗ²:ZVGuUμ7v‚Π£ερΰŠcΊ»Œ…wA‰ΆΕ³&ώ3@'*ΦL±όθ4 ³½Gε΅p„«[ην :Ε ΫζΈΉNΥΡΕvϋΕ~φ©K|ΉWŸ„ Žοφ―τΗώ%τRu5YΥΣzΛΪλQΗώ½!j’Eωχ”ΥΫv²L%ΪΟ–ΡΟε2hN7β\2~ ΎyβΙσ.ηφ³Ξ'ϊDεω₯ΰX³ΐΊΎπlΓ`D©l[wΗ©K―£Ή½E8ˆcTΗ?ά΅ζΑgp τu1‘MZuRίζΰ8ž[o{Ηb|uΣu½ξ€i`»VTήm](;:‚[ΰP4άΆuRoP\-ŠύΊƒχQ·wq εHΠQΧ@OΨ \…:ή@4ξ VεdpΎΌœž?Ι•m§NnγΗ²ϊXV_ΗUΌ~8ί™ ξΤ₯x[ˆ€Ϋ_ρšγˆc;žϊ?›ƒ²γαPl·ΒˆΞΣι:GΡψA`π~»€ApΨ/Ά}}f₯b]!“aΟRIΞ gZwjιΊ:8Χœ'’b&‘€qξQ=NΫ«§Q‡s@ρΊ(ŽAέ–ς“πΈ‹(;@ίΔp'ω(Ž>PO―»{½έKj[Ξεn&qžΚΚklτ΄i _Sή’Ώ\]cμώ§§ J»π€Ψ¬cΝz χ8tdΰ½Sg):πMΈ ˜}l§σςΑΰ4s”mt¦ύΓρ ”m·ƒΪrdΒ6‘P|οη“αΊR;Ϋ„8Oθd9Χr~±Ώβ}]½Mr“J΄₯%^£Uλ¬οΊΝ*‹•Ϋ³Γm•~YΡ©ψ‘š²Ύ5»)Ν›Έ¬=:sx*Ϋ+]tΤΐτΕΩCΣ‚7L…#ϊ§κΦ#k,έυΫtOλ`΄Α€#<κ$e< OƒAseν ή~Ά5°"ε0ΘωeEGΫΦ„Q pϋδΟΣ„ έΌy7xΆ‹$±}MΫjƒ»œsoCψ-Μ„“@QwΕc[EΫ–έΣzύ·L›έ,½uIχTΈŸΏΎβ/{ͺώΦ.UŸΤ/ψŒU΄Mλο²$ΒZγž'ρΧ(Ÿr_|²¬ψgJώyόιOx7ξ'όΥ IxXFcΌ«‘θXƒϋ˜ŠNΧΉοψ6W›ŒUd Hί ό|:Ϋ1 Μ`Ϋn»QΆ}w!ςφRΩU ζŠγͺ‡mΕΎ'ΐS ¨¬TΌm™Α.Y]YZ—ΰχβΧS‡_Β ηήQυΎzš―ζyš?ΙΣwSπεaƒπΫ©e7Ύϊ­4^Σ:¬9'™ΏA›Ψ¬ξοςϋ…4HψΜ««D9>…«‘(™CuͺŽτψ’ Ž d2ψm<WγW`pcΕQ,n»Ή£AuυΖ<ŽηΨφw·qCqlƒn°hυgSw+˜°&§xMΤΥyΌf»y,€ΛN»ίΉFρo/kSœλ7ΦoΟM3]–#³EΐK'Ίe&Ÿί–ζτzzjνX½ ξaυdσκ"—=“½|£ΤΥCψ tΦ;₯£:Š«ή@ ƒιΛHλt¦Ϋσ€RΩσθczΒ΅`½l^::Vΰ-Εr΄1aφΗω ŸςMͺθγρ01l§N·‹=Α@«Ÿ„Μ§ΰ&šm›Ξ«ϊ”₯[»aA3όRŸθ7ΏΊηΚgΣΌξ;§“΄˜υ΅ol½Š/ι3ΗLH‡όϊΕ4»M=Α―=ΡΘSa½ρινΦkfMl°μAU·ˆG}π-pU꼏αpηψ·Ζ8:Ž«_ΩL*―ΝΫε9=w+τ¨sΞ“a*φχΊ =M–*Έ ‘Iμ½^ύ½VΪSΝwϋuΈ’_νχ^*›έˆH¨Μ?+Rσ­?Ι>υΔΩK>;—z΄1[€+ώdϊVW¬m!Fο˜ήϊxb:qΑό,ψvseΥ–oΒωΆ ›ΑeΰκGΪd@v…»ΑΊΞ`r}™σ9Ζ<Άρ ƒ‰±tWυ(Έ l κ˜λm ϊΞ°>\Šc¨ΊΨ6Ζ§X#ΝΚyiΧyhΪφ–πζjΡρK}nλΑ‡6HmΨ5f=υp6~#zΦέ€.ΕκnوZΆ°ΑΏΉ\Μ·FLMνn™Ÿ­κ˜!η#Έ:6/pr'ά [A7Π•εΰκ3Σαop| ^wK78ή—?‡ΠΫγ?ΐ~nρ`ψ3„Ώlσ$8Ζνp½œΝ·ηfΛ~ξεfŒޞ//δΑλχ·Ρ©ο|σžl»^.N, ͺ]‘R~nŸ 6¦μ-^ ΫΪG|²χυΏΫ»·”-ΐ€θ ^]€βς~!TφΎ„“WόΎ,U½}χίj–¦MTοίπsΰ4θ +XΧ‘7cΦnV™:U”gΧΖSw(Έκm·?άŠΑ_-+ L·ψγΰ·``;±΄²•ά΅Yκήe“΄νΊRoκ -ΦΜ>ξeΫ_ΚJό•Φ°bάj~ΊΕϋ ·'£Σ¨tΧQS˜~Ό­³&7‚―Έϊy}Σ…ε·¦Υz¦}ννR?ι3ji¨(γ_ήηΝΏΕΧάr£_ΐ?Δ*<ώΝΟ*d_…K>Μ=uXšΏu?ήΫoΏςόˆΗͺ²€ζ­SF«pπΰT8–ΧW ύWΏTω¦>όΐλ+Έ5bπ‡ŽJ·˜ΰλŒΌ'Έο[|–k‡μ-κ•Ϊ?«LπA²ςŸ€­σ #ξ‡V~π/(Ύ±΅²fΑͺ“_₯ζGΏšψ FγeΗ©©Ό|φκhΌΗšxKίΣ]!Y άVκE²R·H°+Rυ3ΎΡ»2“Ά³[ΥΌΌ=Wœ¦«Nܐ杺y*oθ_ͺδΓζG―φΫ‘ίήPο§|ςΝWΨςͺ“„θν{RΩ«όό―[늜oMo‘fMi•zΦu}eͺσmUJ^‘Ύμ=#νuV*h>–ΏP ΌŸW˜Τ6U·uΊdςό: ‡XαJ«\tν‘žyt4ρύΦι€ΦU©bλYi.‡ΥόΎΈκ‰5SōόΞﲁι€NsΣε“'eνZα‚Ί$ ηΐ’τ[‘Ϋ•*_z0-θ;: {³kΧβ‹T˜Ϋ&•u›–ξ{ΣΓwHΝώQϊφ mh#”_% ό²Ε6©‚cU¬;#•}Β'•ψ€jΥΣWξ=ό³ϊΈΪ«=°Ϊ«=°Ϊ«=°ςzΰ#¬§ΣlPIENDB`‚SocNetV-3.7/src/images/socnetv.ico000066400000000000000000000400561524402210300171130ustar00rootroot00000000000000@> @(@| B*%s…—ΛœέŽ΅,`? ~–ΚΜΜΜ­ι-_4?2¬πΜΜΜΜΜ‡ž4L>ΓΜΜΜΜΜ‘³9$ŸήΜΜΜΜΜ!{Œ^x ψΜΜΛ„5<-&’o§~±N²y99v†zlD@ %q|Ž₯ˆ˜+bNDmWC 8XcN/]G"z€$tw4Q,M3 ‰ΒΛΜΜΌχ$twOd ƒ  } ZX"y˜ΌωΜΜ κ&qj#t}ΛΜΜΜΜͺκ@3\[ 8] P9 Qc'k[ΓόΜΜΜΜ­κ5FŽ₯ΜΜΜΜΜΚ/’utttuv}–²€zzΊ{zrrut±qwt₯™lkkjhhgfΎΜΜΜΜΜΜ*daŒΜΜΜΜΜ ΅ϊ+00 wS&ut3Pˆs“ΜΜΜΜΜΘ.VM5D"’άΜΜΜΚn³zw- ƒ@ { { <„ $t’¦νΜΜΜΜ’Ε33.P-ƒ«Žθ…α h‘ _ya‰9 |  u5ˆ\yg$SVŠΜ¦χœσ„°/Q& w&p  –yy7o&   /a9}› bE|u~ -Mwh„  xhvZ(Š{wt=YŽ  ˜”O Q”’  ŠKGvs ‚&‡  ƒ?xv n}G‰  ƒ%‚ us i™ ;Yh§‘i_4 “l ur@Œ  {  6|„9:~y. |„}9usKwp& rh€\Xz_s )vtDv iz ’†z*    &w‚‰  rq›]₯  zr„—    •}f| !₯Nf]z )s\yoi,    =bmsNd* z_TqJ}/€»8 € { EΆu&|U]xD{l„b K] O<    JN ZE`rYsKe6G%r|‡–$u~'73 FΆ~+  }   w|  2tͺ1 El2G$u}‡–%r|6G/Y(šΧΜΜΜ“ν3T#w{R€  Z35T   Z59\ |]wa,a”κΜΜΜšΧ/Y(‰¬ΜΜΜΜΜsΨk| ‚z    v€   u )puΤΜΜΜΜΜ‰¬ΪΜΜΜΜ̝β }  sr    |k s γΜΜΜΜ̝ږΖΜΜΜΜΜ”Π  u yy   €o  v ”ΠΜΜΜΜΜ–Ζ(knΒύΜΜΜΔώG³s= y    z€  r4yEΈΔώΜΜΜΒύ(kn&oo—Ξ£κ•ΤK―Q Ivtf*.e    e37_ €pzTIHΆ•Τ£κ—Ξ&pn! cW₯sR y {y N‡­!Pp * Y_ z3qn"R9 JF  P@ HB)xE| Xd  Uf dΉ` w |  hΊj"s `Y  Fwz >Z,sv7e&  8V5~y9m7 qlP B° |VŸ  ¨†Y$x¬G   'i  }s@ Dz‰v  p ‚/Š q   #}Lti %q~B€ ,m„Ap  }$Œv ƒiuO\yj }  ~‰*mys– 4b  8zƒŠx4 i  ›ll~ €Œ  …cy`aZ~   Ž* ly T=‰ H ”) /‘8 %ŒNGly } …,mxO€ „S{g.ƒ yk.LMB²3tQ  «‰g! n'   ?` !sŒ₯ j3n*—4"==‰ΉΔΜΑώ‚Ί++-yw:‚A  |   MuHwi$_V”ΫΔΖžί(h`‚ŸΜΜΜΜΜeΐvTqG s $nSoa ‚žιΜΜΜΜ ―κ1X4ŸΪΜΜΜΜ̐δKJIGLFG™d;B<Pˆ421184—>3-4l‚52///0V…ΜΜΜΜΜΜ"z€œΥΜΜΜΜΜ‘άFEDBDFLOyGJP›JOHIM[ SWP¨rVTQQSUXI ΜΜΜΜΜΜ!|‚#y’ΛΜΜΜΚ!n• af?b v*|M "3-¦δΜΜΜΜ ²ν-Y99 }­ςΙ­ρ!xœ# Op ‡ƒ? %dY›ΪΗΚ’β(me=30SP>3 Bt Q7Y: ˆ0 7%/XW.\_<< 8~w€&  2…7˜{£v΅F‘ hŸ»όΜΜΖxΆ ,G: ·φΜΜΜΜΖ(fg(hwΜΜΜΜΜΜ‚/WTΖΜΜΜΜΜ$t{B*‡ΌΜΜΜΜ›Χ5K7E ˜™Σ›Ψˆ¨0W'ό?ψψψψόό?ύηοƒϋƒώ7ϋίΓχοΓοχίίϋΏŸωύύΏύώΏώώόύοοώώχοόχ=ύύοοχχƒοΑοχ€€χχΓοχΓοηΏΏύοώ?χηώώηϋώίύΏώώύΏώΏŸωύίίϋϋοηηχƒχχΓύχοΏwϋΫΓχ―ψψψψψψό?SocNetV-3.7/src/images/socnetv.png000066400000000000000000000200411524402210300171150ustar00rootroot00000000000000‰PNG  IHDR@@ͺiqήbKGD ½§“ pHYsυΘS`tIMEα ‚ί0IDATxΪέ{ytTUΆώ9η5₯ͺR•JR™!„„ D&EΔ =<šJkC# ΨA B ež‘Qž 4*ƒ€Θ„0„„€R©ΉκΞχžίΦε₯iΨΦo­wΦͺUYχήΚ={Ÿ½Ώύν}φΰ7 Œq$ HXΰοά€Z@%ΰ"„Πϋ¨?†(x6ΰy@ϋΗ1γFg­ζΐΔΏ> J/ΔBYFΈUΊ«SαΜs­Ϋwχ=&₯œ–ύ―€1ŽΌώΈEQΐΪ—ΪΏάώϋ‹ρM―—gΗyϋ|wrOŒ=™ŒΦqπ„°ΉΠ„Pτ8…€o6Νo™uθ_…€Φeu‘?,*nσ˜έ£= (,Λ£+c< ύ§«Έ^i’•f”ξlΤ>Œ@€ό°LW@ψΑώ{Š’QQR·κ#(ιήϋ€ΚΜτJ’Α“ύο§t³bΒ ‚3Ϊ=λΎϊJΟλ ΒM…?Ϋ―ΓνžoLΈ&Š"”$ *Šς€”π/ξ@ήxޔπΗ!Qaii©ω Œen½ώRyΆΘ’!―OSΙUˆ&ώΩκjmrr2‡1VhšŒzάS†1Ύ¦cΣώϊ“πω¦Β;vΜ²}ϋφ€Ω³g_`MɁ‘λχρΥί;τIyσ束>}zvyyΉγ8$TεIX Λϊ?.ŽσDϋ[Υεϊo6/LΉU]WM΄Ή$IP’$Θσ}θΠ‘„ώύϋW‹’΅Z­Όcǎ΄φνΫ;“’’BA`‚ p[ ,Λ@–eδ+ύ.qπφƒχ›ϋΝρω?ώlύΉXΑ2œΨ4 b°™Έ, !‹"‚ήU€ ˆ¦i™¦ieϞ=-rss}ϊτ©η8Ž0J}}½&22’KHH` ‚B ,Λ)))Μ‰'bνv;k2™$­V«€¦¦^8rδHΜ₯K—,C† ©c†Δ`EέEK³l–€ψ!XA†³ΊϋΓ%B@κγWζ{οΥ%Εϋ.XΣΣ}ϋφm°Z­’ γ8΄|ωςΤΑƒί|ύυΧλ#""dQaCCύΩgŸe̜9σbBBOQφω|€Ωl– ±±‘ŠŽŽΎΈuλΦ€©S§^eY­VιωηŸχ¬Z΅*ωΠ‘C±cƌ©bYY­VΡνvSΫ·oO¨ΨodΫά θώe~v#—:`HυC@†SΪfΗ Ÿ<}ΐυΊ!γΰω­ @ˆ„ΰj―œΪ7Wl?—Β?~wςςς†!Β+Œ(ŠΒΗ‘ͺπ‡΄X,’ͺ«Υ*J’έn·–ηy„1†@€πω|δΠ‘CkvμΨ7wξ܌φνΫ»KKK£L&“0|ψπκ΄΄4φΠm=ΏΜϋ¬Sλ ‡ an€Ϊ¦±c~Ξ}n@γC`…γU œ’(ΒoΏZ™΄wέϊΜ”Žλ΄Ρ7ήx£†γ8h0A Γα ίzλ­III–-[ϊ‡ R›œœΜ‰’ƒΑ ΆŠšάάάΗqΘl6KM-ΐνvSZ­VΩ½{wLee₯ρ­·ήΊE’€BQΎqγ†nέΊu-‡Ύ‘‘AΏuλΦγ,Λ±±±‚,ːeYτιά9±z VVTDN›ω™(KŒπ($Š|€’„ gα΅»]°όe\ΡΥ‰'Άε8–a‚  Λ2())Ι*...}ξΉηΌϊΥ«W·…BdvvΆ7//―ΑνvkΣΣ‡ΓAΫl6ΡιtRV«UEΉ\.h4Κ‚ ΐ:ψφνΫ—ΐ²,Ϊ³gOόεΛ—Ν6›ϋ裏ʣ££Ε~ψΑRPPΠnΦ¬YC‘‘ΊΧν£ώϊΦΛ—/oiаHΚŠΘίJ£‚ΐΩΩΩή#GŽDuλΦΝSWWG—””dOž<Ή,333Θ„„~Μ9e<Ο£7Ə5ͺ³^―ΛΛΛ ­[·…B!H„`Yi4…aΔ²,qϋφmν;w &Lx*??ϊ˜1cnR…ΐγρνΫ·DDD\7n\‡©S§^$:t(ΊsηΞ¦άΰ·ΠΒG’("Y–αψΗΪ}ϋφΕ{½^²ΈΈΈν'Ÿ|Rf·Ϋy„0›Ν’Ωl–E>Ÿ|ν΅Χv»=΄dΙ’sί}χ]μδΙ“³–/_ήό~? I·ΫM-]Ί΄ε΄iӲΝ;gΩ°aΓI‹ΕΒχξέΫ­ b†Νf;uκδ裏.Ά«««Σœ;w.jΰΐŽ„?²`ŒEQŠ,ˌF£8i€φ‹-:w/ͺ{<c hšVNœ8™ššκˆˆς—Ώά2 ςργΗ-³gΟnΝ²,!a±XψԎ=ϊMΣΨησ‘ΡΡΡάυλΧuqqq|LΜ―ώ,ITσ„ψψx¨¨θβ„ r³³³]‚ @unέ0Ζ€ηy$Ir»έΤνΫ·΅‡CŸ””€@ @(ŠΒ+ŒF£F}΄uλΦ”·ί~ϋV0$hšV|>yςδΙ(Š’”Ν;7ΆkΧ΍1GaY–P™ήπαΓ«/^œN’$ζy)Ё!›L&Ω`0(ΡΡΡB\\\°¦¦&’²²R/Β&E<ΤΤdƒeY„1†·oίΦ^½:}ι₯₯ΕΕΕ­%I‚a „^―—³8μp8(ŸΟGΡ4­P…wξάi/++3 iΨ°aΥΩΩΩ‘¦QΰτιΣζyσζ₯ͺW―^Žή½{»dY†.—‹R£„Νf%I‚.—‹δy]»vMo±X„ΒΒΒ+“'Onϋβ‹/ΦΚ² EQDEa„ΠCαaPQ ςυ`0HŒ1β™ΈΈΈΠ²eΛ~ζ8]Ύ|Ω°{χξψ &\S_(Š"δ8ƒAbƌYA(z½^κ₯Kγοϋz‚ °Οη#-‹Δ²,"I« §( ΰ8mΫΆ-ξΔ‰Ρ~ΏŸΆZ­άθΡ£«’££š¦q0$ ƒ,Š"œ9sfλ1cΖT₯€€p@€?~όSΑ`ZΊtι9ƒΑ h΅Z!τΐšω(UœΖΖFκΔ‰―Χ«Y»vνI„ΠλυJΫΆmCλΦ­ΣήΉsGc³Ω–e Œ1ΨΉsgά•+W"kkk kΦ¬9•ΐ#„€(Š!t©u:’‚›ϊ ωωω΅tx½^rψπαέfΜ©ΙΙΙqΏώϊλwL&“ ‰ΊΊ: ΗqDDD„μv»II’Π€I“Όωζ›]ωΟΖ8ЁΒ+¬EEE―>Hxžηсl§NŠŽ‰‰azτθΡ¨( Ι² 1Ζς™3g,Z­V^·n]‹'NDuξάΩνχϋΙ‘#G^‹‹‹γυz½‚ Š’°N§S|>©Υj•pRtχ; z½^aYEEEI6›-HΣ΄œ••ψόσΟS=jΣιtβώύϋγ^xαGjj*CΣ4P±’¦¦†v»έtuu΅6+++!ͺπχKέΡΓ„_·n]bUU•±¨¨θ²$Iˆ$I¬£Ηγ!ΛΚΚΜΫΆmkuμΨ±θqγΖ]›:uκ•.]Ίxέn·&777)y<RU€ϊ5Z¨ ~«V`΅Z%‚ pηΝ}ζ.]Ίx.\xaϊτι—Οœ9υύχί'Ÿ8qΒζρx¨P(„hΗC*Š?όπΓJΗ£YΉre Ηqˆγ8$Λς}«Kds%,AΰκΥ«“yžGοΏ~Οσˆ$IΕλυR7nάΠ}ϋν·v‚ πΠ‘C«m6Ÿ””ΔX,QQψυΧ_ΗvκΤ©1 !«Υͺ¨+ΠTh„Π]aU0τω|€’(@u ³Ω,‘$©δδδΈΏύφ[[^^žBΏ€€€0sζΜiΕqΩ³gΟϊ§Ÿ~ΪΛq)=zτ5kΦ$ϝ;7mμΨ±Χ1Ζ@«Υ*χΊϊ—š}ΠG†B! …ΠΒ… SBxΤ¨Q7έn7ισωȊŠ Λ²eΛR+++# Λ'MšT‘‘‘Α>Όfοή½‰” °΄΄Τ:tθΠ:ΥδTΑΥV)¬(АeYΤΤ T₯X, !’’’€W^yΕqδΘ‘XAΫν&Ϟ=υK/9{τθα]Ίtι/«V­*e†œ1cFVYYYT]]F–eψη?ωvLL WRR: Γ "šZ ;gŒoΛ|΅+ƒhpx«‰½θO~ρΏ*‡Z»sηNϋωση-!ά’E Œ3.λt:EuΥjμv;SWW§EΩl6^B·¦FY]uŠ’p8Ζ“!¬( 4R("$I‚f³Y²ΫνBLL wξά9“(Š(>>žQ€&[ƒλΫ·oÌ3Ϊ|ρΕ) Й™™Ύ?όαwφξέ«Μ> ΧΣΞ†Ϋ―“c’B†Α―U ϊhnά1sό‰˜Ισ»θ%|!8‚/ϋv¬i]κzτθαμέ»·K«Υ*mKJJ.FY£ΡάU£$I°Žž2eJΆ’(pΜ9΄Z­b4ε¦&ZΥ<©ηζ’‘ –UUUΊωσηg‚€ζΝ{!22R $MӊF£QxžGn·›š5kV«όγeA€οΎϋΞΊgϞtγtά°ΓΏ$jε‘ qΓ΄q'I~χή΄¦Β€VΖΰi‡3βΕ5—λt:…¦i% $IbF£¨±\5%„Πj΅ EQ²j4M㦀§*A­4<`ιυzΕγρ$IbEQ šWπ<ŒF£$²X,‚V«UT.‘ΊΗqˆeY΅‚ !„ψωηŸχtλΦΝϋΟΎΉiM…½„!Ώ{o ΌΎϋ– @#Λ2 sl$β8ŽPbŒοšκ’(4M+ͺί«,Lάl6K*—oŠ n·›$ ω^₯ψ|>’’(…ηyV0&IR‘eͺΦ₯Z‘F£QΤ2»Σι€"##₯pρ!Ÿώer―OC*­Rέχ+yUι4ωωσΣςσσo©V€‘Pq@―Χ+’(Bƒ„ίο§5¬ϊf  ¬VλέΥ—eFDDΘͺΥ¨α9σW•Ζσ©wοގV­Z>lkΡ’c³Ω$ŽγMӘ$I,€( K’UrδρxH•±,‹B@Θ0 Α0 R³ΐM›6%vοήέ•žž8pΰ@lǎ}4M+‚ —ΛEωύ~’aβδΙ“ΆλΧ―λvοޝΠΠΠ ωλ_z³†$8GyilΌ_Έφ―fΗyŸ^°ςG²u‡^Ϋα³»Ξ›’ οΤλql4“7rbωΤβY™–„ցiΣ¦]…BΔΑƒ£.\Έ]TT”•ŸŸ3;;;¨( Pύ²²lY°`AFiiiμΤ©SΟ©[b}ϊτqΚ5+³gϞ.žη„PiJ>ΤdHUΓ0Θb±HjhUθυz₯ަ( Ÿ={6jκΤ©—yžGA`»έΞ-Z΄¨νΩ³gλ† v«S§N~Qa8A"%IB[Άl‰gY–xπΝ&Cjβ!}ϋφuξΪ΅+¬¬ΜθΎ}ϋ6P…/$&&†B‘dY–ΨΉsgΒ©S§¬ΉΉΉήkΧL&“!ϋύ~Βl6«¨MY, BΤH§Σ)Š’ΗC†B!BxΧ΅~όρG Λ²(555΄dΙ’΄C‡E›Νf1...ψκ«―ήΙΝΝ @‘ ( 'NœˆΊvνš^«Υ*|πΑυ‡U†X ;ΆjεΚ•)—ΛESΕk΅ZEEΈqγΖΣ¦M»l³ΩDΈ~ύΊvρβΕιG3wξά¬Ω³gŸŒŒC‘Α²,²X,’(Š(!D·ΫMšL&YTY–A]]†¦i%\TM‰‰a7oޜτξ»οVΕΖΖ $Iβςςrύ‚ Zεδδ\ ƒ„ H₯λO=υ”gδȑՏ₯,ςρχή{οζργΗγΧ―_Ÿψ<Εσ<’( oέΊ5aǎ‰N§“=zτυΘ‚‚‚Š€€$Ξησ‘:N‘( CAcc#e·ΫΩuλΦ΅hέΊ΅ρβΕi;vτ0 ƒϊυλΧ@’$Ύ_ώW…M&“ !ύϋχo0RqqqΓ0dzz:γrΉ¨˜˜!Μά°šθt:–eY"22R=zτu³Ω,9rΔR\\œΕq1hΠ jŒ1όζ›oβ)ŠR~χ»ίέ5jΤ­0»ΓΗ‘III\”±Κ$5b4εττt&Π‹-Κxωε—k»vνκ=sζLΤΫΐY–aΏ~ύccc™`0H©βy™Νf)""BV›ό~?ρβ‹/Φ}ώωηΙ$IβΖΖFͺS§Nώ?ώψJBBBhΑ‚ΩΛ–-ˌŽŽζήyηͺœœœΛ²ˆeY΄vνΪδήV»Cτz½Μ²,"»έn²±±‘ ƒD  - ί«W/χOτ›w† ƒ,Λ2¨――Χ^œ8qb»ΒΒΒ2»έ.ψ|>2**Jt»έ€Ρh”΅Z-~ξΉηά»wοN”$©ζΤ©S‘GAα1cΖTMŸ>ύͺίο'œN'½bŊ–>ŸξΦ­›σΩgŸu]ΉrΕ2zτθ›jEΨh4ΚjB8…†ΕΕΕY~ψaΩ²eΛ›«ψ|hŸœŠΣ]kΈΎ+{GM±"‘CAxȐ!·;tθpCυΣΙ“'gsGtκΤΙμͺ»?ΨιtΡΡΡΓ0ˆ$I¬ξ"aŒAΈ¨ŠTό$I!?~άΊgϞΔβββKV«UTVUUι.\˜ΞΊk"ϊώ>½£#€=°ηtΚiΧΪΟοΥhtBF7ďtmLΛεΫsξ½ξ0Jδώ½_·jέΡί”uω|>R―ΧΛEEEY­Z΅ς4¨.ΜΔ[·nι6nܘ2qβΔr’$q0$"""δ0ΈΑ₯K—¦ζεεΥ₯¦¦2jϋέΡ£G£~ϊ駘βββKjŽξƒcxκ³IO΅[ϋuΦ½σ“ήM‹Ώνχ‡wΤ'τ ~=f¬pCƒώ~Χ£­š?;‡ŠΙt«έ^jηΖ‚€:ΏeΛ–Œ¬¬¬Fšώ΅IώΤ©Sq‚ !Πj΅2Θ² !„ψτιΣφp·A„rι%›^―³²²ά+V¬h©*@U8„k―UYΪέάxgΓΓΪο+IΐΕ’FšƒώνhIMœ‘+Z΄ϊ€=&™k4eY†Ÿ~ϊi*„ΌύφΫ·B‘ρσΟ?ίΉtι’iĈ·cbb―ΧK’$‰Ώψβ‹„^½z9rssύςζΝ›,‹0eΚ”ŠρϊοV%φd‘]'*2ΕΪ8#—σΪ‡(ΰ" 0:ίάyzρόσ™υMgΰΣS2ρ§‹ρφBάοφ{εƒ>¨2β‚ R ƒά­[7OEE…Y§ΣΙ.—‹ιtŠ,Λπ—_~±φμΩΣ!ώωηΙΗS§N-'I7χ„θ7r΅ͺa/•ϋtδ]_Ώc7rpά¨–ι9‘‡΄Κza“fιqΝB% φ–ΌίUTFƒAŒκχςνήυΞ­Gν—enΪ΄)ώβΕ‹‘ϋί+7nܘΛ½ςΚ+ Š’ΐcǎY***ŒoΌρFυβΕ‹Sνv;.=r8ϋaηκDηOΗbV'gΌϊ»κœN½=ωΙ|alβ»ο€Η|>θ^%μή½;ϊΰΑƒφ>ψ βγ?Ξ™9sζ˜>}z›)S¦\™7o^ZΫΆm}ωωω΅OΰœΐΏ5Jίνor`’θqŸΈW G΅lΪ΄©…Vpk2·VΛqd90”ΑΡ·Ο‹u―½φZΓ^©&ΰ}ŽΜδ?±7«ξ0εέv‘‹Wη&%0ৎIξ‘ϋO3*Κ.€';6@έ7ίΨϋ€ήŒ’Δ#Ν•© ―B·s·­ίMy―Γ~oSαο› Aw=I%œ=Ί':ι†Σxί›U7ΜOXψ]” †άφ—Η:2Ϊwχ4Ztχ7s‹…{B>Ώα~Β?0›JΡƒ8Β2βb“ωΠ€ͺ”{˜Umœ‘ΝωήεǍφaΐ;Φ¬œX x¬‡§‘G;>όK.8φS"5 %Ρ›φήί/tyyhέcόxϊ>Šψ?w|ώ!fκ₯6š;ΰIENDB`‚SocNetV-3.7/src/images/socnetv.xpm000066400000000000000000000036251524402210300171460ustar00rootroot00000000000000/* XPM */ static char * socnetv_xpm[] = { "32 32 49 1", " c None", ". c #B40B00", "+ c #B50A00", "@ c #AB0E00", "# c #CC0100", "$ c #B00C00", "% c #CE0100", "& c #D10000", "* c #941700", "= c #A61000", "- c #9D1300", "; c #A21200", "> c #B10C00", ", c #CD0100", "' c #000000", ") c #950F00", "! c #CF0000", "~ c #CE0000", "{ c #A71000", "] c #B30B00", "^ c #D30000", "/ c #4A1100", "( c #850D00", "_ c #9D0C00", ": c #761200", "< c #7C0F00", "[ c #8D1000", "} c #B60A00", "| c #A40C00", "1 c #A20C00", "2 c #A11200", "3 c #D00000", "4 c #C20400", "5 c #5F1400", "6 c #4B1400", "7 c #6C1100", "8 c #671500", "9 c #D20000", "0 c #671300", "a c #8D0E00", "b c #9D1400", "c c #CD0000", "d c #930F00", "e c #A60C00", "f c #3A1500", "g c #CB0200", "h c #9D1500", "i c #B20B00", "j c #B20C00", " ", " .+ ", " @##$ ", " %&* ", " ", " = -; ", " >,& ' )!~{ ", " ]#^/ (!~= ", " _: ' <[ ", " ", " ", " ", " ' ", " ' ", " ", " }| 1} ", "}#! !#}", "234 432", " ", " ' ", " ", " ", " ", " ", " 56 78 ", " ;&90 a3&b ", " >#cd ' ' e,~{ ", " $@ @= ", " f ", " g9h ", " =,#i ", " j+ "}; SocNetV-3.7/src/images/socnetv_logo_trans_128px.svg000066400000000000000000000174161524402210300223350ustar00rootroot00000000000000 SocNetV-3.7/src/images/socnetv_logo_trans_64px.svg000066400000000000000000000177031524402210300222530ustar00rootroot00000000000000 SocNetV-3.7/src/images/socnetv_logo_white_bg_128px.svg000066400000000000000000000073621524402210300227750ustar00rootroot00000000000000 SocNetV-3.7/src/images/star.png000066400000000000000000000012761524402210300164160ustar00rootroot00000000000000‰PNG  IHDR5βήξbKGD ½§“ pHYs  šœtIMEΰ #²sΚtEXtCommentCreated with GIMPW&IDATHΗΕ•½NA…ΏcYΆ°EαE’+ΗHQ₯Β’ΑMSa r›ΚJ“2οΰ₯ΘP"EΗψΫλ½if­e=‹W)’•ŽφG3»ίΞ½ηŒςŸ»]ψp|¬ςŒWy_άh4ΰφφ6Χ‹užA—ηηr2™ΠšNι΄ΫωHDd£Œ1" bŒ‘'GG/‹gŒ‘δI)yTJ&–<΄”1Q”ψ³Qν‹β΅šM}Ÿ…R ΄F%H¬θT⬳hΣνSί‹ςKk™₯hΣzTJn gΎh·V³Ι§z«j•ν(ΪθͺοΦi]1ΖΘE­&²°έ!―¨S«9Mγ½Fδ[όέ‘]™π½P ²‹‹•l­δυηΉ3Δ• ΑΎ SocNetV-3.7/src/images/subgraphline.png000066400000000000000000000002151524402210300201200ustar00rootroot00000000000000‰PNG  IHDR szzτTIDATX…ν“I Δό§λ΅ŠJ7ΤCs‘jkπ’rΈ¨%"ΒΛΞ0ά(ήuΌiαΉs:\Ζ{ί"]φšLΗ³T₯;₯μ<₯ΐ#ΈH7@5μIENDB`‚SocNetV-3.7/src/images/subgraphstar_128px.svg000066400000000000000000000036261524402210300211300ustar00rootroot00000000000000 SocNetV-3.7/src/images/svg.png000066400000000000000000000103031524402210300162330ustar00rootroot00000000000000‰PNG  IHDR@@ͺiqήŠIDATxμ•MjA…ƒ!q^ε&7Ι"§ 8λΰ/0δ&›€AKύX²eΩR,βΡθG£8•zE?Shΐy6<Ί[ΣΣ]ί«ξ‹ηηΡηωΩR½Tm«^o˜ΆCμ[kΓομμΌΥvWυ^υaΓ„˜wΑ° ―TοdΓ0ΛΟΥG,cιu»ουΌdΠο{Ιp0Θj8”Ρ4„²λfφF<cˆ1γXΦ5ΰΑ’C]Τ@GlƒΖPz<Y?΄‘ρΨ”@ψέ οΌΒ7XϋaOΏΖ4& 6ΞEr4f<`x²ζrpή2Γ–A=ΐyΨ$1MWfΦΑšFCj΅š”ŠEΫߌIΌ£Œcΐίς5€ΰC‚{1£«ΠΪ'œ'“ζb]μ[*•€ER­Ve@Ό%%ΔΕ1Dcr3‹q“1΅N Ϊ)₯γΗΔΜcύθφVZ­–4――₯R.KνμŒ5…§‘F0σŒ1 |εž&<ͺμ‚ΰΣ©LU3HΗh§Τl&3 c—υv»-­fSzqμ !‹0ƌƒfΡ #ζεh€+Vž°ϋ„τΠ„kίδΰ'ό—fϋκκΚͺ7λI?Tw\νkΐiˆ‹hn˜³ίˆΐ8ΪΜ8 !fΩ›ζsšo ΄©ΩΧλ+8Ž6ΪnΘ6φ±΅W  i Ζ&τYr7ΐSΜ6E0 °Μ΄Ÿ4./εόό\’(’ίŽχš°vuάχ4Μ 2`Γ<Ύσ§±"ζά Hό†Μ|6PΚΐΑ@‘‚_\\H|w'um‹Ε’eΥ½θ°Ώƒ` λΐqΠrOo¬?™«’gΰύ½Π‹‚Ή€²·'ϋϋϋR( ΩΆ5ρ€ IΣ’qx‡+λ€ƒύΌΑώšypg~°ͺC¬β:ΐΩ‡Π ‚?>>–ƒƒ9<<ΔΨΦ"πrΉ4₯*όΖ¬Ζ:/Őλ(ξΗ=}<θΓΐdΠ—οG?erŸ“Μ6ε‹΄}7λJ₯"'''ΎΉ1pό•α=ΐ(0΄Τ> °&ξlGkΑ^^ Όƒ ŽyMGͺ’ΏχR-—εۏ‚|>*ΛΧΊΘ\r6`Jp*αΑkzδOOO P„†–GΖp·# “Μ`?_eπlUΆ§Άx§ΤRϋΧΎ•W]]a‚:Υ*­KΥ™ŽΩm;¨΄U@A@”EEA­JΫΪͺE±J‹,EΩDˆ’ =d!$yYςςbHP 1Λ[^ήΛN–·=ηΞ;Μν3Xf|„ώΡ;σ͏yIήο~ί=χ,η^Ύ>ƒΔά"¬J.ΖκΚNl­ `σ9֟υ„Q€Πx™ΌΰPVM+ΐ‰ ό½Ό„ƒ&KΔ}>ΈZi›ΝΖΛί₯>—-!b)²ς@πy`2™°?§+’Kπ“ώΨrή+ΐƚ–-„?Ftβ qR\¬0Μf³"Έ1`^>‘@ΐ"θΔY$ήό}LP‰# lΛπ ΐ±œ«2ήΫΌΪlΊ‚±™½5―¬ϊŽ/εΒΣιD“Γ‘~Oˆϋ‰΄@„ΰai¨Ηg©xσ°ΛN°ͺΚ‹N{°ΆΪ‹ughoγΕaK-°΄ΐŠΘ4L•beb©αΐb±@κrE>X«³γκΡΌ΅¬ @8…,Ε€ηR―²ŽK’ηQ{ώΦ&žΔ|XA{Έάε&7V~εΖͺJVWy°†E8 ,N2!φ„v‹€_Bδ•€ΣU£ΡˆββbήΓΌdε%¦‹A+`!€κ_ύ<ά.υ3&«CMΎ£­λγ³°0Ν‚χKάψ Ψ…%₯.ό³Μ₯ε.,#¬¬τb©©6 ε$97 Ύΰ»4ς–E€πϋ~ςκ3yΩσΝSΛ„ψwμΣΰΑπ§;;ΗNœΒΛqXTθΑ’ό^,.μΕ{E½$‘-!ŒnΌ“ί†e‡N‘¨¬δ}@peΠσ|φήzR£C<=o€Κ{ξP„E΅r=]X²; ―Έ€Ώδtα­“έx;―‹ zπNa“oŸΊ€œδΊA'~υ`Η§;=!.δ9bλπ²wίΝhI›s£r°0Λ…Χ³Ίπη,!· oζE0x±0Φ€Ό’R@ϋ[yΟU€WžΝ[©>u1ψίLό«»ξRO!ΐNkΞN€uγΥΤNΌ–q―g*πYΒkι­ψxόξ^Ι}Z› _Δ‡ŸΊΊι;^}ώL7έqΔz:Ϋ0{k6^JκΔόδΌ’?€uba:‰@[anB ROκ«.Δ/Χό% \Y$meθ`Λΰ°(Ÿ”ί|³zςψΣ–/ρΒ±.Όψe^:֎ωI$B²ΟǝCΕrΏ_9H"πεώ•€γ>G‚‹‘†_¨½X/u~ΖoC©9˜y s[πόαVόžD˜{΄σŽ·c΁FΪοeI^ίχŒΎV_Ÿƒ.#άΈxσκ‡šωΡσ‚€²λ'Φdc πl<‰Π %ΒΒρ.DΖ§θf/U ?Υ#0+Κ€'χ80=ց™ϋœxj_fμΕ”]_#6ρœ·Βωό‘Ώ³―U—οβτtΑGΒΙJ3Bθ·Ί²Z‡†-„![¨0ο²`ςξ&Lύi{μxrw=¦FŸΓθe)θιh…ή½ΈΊZ#D7qiΒT›;pΪ܎ӍνrΎ ΌHζ^'¨ΰ±†tpΩ D$ ‚ΉΟ'>³cn&G7`Jt=¦ο΅c~d&Θ–ιΣΓ»4β;Ε$²±Ϊ ψά(―kΥΟ τsHvΪαυό…¬¬LF Η_Άρ 0<Ζ…‰;mxœ,aβŽZ²+¦Ε4γ­θl@Oq΅lRš‘ς./™»σ «=”iΪΫ»ΙΪ`iξS"…Ξ`•*'a@Ÿ˜ΘJqϐ_Μτ‰|aθη=»£γΆΥcBT&}fΓ”/œxuΫ  Τ™ͺ³A^Uψ=Θ8U[R±dϋ!Ό»' »RΠΫΥΙΔυ“b94Υ/K„W9Ρ‘Lαh&Κ?χx…ΘΏCՊG©W5šwγ£,x<ΪN"Ψ1fy&πΑsΡ™ΊTwΧI+Μ½w[+φ%`Μ*¦ΗQ”z R:± £ 6ΔγB{›"­Zƒ€Ω‘ς Ÿ6…Υ°rD¦›g($αQM ΩΩ‰G77bΜ63‹²bό'6L$Ζξ΄"#'_™7 ΠΠά9΅βͺF΄Pγεθ±TLίg₯|2Η£­˜K˜wΌ/^IοΖ†½GΤvS„ ”†3i>mbp;^τtXk’^2-υ8M ŽlΑˆΝfŒΪbΑθmV<ΆCα_9pΪ-pΣκWZ:Ÿ ₯5eϊO­KΖ³ -x.±ΟjΖ GZπβΡΜ=Φ’„˜΅1­-ά¬Q°ΪlάlεR\υ*λλλE€ Χ`τƒΆ]A¨l,/Βπ xd“n%"-J„q,Β§ŒY‘…œΌΈ<^œ±w£Φڌ¨˜Γ˜ΊΗŒg8ρΜA'eNΒIB8I'^<Œ©ŸqφL₯ζ#7&ΝΫ‡[τ| ωu‡_ϋΑy€^fΚVΠΟ τ£+ œψ۞SΊήͺDΑ"%Œ"ΖE˜@bτφFŒ0³ΧΗΈεɘL‰ΣŒΨ&Κ›πτ~fνo"!(sdΔ7‘LΪ^†*S™ζ“f>‡δ ·ξΤ–HHHˆ&w λΉ€4>ψ%‘?‘|ŠIs[3ξyΧ€‘λ0|½²Œά΄„νVŒ%Η8.ʌ ;(Tš)Lr”°SDΨΫDBΨ13Ɗ1fz6੘z<Χ€i+r#• σœκY €§§σ [€‰ζαΗL&,Θ“!͍}y‚πX•ˆ!k¬ΊΆΓX„ΉΩL‘ΑŒ1‘ŒzŒ£P9q—•’„•' AΩ#αs ¦μ#˩Ĉυ₯ΪRkσ±lυdfd ƒΝ‡°L\…ΐΒ‚‚jšϋPΒO Γ&@¨β€T–Ÿ‰₯$€€ίλΖτχφβ·k­x((ΒΓΝ΄%0rγ·΅•’Δv3Ζq” θ „ψԊIŸZ ΄}*ρίG%›•"Kjj-Ν{α1ύ°  CEœ£8H7 0p άrΆΨՎ'οŐΥ(>&6(k Υmδ-A‘‚‹[b§vΡ3κ[ ϋ¨ΠZkβk€Fαά‘ž|A^n•ζ<–p›Ώ’θŸλŸ±έJΏ]ΪξηΏ·)χώέ„ΧZH^]‚·Δ3Y‚™B₯™­²GΆΚ ·Χ`Θrθδ₯Bεj’ΙŠ 'ΝχqΒν:ω~±9ΦΦ/BΉ”€žzΙπϋQn,ΗΜ%qοϋxh½’„™,œγ&’‘’„‚Β₯œζyάχς&Έy”;h“#""ξ€η΅B |pΕ%—#„L_ΠΛx˜n½U=εϊŒ~‘ ͺ²+vŌ₯‡ρΐ›_bπ’Έq6~³( ->ˆΧWΗβ‹˜8δεd@Ÿδ₯₯všη$|ΈθϋϊΫχˆΈΑ£ώΆA₯C¬DZl~―WZ›,°5Τ’ΩnA{k³Ί;x¦ΊJy{‘δMF£ζ8‘Θίnς—Ύ"#Φp K`ςκpt>α―Uκ |·eŐ:^*:a\Θpjk!ς|•Ž/IgPlηΡ‘‘//+k`‡Gδoςύ#@_·@ι© $νgτ)Cr©‘ ζ˜ Ÿ@sIΜWκ*ιHώDf&xp±ΣPW‡²’’s4·Q„Ÿ‰Γλ_‘[γ2Ώ—.Žά g8»δ’†/IŸ§τΆ’Όyyy*0yCaαišΧ#„[…όΥ O«Π·€ΒΌΎΠ-@:8\Φr)ΛN=‘εάΎ€΄Tέ j!ar³³Kƒή-’αυ‹έ’ε]¦€Kƒ›>λωΞ}>ύJ«Ί ν³Άs²³Uέ‘tόxΝηw„›…| ΐΰ»L!Ύ»θ7@7}³ωΛΚΤ…Œ΄΄΄tšΛ=·ο/†Aς@BΗeŠ δε{δ‰χσ―γ—κyώήϋχΗΡ<ξ'ό€?Ισψα—Έ:ƒ-‘nΑ‚‹x„›„~ΧάpΓ wΥK˜ΠO qχSŒ9·³yΧ ƒΨϋ71q s+#’_ΠΏγγίθΞΉ μξ₯IENDB`‚SocNetV-3.7/src/images/sw.png000066400000000000000000000102531524402210300160710ustar00rootroot00000000000000‰PNG  IHDR szzτsRGBΞιbKGDΩMM ‰…ι pHYs  šœtIMEΩ Žu+IDATX  ίούόϋώώώNbNFiOόϋόœπμπmΑ±ΑϋύύAQA §Kόόό΅φςφ»ͺ»ΊϊόiOX8ύ«s· ωύ…ςϋOΦύKK ΘσόOjyjjyj ά”σωσϊψόωύόώ ΦύƒjyjjyjF f2ν"†Γ;Φν·ΞθώώA:6) %Ϋξψϊύό}<Q'z°2mΥσοσΞΊ©Ί~έιJ]J ? ϊθ*ιΛͺ‘Λό•κώίοωύώN%r m-όεωψξγργκΥ(ήμυ ςψοξκξ£ψφψΐ υνζ%χγϊ'Vx9 a ²TPšΣιό’^΅ρ©ϋ/'E†σϋϋ’™γγ~ωύ  D ^3YτΑγϋ|wΏσ²ρυϋJυϋξψώΗ…Θς¬τψυΡππΨ2~qDφώώA™ηηεωψφπ ωœ ! 8@σΌψωόϋό8σψΥκΖΩνϋοςλθλ΄ννˆΉ ’t?ώϊχολθΣκϊίβπ½υω/υϋ"ςωΕδϊΣωυστχϋoΐΐK© –t§tόϋχτσΤμύΖσωϊύπψπψΏγσζώύύoΚΚ})&&£π/œόϊ³έρΪςωςωAQA?Μ ο H"*ΊχυχΑοκο’ΗΉΗ³ύ[³³ˆΙ^^ ΖΩΩ<πρρpκάψψ”΄ννœι?#οχ_ο ?ώΌυώό³PP$4**ί††ύ7’’|š@@dκκύγ Υ)%9Ι πφϊ0<9ΠΔΠΉ ShSυjyj–‡–5ό8δπι π  φόόσ ²ςρεε΅ Ζάάγ%"Ήιιύρρ²ρφφξ  ήφσφΖ·₯·oζοώυφ ϋύύςωωπρφρρYωυώώ€0--ϋ]γύστρρu¬σ¦χ jyg–‡– :ώόϋόΆJ–‡–Δϊϊζ~Ί»dl&%ύύύέ&ωΠηηξεεκl@@NX$φχύύχϋϋeώΰξξqύχώDUDL hEψζψωίψφιφΎςϋς° 6 τUIόϋόΆςος€tοι ψΰζνϋΑϊΪξυςυΘΈ¦Έkζο  " *^Rς;ψψυωΒά±δ\ ε ώˆP#ΗέK$ώm /τρτΙκδκͺΟΓΟ΄μσ  & 8Q I1Vjyj–‡–ί¨χ.Σϋψσόμ ΪσΙ1 ωυποτωόύωυΪυΘ‘Ί#?χϋώ όωωώτλϊΪτύλχγχχχφϋώ J]J• ?σοσΞΊ©ΊfΥύό όψϊόώύύωώϋόπμπ  @τρτΙηαη΅ χωσϊϋώ ϊφχωϊόόύώώώϊϋόώώώ΅Jτ‚ΙœPIENDB`‚SocNetV-3.7/src/images/symmetrize.png000066400000000000000000000015041524402210300176470ustar00rootroot00000000000000‰PNG  IHDR @ΘsBIT|dˆ pHYsώώOΒ’ώtEXtSoftwarewww.inkscape.org›ξ<ΑIDATH‰΅—ίKSaΗ?θ’(6‘εΟˆΚ؍ΫnR$ˆ _ΧA%TWώB#F»Pϊ'$ZXD₯ b1P#Fκ&θ4›θ‰Ε§ 7ΠΉΣ™gΗΞα}Ύηύœχ<Οϋγ(α$¬΅U=v:ι±Ϋ©Ψέegi‰_±ΕE™@DLu Δηc4fS„?=$ιvΣ/"ζƒ;:xΎ°ΐV.4냃$ρ™=ZKo/ί΄ YοξfΒbrj›:;)ΧUWc7¬ š ώ19IJO΄²ΒΊ©`Ω‹DxŸH°§₯!=?Ο‹“˜NV―—±h”tnQ…B$].ό"‚2cQJωAYΜΆ΅΅©Ύϊz——γΨήfgu•Τ܁DBήŁ•R-ΐP"" ’<kΠjΰ)p Έ―₯Λ- l―”ˆ|ΞσbgDδ―ζΣ ΘάΚάŸΞ‰΅ βΏ}Ω€»dκγ@ϋ) ό‚ΊύίJσ΄_naΐn °e7³š«@3p­> šNJ©πψ*"yβΝ™—›Φν,cΊU­”rχ€2`ΨΘ‰_Ξ‹Θ—B‘€ώ§>Φ<±2ΐghfθ@+gyΪ›s@·¨&˜œ©’³γΐ £ΠCΕUW§Ίššθ«¬Δa³QΊΆF*g|fF99}ΌΜ€ΰ‰ˆ,+·sμrα…HJΞ6’φz¬Ω/LŠ­ˆ@C]ΓΓG‘YΗΩjoηU|p θιaB šυGψXΜfέRU…]/ΰŠ‘\jΨqΞ\¦ώλX–—YΧMM±Ζώͺe8‘ΐ?4DZK³‰πFD4OކLDp»ιVv8Μ¦ΟΗ(ϋg*Σ λΠR[«SocNetV-3.7/src/images/symmetry-edge.png000066400000000000000000000002051524402210300202270ustar00rootroot00000000000000‰PNG  IHDR szzτLIDATX…νΣA DQοιiŸ¨›ΤA›Μž6{k @Ψ_UΑΓn†²Η¦Š.§βέ4Hη¬ώ‚» ;@YΤπ—ΰ‡yNΌ“UIENDB`‚SocNetV-3.7/src/images/symmetry_48px.svg000066400000000000000000000042071524402210300202310ustar00rootroot00000000000000 SocNetV-3.7/src/images/system_update_alt_48px.svg000066400000000000000000000004021524402210300220570ustar00rootroot00000000000000SocNetV-3.7/src/images/text_edit_48px.svg000066400000000000000000000002711524402210300203260ustar00rootroot00000000000000SocNetV-3.7/src/images/tip_24px.svg000066400000000000000000000025061524402210300171260ustar00rootroot00000000000000SocNetV-3.7/src/images/transposematrix.png000066400000000000000000000011611524402210300207010ustar00rootroot00000000000000‰PNG  IHDR22?ˆ±bKGD ½§“ pHYs  šœtIMEΰ &}ο―IώIDAThήν˜Ο+QΗ?ΟΓ’H6€$½²U~l‰„’”•z%!l”₯=±C)a㠐…• €(²ίc3κ%οάyσξάuΏukjΞ9s>sΝ{•••UŠE GGΎ9Ά"f*δ9?]9rH7’‘ͺu 8UΌέψ―Š8Ξ-Zςβ]BŒόΠP@Š€g ;Μγ £!~FcAbΐY† QiΞΒ>κ,@b)CΫη·_Έw(|\c΄V«P˜δ@xIs (ˆJk%…φΩrmV›‘¨,‚»B’CM»`s…νQ•ΰPž²ZίΆ-:“ς3Ωϋ„{ΐ•{ύ , ΆaWδBxΛγΏl‚ν;PDƒb¨όΓg_°Ÿ dFHκ8Ο ΰsΖα.ά IM¦ρ+ήΏ6Σ ­ŠΆͺ|7ΏuΣ ‹Šƒ“€^Ε€/5‘<ψΨ$zc¦@:„p€S Λƒ8@­‰Άz424H‡Ές‚άkυjίwΗˆr{α-ΞϋˆΉ'Δ[ λ\ήδ#ζ€οΕύΕ€]sŠžŽϋˆY Όšό½….d{Cˆ»­€^ΡVΩLΜnΕ)³B'Θ΄π°Gw}ρ«<ΕGdB'Θ©π  ρg£°e±²²²²²ψχχΰ‰Œ [»IENDB`‚SocNetV-3.7/src/images/triad.png000066400000000000000000000031531524402210300165440ustar00rootroot00000000000000‰PNG  IHDR szzτsBIT|dˆ pHYsψψΟΑζetEXtSoftwarewww.inkscape.org›ξ<θIDATX…΅–mL”WΗχaW·k^TΚΞ……₯Šˆdm(.u»έΕnkmi€»IϋA6nnφƒΩ—¬i±±±k+M· *”°ΕΦ(Τb™Zi2t ˆe^`ζαμŸvΐΔ“œδyΞ=χžύŸsοΉJDŠRΚ΄vεΚWσssΧΞ‹ρθυλ}ηΫΫύΉέ~”{$¦ΠŸ λΦΥVΏτγI‰‰A“υ«V­ϊy^ή’ —.½}/hΑ΄””υΟ=ύtyHp–Ωl ΉΉ/ά‹ΰΒΐ’ΤΤ΅K—ΖGrΚΆZΣ”R©"2`SJ-°ΩxrΙ |>&zzψtp£"’Οΐ4£cc^Ÿ/’ӐΛ5ΈŒφœ΅yλV>mldwSΟ|φUGŽπnY§’“ΥCspΉ«λΠ‘Ί:»Ρ!πyGG‹ˆL†ΪΣΣU~U;pkz: iPTDL}=₯₯Τ*₯’"‘i]±lٚͺ-[ΞΰχΩ“'έ΅{φŒl*+ϋH υ{ŒNMቬί}‡§¨ˆΧσŒn€ψΤδδGσrrώ8ίbΙ‹8 b^~™+3je%Ρ˜"024ά™dAr2ΣτŠ LΊNLl,ώ˜t‹…€hΠ’9DΎz—39IœίO¬¦1e2υΒm­i”R ™™l]Ό˜΄ώ~.~υ΅"2:A)u°5+‹ϋΡRSρ+u³ΫλΥΧ3~νg³l6ΩΌ™ έέx‚ωλνΕ³e ³³©Έ•χ΅ΐ»ΐϋΐ#@|E§œΞΫ9χϋ™π?ΟΘϊυΌ-"‚2›I­ͺβ“šΒΑ‘Άogό­·°λ:Mΐ;"2M«R*Ύ¨ˆPœŸΟ’‘!βμvF;;y½½]jBό³iΣΛ—ΔΕΖ& |ϋmχι3gώΡρε—η()α#ηΞκΥuΌψ}>όΎRŽF=N°ψπΈΑnϊ}eε'=ξΎiέϋΖ_/ΛΚZg²ZI3›oξVΧ‰Ρub4©ΨΨΫΉ΅ΩH™E*ϋ”Rn£ύαΒΒν^kΊσΐU”—δόΕ‹;΄yσn• ₯“‰@08€ΩL’RJEI¬Ά͟qlqZZšζr1 iLΕΗ3a2-€Λ…SBs‘‘‘±™Ζ<ϘζpΠxφ,Acˆ¦‘ONΧ‚φΕœΎ›ΰW{z­« λ’#££vαB3"BI GμvƌW©ΓΑΥ«iNہΈ(…Έ CŠ%ΕΕ•―ξάy©ύά9O·Γα~o~gΕƍoš”Rͺ°yyό63“ϋuε ŽŽ΅΅ρΧ[ TοEκχJ©mΐu {Β)₯,Ω™™e‹εΗεΛ γγγέ@Δf΄X8SΊ΅Λz P³aΰ^DwS[J©8ΰyΰWΐ›ΐιgŸεŸK—²ΑlF‘£Ή™]­­u±Ή ΐˆΨρΤS|οσα ^Ε"Έ’wυjΦΜω=0W-/η•7πLN2ιυ’ˆΰ¦nΞοšS ςσ7ϊύ~_‡έή "ίGς³ZΙΆXΐolΗ)),‰–ˆ6–•½Άοήm%ΕΕ‹υύJKkšš›χ}‡‡Ύ š; Κηc4€0JΦ=κ㏇CG_W—ϋπΑƒύ?ΛΝύ₯ΡΏ°¬cΗθ6ή!N'γO<ΑŸ’₯ μE΄<'§β§σŒφ•…… sW¬¨4ΪΫΪδΚΎ}쬩‘ΫιDy½pβ?Όψ"ο>,»’–‚Δ„„39Ο·XF²?.QJά·G“’x ½^―τD @ΐ@π°Ρλ:Χz{―Ν΄ˆά>šMΠP KAKkλkοΤΦ~mXœΏοήν8ΡΠ•ΉJ}ύύέ¦§Ίχ›oώς`zzζΤΤTΐΡΩyΉ©Ήy‡ˆΜΨZοVώΧρ‚Γ4ΒδIENDB`‚SocNetV-3.7/src/images/triadcensus.png000066400000000000000000000022041524402210300177610ustar00rootroot00000000000000‰PNG  IHDR22?ˆ±bKGD ½§“ pHYs  šœtIMEΰ --2§ΒIDAThήνΨIheΰ'&mLRm›Fmm:8΄ΦtP΅uV\¨ˆ ‹‚(.‘θFPQtηΚ…ΈqU‘*ΊpVpΐ‘]hJΥͺΥN¨Υ΄Vkν”Nir]τ~.Li{kσΒε‡?ίOΎσ½οyΟω^†c8N¨hκ%q9nΖzl«ΧL\O±Ο£³^Λι!μFe¨`ΚTZύΨ€6ΜΑɘV|wΈ2+GΆfΣCSk gΰ΄`dήm/€™ύ_Α4ΥΈ;έ› οŸ؈Mψ„7θύωnA™JhvΊS_@μN&ΦγלόηXœχiΌP¦Œ „άMωUR2ί £1 ωUς„π+KVΖbuα€+Έ'@¦γv<ŠW“©υYσΞ,›v\† @Vγξd©9DoΒxΜΓ#i₯Œj0«¦.£ ?U•Ω­υ OΰYάV•™ ξ(Γ&GaΚa@<]Ψτ¬Ό+6€αIC­@Lΐ·ψσqκΦ<ρ«ΰͺnΆ[BςšΕΌˆ];° ρ`Ϊj+ζβ{¬‰ΊW[₯Ρθ¨εΕͺ#›ŸΆΉ7'<η9;ς >ΔkΗb쉍―‘˜K1+Ѝqƒ‹ςχ‰X†/Π{4ώρΡ2θ™ΞΟ†{ρJ6 §α,œ‡ Σv›πsΩ€H9΅’+'ΏοdΓsΣΝφbΖυ)·/Λd7φafŒ_kΈsΞΕG)·ζdhkΈ½J·„Δ•Βσύ‚Ζ\ƒΣͺK=HθΒ’θAKγ«£ε ϊrDqΆΨζC|ێ;19$ίNτ₯·dέ£9·*GΒΓi₯›Β‡ώ* πdΚηΝ¬½$ΧΦuQϋZ–Λ!j?ΰρ΄Π–ˆAΫ±¦pΏ6v₯‚žΒϊšDCΘΉΌp^‹ηp]NύΎ*‹ώF²qΜΌSγ€4DΘΞ‰‚·§\Ζ₯ήwd΄³/J/wρ—cKj€hŸϋpzLα6GπΖη½6*ώΩ8πw²₯@fF} #™ήXφt€Νi³±ΫSZ»Žwν Θ•x&’6%λv€\:…z"p ™S ΘF¬ͺ{=@&ηζvS2 >)·΅Ώb ΫJGΨζδ9+#›Η³ΝȈΊ+>ip,35™šROΙ†ίK70έx=Úf€?Δ“Ί›έυSσ~T4αλό–Ϋ?³]„%eΰHκ»7|θΜ¦—β]όXΐ5ɞ”\wΊ—²}OSocNetV-3.7/src/images/walk.png000066400000000000000000000016371524402210300164040ustar00rootroot00000000000000‰PNG  IHDR szzτbKGD.l— pHYs  šœtIMEήl„Ι&iTXtCommentCreated with GIMP on a Mac•δ_[ϊIDATXΓν—ΟK”AΗ?σ„9J!’K Ί‹Hh«A—ˆm‚ Ρ-Ίt­@J’‹t­?AͺΥ$6q·Θ @θX”τ›uσιΠΜ:½­ϋϊb—ζΧχ™™ο|Ÿgΰ©ΏdP2uv§Α₯ΏGΏœ8τs •x ΘN:QH§s_εΈX;έςڜD€’κAW°ϊdf0ή9ΤZi[œyΗ@ζΡ*Πe-/*Έ οvβΕ'.gέφΙ‰"]xΎ£ΆυΌέ(hOΠχ-œ$•NδζgeΌC S4‰ ­Ε·8fκ+ΞΒ1χψ ]xdν™ρ"hΩ*Έ]l:7ύνΞίuBΠώ¦I(h₯XA9ΣS¬¬mΰ€l_£b₯dVׁVΤfwœ]ϋ›œSσΘΓϊP΄«μZŒz‚Ž…Νqœ*(rrtχ™σΉS4`YΕ{η‡,ͺ•)šZfσ™}ιΔ,cΩ5»3ΊLΥ£h›|~…T:‘sTΝF€³«€ryνC‹ΰͺhΦ‰dσ€Ή|ζkw2‘+“ΧίTFΌWΊœ–κW!/­πώεΛΝ=9Ί»‰fSυF5KDA'«v――+γZfŸ H₯9χΨA§· ^–ιρπn—ˆF9KΥΈΆωTΦ Έ1Ψg¬ςRβ—”#†tq6 άh+h€d5E 'ζ‡I§g$x7πΕΞ±‚dΜn•]7L’ΠιœB«έ]7Έ\ˆ"ΕΤ`νF g΅γz‚nτΩཇ•3€j]ž[ώœi+ΊOΘXs­„΅ OΤοHX0ς-α̌΄]ΉμΨϋΙο1c|Cࠝ±ZοfJQu₯4·0όΩuΰq>σΝΩ}MλNκQ²εjχ²tιBqΩmΈ|αω[ΰp0ΔΚ7nυ΄»soήξέŽόιȌ΄«p τϊzϋώŒvΙ”ž―'ΊΦϋνΚΪlΩΡϊύ1ύσ?cδς Ω­{Hg•IENDB`‚SocNetV-3.7/src/images/wallpaper_48px.svg000066400000000000000000000005411524402210300203240ustar00rootroot00000000000000SocNetV-3.7/src/images/webcrawler_48px.svg000066400000000000000000000044441524402210300205000ustar00rootroot00000000000000 SocNetV-3.7/src/images/zoom_in_24px.svg000066400000000000000000000005601524402210300200020ustar00rootroot00000000000000SocNetV-3.7/src/images/zoom_out_24px.svg000066400000000000000000000005321524402210300202020ustar00rootroot00000000000000SocNetV-3.7/src/main.cpp000077500000000000000000000153361524402210300151270ustar00rootroot00000000000000/** * @file main.cpp * @brief Entry point for the SocNetV application, initializing the application and starting the main event loop. * @details This file contains the `main()` function, which sets up the application environment, initializes the main window, and handles command-line arguments. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include //core Qt functionality #include #include //for text translations #include #include #include #include "mainwindow.h" //main application window using namespace std; int main(int argc, char *argv[]) { // // Set the global default surface format to enable multisampling // used by default in QOpenGLContext, QWindow, QOpenGLWidget and similar classes. // QSurfaceFormat fmt; fmt.setSamples(4); QSurfaceFormat::setDefaultFormat(fmt); // // Create the application instance // QApplication app(argc, argv); // Check that the default stylesheet is available in the resources Q_ASSERT(QFile(":/qss/default.qss").exists()); // // Setup app translations // // Todo update/remove translations QTranslator tor( 0 ); QLocale locale; // set the location where .qm files are in load() below as the last parameter instead of "." // for development, use "/" to use the english original as // .qm files are stored in the base project directory. if (!tor.load(QString("socnetv.") + locale.name(), ".")) { qWarning() << "Translation file not found for locale" << locale.name() << "- continuing with defaults."; } else { app.installTranslator(&tor); } // // Set application basic info // app.setOrganizationName("socnetv"); app.setOrganizationDomain("socnetv.org"); app.setApplicationDisplayName("Social Network Visualizer v" + VERSION); // Used in widgets app.setApplicationName("Social Network Visualizer"); // used by windowing system app.setApplicationVersion(VERSION); // // Setup the command line parser // QCommandLineParser parser; QString cmdDescr = "\nSocial Network Visualizer, version " + (VERSION) + "\n\n" "Copyright: Dimitris B. Kalamaras \n" + "License: GPL3" + "\n" + "Website: https://socnetv.org\n"; parser.setApplicationDescription(cmdDescr); parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument( "file", QCoreApplication::translate("main", "Network file to load on startup. You can load a network from a file using `socnetv file.net` where file.net/csv/dot/graphml must be of valid format. See README.") ); // A boolean option for progress dialogs QCommandLineOption showProgressOption(QStringList() << "p" << "progress", QCoreApplication::translate("main", "Force showing progress dialogs/bars during computations.")); parser.addOption(showProgressOption); // A boolean option to override maximized display QCommandLineOption showUnMaximizedOption(QStringList() << "nm" << "notmaximized", QCoreApplication::translate("main", "Do not maximize the app window.")); parser.addOption(showUnMaximizedOption); // A boolean option for full screen display QCommandLineOption showFullScreenOption(QStringList() << "f" << "fullscreen", QCoreApplication::translate("main", "Show in full screen mode.")); parser.addOption(showFullScreenOption); // An option to enable debug messges with a verbosity value QCommandLineOption showDebugOption(QStringList() << "d" << "debug", QCoreApplication::translate("main", "Print debug messages to stdout/console. Available verbosity s: 'none', 'min' or 'full'. Default: 'min'."), QCoreApplication::translate("main", "level")); parser.addOption(showDebugOption); // An option to load the startup file with a specific text codec, bypassing the // "Preview file & Choose Encoding" dialog. See #261. QCommandLineOption encodingOption(QStringList() << "encoding", QCoreApplication::translate("main", "Load the startup with this text encoding (e.g. 'UTF-8'), bypassing the encoding preview dialog."), QCoreApplication::translate("main", "encoding")); parser.addOption(encodingOption); // An option to run a plain-text interactive script after startup. See #261. QCommandLineOption interactiveScriptOption(QStringList() << "interactive-script", QCoreApplication::translate("main", "Run the interactive script at after startup. One command per line: 'delay X' (wait X seconds) or 'new' (File > New)."), QCoreApplication::translate("main", "path")); parser.addOption(interactiveScriptOption); // Process the actual command line arguments given by the user parser.process(app); // Read positional arguments const QStringList args = parser.positionalArguments(); QString fileName; if ( !args.isEmpty() ) { fileName= args.at(0); } bool showProgress = parser.isSet(showProgressOption); bool showMaximized = ! parser.isSet(showUnMaximizedOption); bool showFullScreen= parser.isSet(showFullScreenOption); bool showDebug = parser.isSet(showDebugOption); int debugLevel = -1; // By default, we assume no debug option was passed if (showDebug) { if (parser.value(showDebugOption) == "full") { debugLevel = 2; } else if (parser.value(showDebugOption) == "min") { debugLevel = 1; } else { // Any other value/string, disables the debugging debugLevel = 0; } } QString encodingOverride = parser.value(encodingOption); QString interactiveScriptPath = parser.value(interactiveScriptOption); // // Create our MainWindow and exec the app to enter the main event loop. // MainWindow *socnetv = new MainWindow(fileName, showProgress, showMaximized, showFullScreen, debugLevel, encodingOverride, interactiveScriptPath); // Show the application socnetv->show(); return app.exec(); } SocNetV-3.7/src/mainwindow.cpp000077500000000000000000000225401524402210300163520ustar00rootroot00000000000000/** * @file mainwindow.cpp * @brief Implements the MainWindow class, which serves as the primary interface for the SocNetV application. * @details This file contains the logic for the main application window, including menus, toolbars, and user interactions for network visualization and analysis. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include // for SVG icons #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mainwindow.h" #include "graph.h" #include "texteditor.h" #include "graphicswidget.h" #include "graphicsnode.h" #include "graphicsedge.h" #include "chart.h" #include "forms/dialogpreviewfile.h" #include "forms/dialogwebcrawler.h" #include "forms/dialogdatasetselect.h" #include "forms/dialogranderdosrenyi.h" #include "forms/dialograndsmallworld.h" #include "forms/dialograndscalefree.h" #include "forms/dialograndregular.h" #include "forms/dialograndlattice.h" #include "forms/dialogexportpdf.h" #include "forms/dialogexportimage.h" #include "forms/dialognodefind.h" #include "forms/dialognodeedit.h" #include "forms/dialogedgeedit.h" #include "forms/dialogbulkedit.h" #include "forms/dialogfilternodesbycentrality.h" #include "forms/dialogfilterbyattribute.h" #include "forms/dialogquerybuilder.h" #include "widgets/filterbarwidget.h" #include "widgets/graphtablewidget.h" #include "graph/io/table_export.h" #include #include "forms/dialogfilteredgesbyweight.h" #include "forms/dialogedgedichotomization.h" #include "forms/dialogsimilaritypearson.h" #include "forms/dialogsimilaritymatches.h" #include "forms/dialogclusteringhierarchical.h" #include "forms/dialogdissimilarities.h" #include "forms/dialogsettings.h" #include "forms/dialogsysteminfo.h" Q_LOGGING_CATEGORY(lcMainWindow, "socnetv.mainwindow") /** * @brief Constructs the MainWindow (MW) object * * @param m_fileName * @param forceProgress * @param maximized * @param fullscreen * @param debugLevel */ MainWindow::MainWindow(const QString &m_fileName, const bool &forceProgress, const bool &maximized, const bool &fullscreen, const int &debugLevel, const QString &encodingOverride, const QString &interactiveScriptPath) { m_encodingOverride = encodingOverride; qCDebug(lcMainWindow) << "=========== MainWindow (MW) constructor starting on thread:" << thread(); // // Setup debug messages/level // switch (debugLevel) { case 0: default: // Debugging disabled by command line parameter (case 0), or no -d flag at all // (default -- WS14 L1: a no-flag launch is release-build-quiet by design, same // as an explicit -d 0, not just "whatever was last saved to settings.conf"; see // the matching fix in initSettings() below for why that second part matters). // Bare message, no decoration -- NOT an empty pattern. A truly empty pattern has // no %{message} token, so Qt's formatter reduces every message to an empty string // regardless of severity or category, silently swallowing qInfo()/qWarning()/ // qCritical() output too, not just the qDebug() spam this is meant to suppress // (found while wiring up WS14's own qInfo()-based benchmark instrumentation, // which went dark under the old "" pattern for exactly this reason). qSetMessagePattern("%{message}"); // Disable debugging messages with filter rule QLoggingCategory::setFilterRules("default.debug=false\n" "socnetv.*.debug=false"); break; case 1: // Debugging set to minimum by command line parameter qSetMessagePattern("[%{type}] (%{file}:%{line}) %{function} - %{message}"); break; case 2: // Debugging set to maximum by command line parameter qSetMessagePattern("[%{type} %{category}] %{time yyyyMMdd h:mm:ss.zzz t} %{threadid} (%{file}:%{line}) %{function} - %{message}"); break; } // // Setup window icon // setWindowIcon(QIcon(":/images/socnetv_logo_white_bg_128px.svg")); // // Initialize/load app settings and store them to memory // appSettings = initSettings(debugLevel, forceProgress); // // Initialize minimum app window size // // Get host screen width and height int primaryScreenWidth = QApplication::primaryScreen()->availableSize().width(); int primaryScreenHeight = QApplication::primaryScreen()->availableSize().height(); // Set a default min width and height int windowMinWidth = 1024; int windowMinHeight = 750; // For large screens, use more generous min height and width. if (primaryScreenWidth >= 1920) { windowMinWidth = 1440; } else if (primaryScreenWidth >= 1280) { windowMinWidth = 1024; } if (primaryScreenHeight >= 1440) { windowMinHeight = 960; } else if (primaryScreenHeight >= 1024) { windowMinHeight = 800; } qCDebug(lcMainWindow) << "primaryScreen: " << primaryScreenWidth << "x" << primaryScreenHeight << "Set Minimum MW size to:" << windowMinWidth << "x" << windowMinHeight; // Set MW minimum size, before creating the graphics widget setMinimumSize(windowMinWidth, windowMinHeight); // // Initialize devices // qCDebug(lcMainWindow) << "Initialize devices..."; // Create printer devices printer = new QPrinter; printerPDF = new QPrinter; // Create our network manager networkManager = new QNetworkAccessManager; // Store the max zoom index // The initial zoom will be the half of it // @see zoomSlider // @see GraphicsWidget::setInitZoomIndex() // @see GraphicsWidget::setMaxZoomIndex() maxZoomIndex = 1000; // // Initialize widgets // qCDebug(lcMainWindow) << "Setup canvas, graph, widgets (actions, menus, panels, signal/slots) and init app..."; initView(); // Init our network view initGraph(); // Init the graph model initActions(); // Register and construct menu Actions initMenuBar(); // Construct the menu initToolBar(); // Build the toolbar initPanels(); // Build the toolbox initWindowLayout(); // Init the application window, set layout etc initSignalSlots(); // Connect signals and slots between app components if (maximized) { qCDebug(lcMainWindow) << "maximizing window as per user request."; showMaximized(); } if (fullscreen) { showFullScreen(); } initApp(); // Load and initialise default app parameters graphicsWidget->setFocus(); // // Load user-provided network file, if any // qCDebug(lcMainWindow) << "Checking if user provided file on startup..."; if (!m_fileName.isEmpty()) { qCDebug(lcMainWindow) << "Loading user provided file" << m_fileName; slotNetworkFileChoose(m_fileName); } QString welcomeMsg = tr("Welcome to %1, version %2").arg(qApp->applicationName(), VERSION); statusMessage(welcomeMsg); if (!interactiveScriptPath.isEmpty()) { runInteractiveScript(interactiveScriptPath); } qCDebug(lcMainWindow) << "@@@@ MW Constructor finished, on thread:" << thread(); } /** * @brief Deletes variables on MW closing */ MainWindow::~MainWindow() { qCDebug(lcMainWindow) << "Destructor for MW running..."; // Init app to clear all maps etc. initApp(); // Terminate any threads running terminateThreads("~MainWindow()"); // Delete devices delete printer; delete printerPDF; delete scene; delete graphicsWidget; foreach (TextEditor *ed, m_textEditors) { ed->close(); delete ed; } m_textEditors.clear(); codecs.clear(); qCDebug(lcMainWindow) << "Destruct function finished - bye!"; } /** * Transforms all nodes to edges TODO slotEditTransformNodes2Edges */ /** * Displays the arc and dyad reciprocity of the network */ /** * Displays a box informing the user about the symmetry or not of the adjacency matrix */ /** * Writes Betweenness Centralities into a file, then displays it. */ SocNetV-3.7/src/mainwindow.h000077500000000000000000001100471524402210300160170ustar00rootroot00000000000000/** * @file mainwindow.h * @brief Declares the MainWindow class, the primary interface for the SocNetV application. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef MAINWINDOW_H #define MAINWINDOW_H /** \file mainwindow.h \brief Documentation for the mainwindow file. */ #include #include #include #include #include #include #include #include // Allows to use QT_CHARTS namespace directives (see below) #include #include "global.h" #include "graph/filters/filter_condition.h" // WS7 MW0: declared here since mainwindow.h is included by every src/mainwindow// slice // .cpp, defined once in mainwindow.cpp. One blanket category (unlike Graph's per-domain split in // graph.h) - MainWindow's slots are UI wiring, not independently-toggled algorithm domains. Q_DECLARE_LOGGING_CATEGORY(lcMainWindow) QT_BEGIN_NAMESPACE class QGraphicsScene; class QMenu; class QAction; class QCheckBox; class QProgressDialog; class QPushButton; class QToolButton; class QPageSize; class QSlider; class QComboBox; class QGroupBox; class QTabWidget; class QSpinBox; class QNetworkAccessManager; class QSslError; class QString; class QAbstractSeries; class QAbstractAxis; class QTextCodec; class QUrl; QT_END_NAMESPACE using namespace std; SOCNETV_USE_NAMESPACE class Graph; class GraphicsWidget; class GraphicsEdge; class GraphicsNode; class Chart; class DialogPreviewFile; class DialogWebCrawler; class DialogDataSetSelect; class DialogRandErdosRenyi; class DialogRandSmallWorld; class DialogRandScaleFree; class DialogRandRegular; class DialogRandLattice; class DialogSimilarityPearson; class DialogSimilarityMatches; class DialogDissimilarities; class DialogClusteringHierarchical; class DialogExportPDF; class DialogExportImage; class DialogNodeFind; class DialogNodeEdit; class DialogEdgeEdit; class DialogBulkEdit; class DialogFilterNodesByCentrality; class DialogFilterEdgesByWeight; class DialogQueryBuilder; class DialogEdgeDichotomization; class DialogSettings; class DialogSystemInfo; class TextEditor; class FilterBarWidget; class GraphTableWidget; class QDockWidget; typedef QHash H_StrToInt; /** \brief The base window of SocNetV contains all widgets and functionality. It sets up the main window and provides a menubar, toolbar and statusbar. For the main view, an instance of class GraphicsWidget is created which creates a graphics widget. */ class MainWindow : public QMainWindow { Q_OBJECT QThread graphThread; public: MainWindow(const QString &m_fileName=QString(), const bool &forceProgress=false, const bool &maximized=false, const bool &fullscreen=false, const int &debugLevel=0, const QString &encodingOverride=QString(), const QString &interactiveScriptPath=QString()); ~MainWindow(); void slotOptionsCustomStylesheet(const bool checked); void slotStyleSheetByName(const QString &sheetFileName); void polishProgressDialog(QProgressDialog* dialog); void setAppBusy(bool busy); void runGraphOperationAsync(std::function operation, const QString &waitMessage, const QString &doneMessage = QString()); void runGraphOperationAsync(std::function operation, const QString &waitMessage, std::function onComplete); bool confirmGenerationSize(qint64 expectedEdges, const QString &generatorLabel, bool scripted); void initGraph(); void terminateThreads(const QString &reason); void initView(); void initActions(); void initMenuBar(); void initToolBar(); void initPanels(); void initWindowLayout(); void initSignalSlots(); QMap initSettings(const int &debugLevel=0, const bool &forceProgress=false); void initNetworkAvailableTextCodecs(); void saveSettings(); void initApp(); void runInteractiveScript(const QString &scriptPath); void processNextInteractiveCommand(); void initComboBoxes(); void setLastPath(const QString &filePath); QString getLastPath(); void createFortuneCookies(); int activeEdges(); int activeNodes(); public slots: //NETWORK MENU void slotNetworkNew(); void slotNetworkFileChoose(QString m_fileName = QString(), int fileFormat = -1, const bool &checkSelectFileType = true); void slotNetworkFileDialogFileSelected(const QString &fileName); void slotNetworkFileDialogFilterSelected(const QString &filter); void slotNetworkFileDialogRejected(); void slotNetworkFileRecentUpdateActions(); bool slotNetworkFilePreview(const QString &, const int &); void slotNetworkFileLoad (const QString &fileNameToLoad, const QString &codeName, const int &fileFormat); void slotNetworkFileLoaded(const int &type, const QString &fName=QString(), const QString &netName=QString(), const int &totalNodes=0, const int &totalEdges=0, const qreal &density=0, const qint64 &elapsedTime=0, const QString &message=QString()); void slotNetworkFileLoadRecent(); void slotNetworkSavedStatus(const int &status); void slotNetworkFileView(); void slotNetworkImportGraphML(); void slotNetworkImportPajek(); void slotNetworkImportAdjacency(); void slotNetworkImportGraphviz(); void slotNetworkImportGML(); void slotNetworkImportUcinet(); void slotNetworkImportEdgeList(); void slotNetworkImportTwoModeSM(); void slotNetworkChanged(const bool &directed, const int &vertices, const int &edges, const qreal &density, const bool &needsSaving=true); void slotNetworkSave(const int &fileFormat=-1); void slotNetworkSaveAs(); bool slotNetworkClose(); void slotNetworkPrint(); void slotNetworkViewSociomatrix(); void slotNetworkViewSociomatrixPlotText(); void slotNetworkExportImageDialog(); void slotNetworkExportImage ( const QString &filename, const QByteArray &format, const int &quality, const int &compression ); void slotNetworkExportPDFDialog(); void slotNetworkExportPDF(QString &pdfName, const QPageLayout::Orientation &orientation, const int &dpi, const QPrinter::PrinterMode printerMode, const QPageSize &pageSize); void slotNetworkExportPajek(); void slotNetworkExportSM(); void slotNetworkExportDot(); bool slotNetworkExportDL(); bool slotNetworkExportGW(); bool slotNetworkExportList(); void slotNetworkExportNodesCSV(); void slotNetworkExportEdgesCSV(); void slotNetworkExportNodesJSON(); void slotNetworkExportEdgesJSON(); void slotNetworkTextEditor(); void slotNetworkDataSetSelect(); void slotNetworkDataSetRecreate(const QString); void slotNetworkRandomErdosRenyiDialog(); void slotNetworkRandomErdosRenyi( const int N, const QString model, const int edges, const qreal eprob, const QString mode, const bool diag) ; void slotNetworkRandomRegularDialog(); void slotNetworkRandomRegular(const int &newNodes, const int °ree, const QString &mode, const bool &diag); void slotNetworkRandomGaussian(); void slotNetworkRandomScaleFreeDialog(); void slotNetworkRandomScaleFree(const int &newNodes, const int &power, const int &initialNodes, const int &edgesPerStep, const qreal &zeroAppeal, const QString &mode); void slotNetworkRandomSmallWorldDialog(); void slotNetworkRandomSmallWorld (const int &newNodes, const int °ree, const qreal &beta, const QString &mode, const bool &diag); void slotNetworkRandomRingLattice(); void slotNetworkRandomLatticeDialog(); void slotNetworkRandomLattice(const int &newNodes, const int &length, const int &dimension, const int &nei, const QString &mode, const bool &circular); void slotNetworkWebCrawlerDialog(); void slotNetworkWebCrawler(const QUrl &startUrl, const QStringList &urlPatternsIncluded, const QStringList &urlPatternsExcluded, const QStringList &linkClasses, const int &maxNodes, const int &maxLinksPerPage, const bool &intLinks, const bool &childLinks, const bool &parentLinks, const bool &selfLinks, const bool &extLinks, const bool &extLinksCrawl, const bool &socialLinks, const bool &delayedRequests); void slotNetworkManagerRequest(const QUrl &url, const NetworkRequestType &requestType); //EDIT MENU void slotEditDragModeSelection(bool); void slotEditDragModeScroll(bool); void slotEditRelationsClear(); void slotEditRelationAddPrompt(); void slotEditRelationAdd(const QString &newRelationName); void slotEditRelationChange(const int &relIndex=RAND_MAX); void slotEditRelationRename(); void slotEditOpenContextMenu(const QPointF & mPos); void slotEditSelectionChanged (const int &selNodes, const int &selEdges); void slotEditNodeSelectAll(); void slotEditNodeSelectNone(); void slotEditNodeInfoStatusBar(const int &number, const QPointF &p, const QString &label, const int &inDegree, const int &outDegree); void slotEditNodePosition(const int &nodeNumber, const int &x, const int &y); void slotEditNodeAdd(); void slotEditNodeFindDialog(); void slotEditNodeFind(const QStringList &nodeList, const QString &searchType, const QString &indexStr=QString()); void slotEditNodeRemove(); void slotEditNodeOpenContextMenu(); void slotEditNodePropertiesDialog(); void slotCacheSelection(const QList &nodes, const QList &edges); void slotEditNodeEditSelectionInTable(); void slotEditNodeSetPropertyForSelection(); void slotEditEdgeSetPropertyForSelection(); void slotEditNodeProperties( const QString &label, const int &size, const QColor &color, const QString &shape, const QString &iconPath, const QHash &customAttributes ); void slotEditNodeSelectedToClique(); void slotEditNodeSelectedToStar(); void slotEditNodeSelectedToCycle(); void slotEditNodeSelectedToLine(); void slotEditNodeColorAll(QColor color=QColor()); void slotEditNodeSizeAll(int newSize=0, const bool &normalized=false); void slotEditNodeShape(const int &vertex = 0, QString shape=QString(), QString nodeIconPath=QString()); void slotEditNodeNumberSize(int v1=0, int newSize=0, const bool prompt=true); void slotEditNodeNumberDistance(int v1=0, int newSize=0); void slotEditNodeNumbersColor(const int &v1=0, QColor color=QColor()); void slotEditNodeLabelSize(const int v1=0, int newSize=0); void slotEditNodeLabelsColor(QColor color=QColor()); void slotEditNodeLabelDistance(int v1=0, int newSize=0); void slotEditEdgeClicked (const MyEdge &edge=MyEdge(), const bool &openMenu=false); void slotEditEdgeOpenContextMenu(const QString &str="") ; void slotEditEdgeAdd(); void slotEditEdgeCreate (const int &source, const int &target, const qreal &weight=1); void slotEditEdgeRemove(); void slotEditEdgePropertiesDialog(); void slotEditEdgeProperties(const int &v1, const int &v2, const QString &label, const double &weight, const QColor &color, const QHash &customAttributes); void slotEditEdgeLabel(); void slotEditEdgeColor(); void slotEditEdgeWeight(); void slotEditEdgeColorAll(QColor color=QColor(), const int threshold=RAND_MAX); void slotEditEdgeMode(const int &mode); void slotEditGraphDirectedChanged(const bool &directed); void slotEditEdgeSymmetrizeAll(); void slotEditEdgeSymmetrizeStrongTies(); void slotEditEdgeSymmetrizeCocitation(); void slotEditEdgeUndirectedAll(const bool &toggle); void slotEditEdgeDichotomizationDialog(); void slotEditEdgeDichotomization(const qreal threshold); void slotFilterNodesDialogByCentrality(); void slotEditFilterNodesIsolates(bool checked); void slotFilterNodesByEgoNetwork(); void slotFilterNodesBySelection(); void slotFilterNodesByAttribute(); void slotFilterByQueryBuilder(); void slotFilterNodesRestoreAll(); void slotEditFilterEdgesByWeightDialog(); void slotEditFilterEdgesReset(); void slotEditFilterEdgesUnilateral(bool checked); void slotEditSubgraphExtract(); void slotEditSubgraphExtractFromSelection(); void saveSubgraphToFile(Graph *sub, const QString &subgraphName); void slotEditTransformNodes2Edges(); // LAYOUT MENU void slotLayoutRandom(); void slotLayoutRadialRandom(); void slotLayoutEgoRadial(); void slotLayoutRadialByProminenceIndex(); void slotLayoutRadialByProminenceIndex(QString prominenceIndexName); void slotLayoutLevelByProminenceIndex(); void slotLayoutLevelByProminenceIndex(QString prominenceIndexName); void slotLayoutNodeSizeByProminenceIndex(); void slotLayoutNodeSizeByProminenceIndex(QString prominenceIndexName); void slotLayoutNodeColorByProminenceIndex(); void slotLayoutNodeColorByProminenceIndex(QString prominenceIndexName); void slotLayoutNodeColorByComponent(); void slotLayoutSpringEmbedder(); void slotLayoutFruchterman(); void slotLayoutKamadaKawai(); void slotLayoutColorationStrongStructural(); void slotLayoutColorationRegular(); void slotLayoutGuides(const bool &toggle); //ANALYSIS MENU void askAboutEdgeWeights(const bool userTriggered=false); void slotAnalyzeReciprocity(); void slotAnalyzeSymmetryCheck(); void slotAnalyzeMatrixAdjacencyInverse(); void slotAnalyzeMatrixAdjacencyTranspose(); void slotAnalyzeMatrixAdjacencyCocitation(); void slotAnalyzeMatrixDegree(); void slotAnalyzeMatrixLaplacian(); void slotAnalyzeClusteringCoefficient(); void slotAnalyzeMatrixDistances(); void slotAnalyzeMatrixGeodesics(); void slotAnalyzeDistance(); void slotAnalyzeDistanceAverage(); void slotAnalyzeGeodesicDistribution(); void slotAnalyzeDiameter(); void slotAnalyzeEccentricity(); void slotAnalyzeWalksLength(); void slotAnalyzeWalksTotal(); void slotAnalyzeReachabilityMatrix(); void slotAnalyzeConnectedness(); void slotAnalyzeNodeConnectivity(); void slotAnalyzeConnectivity(); void slotAnalyzeCentralityDegree(); void slotAnalyzeCentralityCloseness(); void slotAnalyzeCentralityClosenessIR(); void slotAnalyzeCentralityBetweenness(); void slotAnalyzeCentralityInformation(); void slotAnalyzeCentralityEigenvector(); void slotAnalyzeCentralityStress(); void slotAnalyzeCentralityPower(); void slotAnalyzeCentralityEccentricity(); void slotAnalyzeCentralityKatz(); void slotAnalyzeCentralityBonacich(); void slotAnalyzePrestigeDegree(); void slotAnalyzePrestigePageRank(); void slotAnalyzePrestigeProximity(); void slotAnalyzeProminenceDistributionChartUpdate(QAbstractSeries *series, QAbstractAxis *axisX, const qreal &min, const qreal &max, QAbstractAxis *axisY=Q_NULLPTR, const qreal &minF=0, const qreal &maxF=0 ); void slotAnalyzeCommunitiesCliqueCensus(); void slotAnalyzeCommunitiesTriadCensus(); void slotAnalyzeStrEquivalenceClusteringHierarchicalDialog(); void slotAnalyzeStrEquivalenceClusteringHierarchical(const QString &matrix, const QString &varLocation, const QString &metric, const QString &method, const bool &diagonal=false, const bool &diagram=false); void slotAnalyzeStrEquivalenceDissimilaritiesDialog(); void slotAnalyzeStrEquivalenceDissimilaritiesTieProfile(const QString &metric, const QString &varLocation, const bool &diagonal); void slotAnalyzeStrEquivalenceSimilarityMeasureDialog(); void slotAnalyzeStrEquivalenceSimilarityByMeasure(const QString &matrix, const QString &varLocation, const QString &measure, const bool &diagonal); void slotAnalyzeStrEquivalencePearsonDialog(); void slotAnalyzeStrEquivalencePearson(const QString &matrix, const QString &varLocation, const bool &diagonal=false); //OPTIONS MENU void slotOpenSettingsDialog(); void slotOptionsNodeNumbersVisibility(bool toggle); void slotOptionsNodeNumbersInside(bool toggle); void slotOptionsNodeLabelsVisibility(bool toggle); void slotOptionsEdgesVisibility(bool toggle); void slotOptionsEdgeOffsetFromNode(const int &offset, const int &v1=0, const int &v2=0); void slotOptionsEdgeLabelsVisibility(bool toggle); void slotOptionsEdgeWeightNumbersVisibility(bool toggle); void slotOptionsEdgeWeightsDuringComputation(bool); void slotOptionsEdgeThicknessPerWeight(bool toggle); void slotOptionsEdgesBezier(bool toggle); void slotOptionsEdgeArrowsVisibility(bool toggle); void slotOptionsEdgeArrowSize(const int &size); void slotOptionsSaveZeroWeightEdges(bool toggle); void slotOptionsShowZeroWeightEdges(bool toggle); // #30 void slotOptionsEmbedLogoExporting(bool toggle); void slotOptionsProgressDialogVisibility(bool toggle); void slotOptionsWindowToolbarVisibility(bool toggle); void slotOptionsWindowStatusbarVisibility(bool toggle); void slotOptionsWindowLeftPanelVisibility(bool toggle); void slotOptionsWindowRightPanelVisibility(bool toggle); void slotOptionsWindowFullScreen(bool toggle); void slotViewDataTable(bool checked); void slotOptionsDebugMessages(bool toggle); void slotOptionsBackgroundColor(QColor color=QColor()); void slotOptionsBackgroundImageSelect(bool toggle); void slotOptionsBackgroundImage(); void slotOptionsCanvasOpenGL(const bool &toggle=false); void slotOptionsCanvasAntialiasing(bool toggle); void slotOptionsCanvasAntialiasingAutoAdjust(const bool &toggle=false); void slotOptionsCanvasSmoothPixmapTransform(const bool &toggle=false); void slotOptionsCanvasSavePainterState(const bool &toggle=false); void slotOptionsCanvasCacheBackground(const bool &toggle=false); void slotOptionsCanvasEdgeHighlighting(const bool &toggle=false); void slotOptionsCanvasUpdateMode(const QString &mode); void slotOptionsCanvasIndexMethod(const QString &method); //HELP MENU void slotHelpTips(); void slotHelp(); void slotHelpCheckUpdateDialog(); void slotHelpCheckUpdateParse(); void slotHelpCreateTips(); void slotHelpSystemInfo(); void slotHelpAbout(); void slotAboutQt(); void slotHelpMessageToUserInfo(const QString text=QString()); void slotHelpMessageToUserError(const QString text=QString()); int slotHelpMessageToUser(const int type=0, const QString statusMsg=QString(), const QString text=QString(), const QString info=QString(), QMessageBox::StandardButtons buttons=QMessageBox::NoButton, QMessageBox::StandardButton defBtn=QMessageBox::Ok, const QString btn1=QString(), const QString btn2=QString(), const QString btn3=QString() ); void slotNetworkManagerSslErrors(QNetworkReply *reply, const QList &errors); void slotNetworkManagerReplyError(const QNetworkReply::NetworkError &code); //Called by Graph to display some message to the user void statusMessage(const QString); //Called from MW, when user highlights something in the toolbox Comboboxes void toolBoxNetworkAutoCreateSelectChanged(const int &selectedIndex); void toolBoxEditNodeSubgraphSelectChanged(const int&selectedIndex); void toolBoxEditEdgeTransformSelectChanged(const int&selectedIndex); void toolBoxFilterSelectChanged(const int &selectedIndex); void toolBoxAnalysisMatricesSelectChanged(const int&selectedIndex); void toolBoxAnalysisCohesionSelectChanged(const int&selectedIndex); void toolBoxAnalysisStrEquivalenceSelectChanged(const int&selectedIndex); void toolBoxAnalysisProminenceSelectChanged(const int&selectedIndex); void toolBoxAnalysisCommunitiesSelectChanged(const int&selectedIndex); void toolBoxLayoutByIndexApplyBtnPressed(); void toolBoxLayoutForceDirectedApplyBtnPressed(); protected: void resizeEvent(QResizeEvent * e); void closeEvent( QCloseEvent* ce ); signals: void signalRelationAddAndChange(const QString &relName, const bool &changeRelation=true); void signalSetReportsDataDir(const QString &dataDir ); private: enum { MaxRecentFiles = 5 }; int progressCounter; int fileType; int nodesEstimatedSize; int edgesPerNodeEstimatedSize; int maxRandomlyCreatedNodes; int fortuneCookiesCounter; int initZoomIndex, maxZoomIndex; bool inverseWeights, askedAboutWeights; QString fileName, previous_fileName, fileNameNoPath, progressMsg; QString initTextCodecName, userSelectedCodecName; QString m_encodingOverride; QStringList m_interactiveScriptLines; int m_interactiveScriptIndex; bool m_interactiveScriptQuitting = false; QString settingsFilePath, settingsDir ; QStringList fortuneCookie; QStringList tempFileNameNoPath, tips; QStringList prominenceIndexList; QStringList recentFiles; QStringList iconPathList; QStringList nodeShapeList; QMap appSettings; QList codecs; QList m_textEditors; // setAppBusy()'s own record of which actions it disabled, so it can restore exactly // those on busy=false without clobbering actions legitimately disabled elsewhere for // unrelated reasons (e.g. "no network loaded"). See WS15 P2 (roadmap_ws15_ // cancellation_progress_unification.md) - menuBar()/toolBar's own setEnabled(false) // blocks clicks but doesn't disable each QAction's own shortcut, so Ctrl+N-style // shortcuts could otherwise still fire and race a still-running background operation. QList m_actionsDisabledForBusy; QPrinter *printer, *printerPDF; QNetworkAccessManager *networkManager; QGraphicsScene *scene; GraphicsWidget *graphicsWidget; Graph *activeGraph; Chart *miniChart; DialogWebCrawler *m_WebCrawlerDialog; DialogDataSetSelect *m_datasetSelectDialog; DialogExportPDF *m_dialogExportPDF; DialogExportImage *m_dialogExportImage; DialogNodeEdit *m_nodeEditDialog; DialogNodeFind *m_nodeFindDialog; DialogEdgeDichotomization *m_edgeDichotomizationDialog; DialogFilterEdgesByWeight *m_DialogEdgeFilterByWeight; FilterBarWidget *m_filterBar; // All snapshot-backed filter chips in bar/stack order (barIndex == stackIndex invariant). QList> m_filterChips; QDockWidget *m_tableDock; GraphTableWidget *m_tableWidget; DialogRandErdosRenyi *m_randErdosRenyiDialog; DialogRandSmallWorld *m_randSmallWorldDialog; DialogRandScaleFree *m_randScaleFreeDialog; DialogRandRegular *m_randRegularDialog; DialogRandLattice *m_randLatticeDialog; DialogSimilarityPearson *m_dialogSimilarityPearson; DialogSimilarityMatches *m_dialogSimilarityMatches; DialogDissimilarities *m_dialogdissimilarities; DialogClusteringHierarchical *m_dialogClusteringHierarchical; DialogSettings *m_settingsDialog; DialogSystemInfo *m_systemInfoDialog; DialogPreviewFile *m_dialogPreviewFile; QToolBar *toolBar; QGroupBox *leftPanel, *rightPanel ; QScrollArea *m_leftScrollArea; QComboBox *editRelationChangeCombo; QMenu *importSubMenu, *exportSubMenu, *editMenu, *analysisMenu, *helpMenu; QMenu *optionsMenu, *colorOptionsMenu, *edgeOptionsMenu, *nodeOptionsMenu; QMenu *editNodeMenu, *editEdgeMenu, *centrlMenu, *viewOptionsMenu, *layoutMenu; QMenu *cohesionMenu, *strEquivalenceMenu, *communitiesMenu; QMenu *matrixMenu; QMenu *networkMenu, *randomNetworkMenu, *filterMenu, *subgraphMenu, *recentFilesSubMenu; QMenu *randomLayoutMenu, *layoutRadialProminenceMenu, *layoutLevelProminenceMenu; QMenu *layoutForceDirectedMenu, *layoutNodeSizeProminenceMenu, *layoutNodeColorProminenceMenu; QMenu *colorationMenu; QComboBox *toolBoxNetworkAutoCreateSelect, *toolBoxEditNodeSubgraphSelect, *toolBoxEditEdgeModeSelect, *toolBoxEditEdgeTransformSelect, *toolBoxFilterSelect; QComboBox *toolBoxAnalysisCohesionSelect, *toolBoxAnalysisStrEquivalenceSelect, *toolBoxAnalysisProminenceSelect, *toolBoxAnalysisCommunitiesSelect, *toolBoxAnalysisMatricesSelect; QComboBox *toolBoxLayoutByIndexSelect, *toolBoxLayoutByIndexTypeSelect; QComboBox *toolBoxLayoutForceDirectedSelect; QAction *zoomInAct,*zoomOutAct,*editRotateRightAct,*editRotateLeftAct, *editResetSlidersAct ; QToolButton *zoomInBtn,*zoomOutBtn,*rotateLeftBtn,*rotateRightBtn, *resetSlidersBtn ; QSlider *zoomSlider, *rotateSlider; QAction *networkNewAct, *networkOpenAct, *networkSaveAct, *networkSaveAsAct, *networkCloseAct, *networkPrintAct,*networkQuitAct; QAction *networkExportImageAct, *networkExportPajek, *networkExportPDFAct, *networkExportDLAct, *networkExportGWAct, *networkExportSMAct, *networkExportListAct, *networkExportDotAct; QAction *networkExportNodesCSVAct, *networkExportEdgesCSVAct, *networkExportNodesJSONAct, *networkExportEdgesJSONAct; QAction *networkImportPajekAct, *networkImportGMLAct, *networkImportAdjAct, *networkImportListAct, *networkImportGraphvizAct , *networkImportUcinetAct, *networkImportTwoModeSM; QAction *networkViewFileAct, *openTextEditorAct, *networkViewSociomatrixAct, *networkDataSetSelectAct, *networkViewSociomatrixPlotAct; QAction *networkRandomErdosRenyiAct; QAction *networkRandomGaussianAct; QAction *networkRandomLatticeRingAct; QAction *networkRandomScaleFreeAct; QAction *networkRandomSmallWorldAct; QAction *networkRandomRegularSameDegreeAct; QAction *networkRandomLatticeAct; QAction *networkWebCrawlerAct; QAction *editMouseModeScrollAct, *editMouseModeInteractiveAct; QAction *editNodeSelectNoneAct, *editNodeSelectAllAct; QAction *editNodeSelectedToStarAct, *editNodeSelectedToCycleAct; QAction *editNodeSelectedToLineAct, *editNodeSelectedToCliqueAct; QAction *editNodeFindAct,*editNodeAddAct, *editNodeRemoveAct; QAction *editNodePropertiesAct; QAction *editNodeEditSelectionInTableAct; QAction *editNodeSetPropertyForSelectionAct; QAction *editEdgeSetPropertyForSelectionAct; QAction *editEdgeAddAct, *editEdgeRemoveAct, *editEdgePropertiesAct; QAction *editNodeNumbersSizeAct, *editNodeLabelsSizeAct; QAction *editNodeSizeAllAct, *editNodeShapeAll; QAction *editEdgeLabelAct, *editEdgeColorAct, *editEdgeWeightAct; QAction *filterNodesByCentralityAct, *filterNodesByEgoNetworkAct, *filterNodesBySelectionAct, *filterNodesByAttributeAct, *filterByQueryBuilderAct, *filterNodesRestoreAllAct, *editFilterNodesIsolatesAct, *editFilterEdgesByWeightAct, *editFilterEdgesRestoreAllAct; QAction *editFilterEdgesUnilateralAct; QAction *editSubgraphExtractAct, *editSubgraphExtractFromSelectionAct; QAction *transformNodes2EdgesAct, *editEdgeSymmetrizeAllAct; QAction *editEdgeSymmetrizeStrongTiesAct, *editEdgeUndirectedAllAct; QAction *editEdgeDichotomizeAct; QAction *editNodeColorAll, *editEdgeColorAllAct, *editNodeNumbersColorAct,*editNodeLabelsColorAct, *editEdgesCocitationAct; QAction *optionsNodeNumbersVisibilityAct, *optionsNodeLabelsVisibilityAct, *optionsNodeNumbersInsideAct; QAction *optionsEdgeThicknessPerWeightAct, *optionsEdgeWeightNumbersAct; QAction *optionsEdgesVisibilityAct; QAction *optionsEdgeArrowsAct, *drawEdgesBezier, *optionsEdgeWeightConsiderAct; QAction *optionsEdgeLabelsAct; QAction *backgroundImageAct,*changeBackColorAct; QAction *fullScreenModeAct; QAction *openSettingsAct; QAction *viewDataTableAct; QAction *helpAboutApp, *helpAboutQt, *helpApp, *tipsApp; QAction *helpSystemInfoAct, *helpCheckUpdatesApp; QAction *netDensity, *analyzeGraphReciprocityAct, *analyzeGraphSymmetryAct; QAction *analyzeGraphDistanceAct, *averGraphDistanceAct, *analyzeGeodesicDistributionAct; QAction *analyzeMatrixDistancesGeodesicAct, *analyzeMatrixGeodesicsAct; QAction *analyzeGraphDiameterAct, *analyzeGraphEccentricityAct; QAction *analyzeStrEquivalenceTieProfileDissimilaritiesAct; QAction *analyzeGraphWalksAct,*analyzeGraphWalksTotalAct, *analyzeMatrixReachabilityAct, *analyzeGraphConnectednessAct; QAction *analyzeNodeConnectivityAct, *analyzeConnectivityAct; QAction *analyzeCommunitiesCliquesAct, *clusteringCoefAct, *analyzeCommunitiesTriadCensusAct; QAction *analyzeMatrixAdjTransposeAct, *analyzeMatrixAdjInvertAct; QAction *analyzeMatrixAdjCocitationAct; QAction *analyzeMatrixDegreeAct, *analyzeMatrixLaplacianAct; QAction *analyzeStrEquivalenceClusteringHierarchicalAct, *analyzeStrEquivalencePearsonAct; QAction *analyzeStrEquivalenceMatchesAct; QAction *cDegreeAct, *cInDegreeAct, *cClosenessAct, *cInfluenceRangeClosenessAct, *cBetweennessAct, *cInformationAct, *cEigenvectorAct, *cPageRankAct, *cStressAct, *cPowerAct, *cEccentAct, *cProximityPrestigeAct, *cKatzAct, *cBonacichAct; QAction *layoutRandomAct, *layoutRandomRadialAct, *layoutEgoRadialAct, *layoutGuidesAct; QAction *layoutRadialProminence_DC_Act, *layoutRadialProminence_DP_Act, *layoutRadialProminence_CC_Act, *layoutRadialProminence_SC_Act, *layoutRadialProminence_EC_Act, *layoutRadialProminence_PC_Act, *layoutRadialProminence_BC_Act, *layoutRadialProminence_IC_Act, *layoutRadialProminence_EVC_Act, *layoutRadialProminence_IRCC_Act,*layoutRadialProminence_PRP_Act, *layoutRadialProminence_PP_Act, *layoutRadialProminence_KATZ_Act, *layoutRadialProminence_BPC_Act; QAction *layoutLevelProminence_DC_Act, *layoutLevelProminence_DP_Act, *layoutLevelProminence_CC_Act, *layoutLevelProminence_SC_Act, *layoutLevelProminence_EC_Act, *layoutLevelProminence_PC_Act, *layoutLevelProminence_BC_Act, *layoutLevelProminence_IC_Act, *layoutLevelProminence_EVC_Act, *layoutLevelProminence_IRCC_Act,*layoutLevelProminence_PRP_Act, *layoutLevelProminence_PP_Act, *layoutLevelProminence_KATZ_Act, *layoutLevelProminence_BPC_Act; QAction *layoutNodeSizeProminence_DC_Act, *layoutNodeSizeProminence_DP_Act, *layoutNodeSizeProminence_CC_Act, *layoutNodeSizeProminence_SC_Act, *layoutNodeSizeProminence_EC_Act, *layoutNodeSizeProminence_PC_Act, *layoutNodeSizeProminence_BC_Act, *layoutNodeSizeProminence_IC_Act, *layoutNodeSizeProminence_EVC_Act, *layoutNodeSizeProminence_IRCC_Act,*layoutNodeSizeProminence_PRP_Act, *layoutNodeSizeProminence_PP_Act, *layoutNodeSizeProminence_KATZ_Act, *layoutNodeSizeProminence_BPC_Act; QAction *layoutNodeColorProminence_DC_Act, *layoutNodeColorProminence_DP_Act, *layoutNodeColorProminence_CC_Act, *layoutNodeColorProminence_SC_Act, *layoutNodeColorProminence_EC_Act, *layoutNodeColorProminence_PC_Act, *layoutNodeColorProminence_BC_Act, *layoutNodeColorProminence_IC_Act, *layoutNodeColorProminence_EVC_Act, *layoutNodeColorProminence_IRCC_Act,*layoutNodeColorProminence_PRP_Act, *layoutNodeColorProminence_PP_Act, *layoutNodeColorProminence_CLC_Act, *layoutNodeColorProminence_KATZ_Act, *layoutNodeColorProminence_BPC_Act; QAction *layoutNodeColorByComponentAct; QAction *strongColorationAct, *regularColorationAct; QAction *layoutFDP_Eades_Act, *layoutFDP_FR_Act; QAction *layoutFDP_KamadaKawai_Act; QAction *editRelationNextAct, *editRelationPreviousAct, *editRelationAddAct; QAction *editRelationRenameAct; QAction *recentFileActs[MaxRecentFiles]; QLabel *rightPanelNetworkTypeLCD ; QLabel *rightPanelEdgesLabel; QLabel *rightPanelNodesLCD; QLabel *rightPanelEdgesLCD; QLabel *rightPanelDensityLCD; QLabel *rightPanelSelectedNodesLCD; QLabel *rightPanelSelectedEdgesLCD; QLabel *rightPanelSelectedEdgesLabel; QLabel *rightPanelClickedNodeLCD; QLabel *rightPanelClickedNodeInDegreeLabel; QLabel *rightPanelClickedNodeInDegreeLCD; QLabel *rightPanelClickedNodeOutDegreeLabel; QLabel *rightPanelClickedNodeOutDegreeLCD; QLabel *rightPanelClickedEdgeNameLabel; QLabel *rightPanelClickedEdgeNameLCD; QLabel *rightPanelClickedEdgeWeightLabel; QLabel *rightPanelClickedEdgeWeightLCD; QLabel *rightPanelClickedEdgeReciprocalWeightLabel; QLabel *rightPanelClickedEdgeReciprocalWeightLCD; QPushButton *m_networkToggleBtn; QPushButton *m_selectionToggleBtn; QPushButton *m_clickedNodeToggleBtn; QPushButton *m_clickedEdgeToggleBtn; QPushButton *m_chartToggleBtn; QWidget *m_networkSection; QWidget *m_selectionSection; QWidget *m_clickedNodeSection; QWidget *m_clickedEdgeSection; }; #endif SocNetV-3.7/src/mainwindow/000077500000000000000000000000001524402210300156405ustar00rootroot00000000000000SocNetV-3.7/src/mainwindow/analyze/000077500000000000000000000000001524402210300173035ustar00rootroot00000000000000SocNetV-3.7/src/mainwindow/analyze/mainwindow_analyze_centrality.cpp000066400000000000000000001066631524402210300261600ustar00rootroot00000000000000/** * @file mainwindow_analyze_centrality.cpp * @brief Implements MainWindow Analyze menu centrality and prestige index reports. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/graphtablewidget.h" #include "forms/dialogsimilaritypearson.h" #include "forms/dialogsimilaritymatches.h" #include "forms/dialogdissimilarities.h" #include "forms/dialogclusteringhierarchical.h" #include "forms/dialogcentralitykatz.h" #include "forms/dialogcentralitybonacich.h" #include #include /** * Writes Out-Degree Centralities into a file, then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeCentralityDegree() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } askAboutEdgeWeights(false); const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-out-degree-" + dateTime + ext; bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityDegree(fn, considerWeights, dropIsolates, reportFormat); }, tr("Computing Degree Centralities. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { return; } statusMessage(tr("Opening Out-Degree Centralities report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Out-Degree Centralities report saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * Writes Closeness Centralities into a file, then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeCentralityCloseness() { qCDebug(lcMainWindow) << "MW::slotAnalyzeCentralityCloseness()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } askAboutEdgeWeights(); const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-closeness-" + dateTime + ext; const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityCloseness( fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat); }, tr("Computing Closeness Centralities. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) return; statusMessage(tr("Opening Closeness Centralities report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Closeness Centralities report saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief MainWindow::slotAnalyzeCentralityClosenessIR * Writes Centrality Closeness (based on Influence Range) indices into a file, * then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeCentralityClosenessIR() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-closeness-influence-range-" + dateTime + ext; askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityClosenessInfluenceRange( fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat); }, tr("Computing Influence Range Closeness Centralities. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { return; } statusMessage(tr("Opening Influence Range Closeness Centralities report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Influence Range Closeness Centralities report saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeCentralityBetweenness() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-betweenness-" + dateTime + ext; askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityBetweenness( fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat); }, tr("Computing Betweenness Centralities. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) return; statusMessage(tr("Opening Betweenness Centralities report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Betweenness Centralities report saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * Writes Degree Prestige indices (In-Degree Centralities) into a file, then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzePrestigeDegree() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } if (activeGraph->isSymmetric()) { slotHelpMessageToUser(USER_MSG_INFO, tr("Warning! Running Degree Prestige index on an undirected network."), tr("Warning! Running Degree Prestige index on an undirected network."), tr("This network is not directed (undirected graph). " "The Degree Prestige index counts inbound edges, " "therefore it is meaningful on directed networks. " "For undirected networks, such as this one, Degree Prestige is the same as Degree Centrality.")); } askAboutEdgeWeights(false); const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-prestige-degree-" + dateTime + ext; const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writePrestigeDegree(fn, considerWeights, dropIsolates, reportFormat); }, tr("Computing Degree Prestige. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { return; } statusMessage(tr("Opening Degree Prestige (in-degree) report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Degree Prestige (in-degree) report saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * Writes PageRank Prestige indices into a file, then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzePrestigePageRank() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-prestige-pagerank-" + dateTime + ext; askAboutEdgeWeights(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, dropIsolates, reportFormat, success]() { *success = activeGraph->writePrestigePageRank(fn, dropIsolates, reportFormat); }, tr("Computing PageRank Prestige. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { return; } statusMessage(tr("Opening PageRank Prestige report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("PageRank Prestige report saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief MainWindow::slotAnalyzePrestigeProximity * Writes Proximity Prestige indices into a file, then displays them. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzePrestigeProximity() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-prestige-proximity-" + dateTime + ext; askAboutEdgeWeights(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, dropIsolates, reportFormat, success]() { *success = activeGraph->writePrestigeProximity(fn, true, false, dropIsolates, reportFormat); }, tr("Computing Proximity Prestige. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { return; } statusMessage(tr("Opening Proximity Prestige report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Proximity Prestige report saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief MainWindow::slotAnalyzeCentralityInformation * Writes Informational Centralities into a file, then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeCentralityInformation() { qCDebug(lcMainWindow) << "MW::slotAnalyzeCentralityInformation()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } // WS14: recalibrated 2026-07-31 after L4 removed matrix.cpp's qDebug() formatting tax, which // (per real measurement) had been the dominant cost at the old n>200 threshold -- the previous // "2 minutes for 600 nodes on an i7 4790K" claim is now off by roughly two orders of magnitude // (600 nodes measures ~1.2s post-fix). The genuine O(n^3) matrix-inversion cost this warns // about is real and unaffected by that fix, though, and does become slow again at large n -- // measured (MacBook Pro M5, 24GB RAM): 1000 nodes ~3.1s, 2000 nodes ~15.8s, growing faster than // linearly beyond that. Threshold raised accordingly; wording updated to current numbers, to lead // with the more important caveat now that speed alone rarely justifies interrupting the user: // this computation is not yet cancellable once started (WS5 roadmap, issue I1). if (activeNodes() > 2000) { switch ( QMessageBox::critical( this, "Slow function warning", tr("Please note that this function can be SLOW on very large " "networks (n>2000), and cannot currently be canceled once started.

" "It computes the (n x n) matrix A with:
" "Aii=1+weighted_degree_ni
" "Aij=1 if (i,j)=0
" "Aij=1-wij if (i,j)=wij
" "then computes the inverse matrix C of A via LU decomposition -- an O(n³) " "operation.

" "How slow is this? On a modern machine, a 2,000-node network takes about 15 " "seconds; since the cost grows roughly cubically with network size, much larger " "networks (tens of thousands of nodes) can take several minutes or more.
" "Are you sure you want to continue?"), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel)) { case QMessageBox::Ok: break; case QMessageBox::Cancel: // Cancel was clicked return; break; default: // should never be reached break; } } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-information-" + dateTime + ext; askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, inverseWeightsFinal, reportFormat, success]() { *success = activeGraph->writeCentralityInformation( fn, considerWeights, inverseWeightsFinal, reportFormat); }, tr("Computing Information Centralities. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) return; statusMessage(tr("Opening Information Centralities report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Information Centralities report saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief Writes Eigenvector Centralities into a file, then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeCentralityEigenvector() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-eigenvector-" + dateTime + ext; askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; const bool dropIsolates = false; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityEigenvector( fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat); }, tr("Computing Eigenvector Centralities. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) return; statusMessage(tr("Opening Eigenvector Centralities report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Eigenvector Centralities report saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief MainWindow::slotAnalyzeCentralityStress * Writes Stress Centralities into a file, then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeCentralityStress() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-stress-" + dateTime + ext; askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityStress( fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat); }, tr("Computing Stress Centralities. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) return; statusMessage(tr("Opening Stress Centralities report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Stress Centralities report saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief MainWindow::slotAnalyzeCentralityPower * Writes Gil-Schmidt Power Centralities into a file, then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeCentralityPower() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-power-Gil-Schmidt-" + dateTime + ext; askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityPower( fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat); }, tr("Computing Power Centralities. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) return; statusMessage(tr("Opening Gil-Schmidt Power Centralities report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Gil-Schmidt Power Centralities report saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief MainWindow::slotAnalyzeCentralityKatz * Estimates the network's largest eigenvalue first, then prompts for the attenuation factor * alpha (dialog shows/enforces the real convergence bound), then writes Katz Centralities into * a file and displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. * * The eigenvalue estimate is itself a Graph call, so it goes through the same * runGraphOperationAsync() dispatch as the real computation (see * docs/README_DEVELOPER_NOTES.md's "Dispatching Long-Running Graph Operations") - the dialog is * only constructed in that operation's completion callback, once the bound is known. A * QMessageBox guard right before the real computation is a belt-and-suspenders backstop for the * spinbox clamp (e.g. exact-boundary floating-point edge cases); Graph::centralityKatz() would * otherwise still catch it too. */ void MainWindow::slotAnalyzeCentralityKatz() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto lambdaMax = std::make_shared(0); runGraphOperationAsync( [this, considerWeights, inverseWeightsFinal, dropIsolates, lambdaMax]() { *lambdaMax = activeGraph->estimateSpectralRadius( considerWeights, inverseWeightsFinal, dropIsolates); }, tr("Estimating the network's largest eigenvalue..."), [this, considerWeights, inverseWeightsFinal, dropIsolates, lambdaMax]() { if (activeGraph->progressCanceled()) return; const qreal bound = (*lambdaMax > 0) ? (1.0 / *lambdaMax) : 0.0; DialogCentralityKatz dlg(this); dlg.setAlphaBound(bound); connect(&dlg, &DialogCentralityKatz::userChoices, this, [this, considerWeights, inverseWeightsFinal, dropIsolates, bound](const qreal alpha) { if (bound > 0 && qAbs(alpha) >= bound) { QMessageBox::warning( this, tr("Alpha too large"), tr("Alpha must be smaller than %1 (1 / this network's largest " "eigenvalue), or Katz Centrality does not converge. " "Please choose a smaller value.").arg(bound)); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-katz-" + dateTime + ext; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, alpha, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityKatz( fn, alpha, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat); }, tr("Computing Katz Centralities. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) return; statusMessage(tr("Opening Katz Centralities report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Katz Centralities report saved as: ") + QDir::toNativeSeparators(fn)); }); }); dlg.exec(); }); } /** * @brief MainWindow::slotAnalyzeCentralityBonacich * Estimates the network's largest eigenvalue first, then prompts for the parameters alpha and * beta (dialog shows/enforces the real convergence bound on beta), then writes Bonacich Power * Centralities into a file and displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. * * Same async-sequencing rationale as slotAnalyzeCentralityKatz(): the eigenvalue estimate is * itself a Graph call, so it goes through runGraphOperationAsync() too, and the dialog is only * constructed once that bound is known. */ void MainWindow::slotAnalyzeCentralityBonacich() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto lambdaMax = std::make_shared(0); runGraphOperationAsync( [this, considerWeights, inverseWeightsFinal, dropIsolates, lambdaMax]() { *lambdaMax = activeGraph->estimateSpectralRadius( considerWeights, inverseWeightsFinal, dropIsolates); }, tr("Estimating the network's largest eigenvalue..."), [this, considerWeights, inverseWeightsFinal, dropIsolates, lambdaMax]() { if (activeGraph->progressCanceled()) return; const qreal bound = (*lambdaMax > 0) ? (1.0 / *lambdaMax) : 0.0; DialogCentralityBonacich dlg(this); dlg.setBetaBound(bound); connect(&dlg, &DialogCentralityBonacich::userChoices, this, [this, considerWeights, inverseWeightsFinal, dropIsolates, bound](const qreal alpha, const qreal beta) { if (bound > 0 && qAbs(beta) >= bound) { QMessageBox::warning( this, tr("Beta too large"), tr("Beta must be smaller in absolute value than %1 (1 / this network's " "largest eigenvalue), or Bonacich Power Centrality does not converge. " "Please choose a smaller value.").arg(bound)); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-bonacich-" + dateTime + ext; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, alpha, beta, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityBonacich( fn, alpha, beta, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat); }, tr("Computing Bonacich Power Centralities. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) return; statusMessage(tr("Opening Bonacich Power Centralities report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Bonacich Power Centralities report saved as: ") + QDir::toNativeSeparators(fn)); }); }); dlg.exec(); }); } /** * @brief MainWindow::slotAnalyzeCentralityEccentricity * Writes Eccentricity Centralities into a file, then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeCentralityEccentricity() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-eccentricity-" + dateTime + ext; askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityEccentricity( fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat); }, tr("Computing Eccentricity Centralities. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) return; statusMessage(tr("Opening Closeness Centralities report...")); if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Eccentricity Centralities report saved as: ") + QDir::toNativeSeparators(fn)); }); } SocNetV-3.7/src/mainwindow/analyze/mainwindow_analyze_clustering.cpp000066400000000000000000000136331524402210300261530ustar00rootroot00000000000000/** * @file mainwindow_analyze_clustering.cpp * @brief Implements MainWindow Analyze menu clustering coefficient, triad census, and reciprocity reports. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/graphtablewidget.h" #include "forms/dialogsimilaritypearson.h" #include "forms/dialogsimilaritymatches.h" #include "forms/dialogdissimilarities.h" #include "forms/dialogclusteringhierarchical.h" #include #include /** * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeReciprocity() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-reciprocity-" + dateTime + ext; askAboutEdgeWeights(); bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, reportFormat, success]() { *success = activeGraph->writeReciprocity(fn, considerWeights, reportFormat); }, tr("Computing Reciprocity. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { statusMessage(tr("Computation canceled, or could not write to file.")); return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Reciprocity report saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief Calls Graph::writeClusteringCoefficient() to write Clustering Coefficients * into a file, and displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeClusteringCoefficient() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-clustering-coefficient-" + dateTime + ext; bool considerWeights = true; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, reportFormat, success]() { *success = activeGraph->writeClusteringCoefficient(fn, considerWeights, reportFormat); }, tr("Computing Clustering Coefficients. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Clustering Coefficients saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * Calls Graph to compute and write a triad census into a file, then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeCommunitiesTriadCensus() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-triad-census-" + dateTime + ext; bool considerWeights = true; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, reportFormat, success]() { *success = activeGraph->writeTriadCensus(fn, considerWeights, reportFormat); }, tr("Computing Triad Census. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Triad Census saved as: ") + QDir::toNativeSeparators(fn)); }); } SocNetV-3.7/src/mainwindow/analyze/mainwindow_analyze_cohesion.cpp000066400000000000000000000435321524402210300256040ustar00rootroot00000000000000/** * @file mainwindow_analyze_cohesion.cpp * @brief Implements MainWindow Analyze menu cohesion reports: connectedness, node/graph connectivity, clique census. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/graphtablewidget.h" #include "forms/dialogsimilaritypearson.h" #include "forms/dialogsimilaritymatches.h" #include "forms/dialogdissimilarities.h" #include "forms/dialogclusteringhierarchical.h" #include #include /** * @brief Called when user selects something in the Cohesion * selectbox of the toolbox to compute basic graph theoretic / network properties * @param selectedIndex */ void MainWindow::toolBoxAnalysisCohesionSelectChanged(const int &selectedIndex) { qCDebug(lcMainWindow) << "selected cohesion analysis, text index: " << selectedIndex; switch (selectedIndex) { case 0: break; case 1: slotAnalyzeReciprocity(); break; case 2: slotAnalyzeSymmetryCheck(); break; case 3: slotAnalyzeDistance(); break; case 4: slotAnalyzeDistanceAverage(); break; case 5: slotAnalyzeGeodesicDistribution(); break; case 6: slotAnalyzeMatrixDistances(); break; case 7: slotAnalyzeMatrixGeodesics(); break; case 8: slotAnalyzeEccentricity(); break; case 9: slotAnalyzeDiameter(); break; case 10: slotAnalyzeConnectedness(); break; case 11: slotAnalyzeWalksLength(); break; case 12: slotAnalyzeWalksTotal(); break; case 13: slotAnalyzeReachabilityMatrix(); break; case 14: slotAnalyzeClusteringCoefficient(); break; case 15: slotAnalyzeNodeConnectivity(); break; case 16: slotAnalyzeConnectivity(); break; }; qCDebug(lcMainWindow) << "Calling initComboBoxes() "; initComboBoxes(); } /** * @brief Called when the user selects something in the Communities selectbox * of the toolbox * @param selectedIndex * */ void MainWindow::toolBoxAnalysisCommunitiesSelectChanged(const int &selectedIndex) { qCDebug(lcMainWindow) << "selected community analysis, text index: " << selectedIndex; switch (selectedIndex) { case 0: break; case 1: slotAnalyzeCommunitiesCliqueCensus(); break; case 2: slotAnalyzeCommunitiesTriadCensus(); break; }; qCDebug(lcMainWindow) << "Calling initComboBoxes() "; initComboBoxes(); } /** * @brief Reports the network connectedness */ void MainWindow::slotAnalyzeConnectedness() { qCDebug(lcMainWindow) << "MW::slotAnalyzeConnectedness()"; int N = activeGraph->vertices(); if (!N) { // null network with empty graph is connected slotHelpMessageToUser( USER_MSG_INFO, tr("This empty network is considered connected!"), tr("Empty network is considered connected!"), tr("A null network (empty graph) is considered connected.")); } else if (N == 1) { // 1-actor network with singleton graph is connected slotHelpMessageToUser( USER_MSG_INFO, tr("This 1-actor network is considered connected!"), tr("This 1-actor network is considered connected!"), tr("A 1-actor network (singleton graph) is always considered connected.")); } else { // Directed graphs have two distinct notions of connectivity (weak: ignores edge // direction; strong: requires all-pairs directed reachability) - ask which one to // check rather than silently picking one. Undirected graphs have no such ambiguity: // the two notions coincide, so there's nothing to ask. bool useStrong = false; if (activeGraph->isDirected()) { bool ok = false; const QStringList connectivityTypes = { tr("Weak (ignores edge direction)"), tr("Strong (respects edge direction)") }; const QString choice = QInputDialog::getItem( this, tr("Connectedness"), tr("This is a directed network. Which kind of connectivity do you want to check?\n\n" "Weak: treats every edge as bidirectional. Answers \"how many disconnected " "islands are there?\"\n\n" "Strong: respects edge direction. Every node must be able to reach, and be " "reached from, every other node."), connectivityTypes, 0, false, &ok); if (!ok) { statusMessage(tr("Connectedness check cancelled.")); return; } useStrong = (connectivityTypes.indexOf(choice) == 1); } const int components = useStrong ? activeGraph->graphStronglyConnectedComponents() : activeGraph->graphWeaklyConnectedComponents(); const bool isConnected = (components == 1); qCDebug(lcMainWindow) << "MW::slotAnalyzeConnectedness result connected:" << isConnected << "components:" << components << "strong:" << useStrong; const QString compStr = QString::number(components); if (isConnected) { if (activeGraph->isDirected()) { if (useStrong) { slotHelpMessageToUser( USER_MSG_INFO, tr("This directed network is strongly connected (1 component)."), tr("This directed network is strongly connected."), tr("Every node can reach, and be reached from, every other node " "via directed paths.")); } else { slotHelpMessageToUser( USER_MSG_INFO, tr("This directed network is weakly connected (1 component)."), tr("This directed network is weakly connected."), tr("All nodes belong to a single weakly connected component. " "Weak connectivity ignores edge direction - run Connectedness again " "and choose Strong to check directed reachability.")); } } else { slotHelpMessageToUser( USER_MSG_INFO, tr("This undirected network is connected (1 component)."), tr("This undirected network is connected."), tr("All nodes belong to a single connected component. " "There is a path between every pair of nodes.")); } } else { if (activeGraph->isDirected()) { if (useStrong) { slotHelpMessageToUser( USER_MSG_INFO, tr("This directed network is not strongly connected (%1 components).").arg(compStr), tr("This directed network is not strongly connected."), tr("There are %1 strongly connected components. " "Some node pairs cannot reach each other via directed paths.") .arg(compStr)); } else { slotHelpMessageToUser( USER_MSG_INFO, tr("This directed network is disconnected (%1 components).").arg(compStr), tr("This directed network is disconnected."), tr("There are %1 disconnected components. " "Some node pairs are unreachable from each other, even ignoring " "edge direction. " "Use Layout > Node Color by Connected Component " "to visualize the components.") .arg(compStr)); } } else { slotHelpMessageToUser( USER_MSG_INFO, tr("This undirected network is disconnected (%1 components).").arg(compStr), tr("This undirected network is disconnected."), tr("There are %1 disconnected components. " "Some node pairs have no path between them. " "Use Layout > Node Color by Connected Component " "to visualize the components.") .arg(compStr)); } } } } /** * @brief Handles requests to compute the local vertex connectivity between two user-specified * nodes: the minimum number of other nodes that must be removed to disconnect them. */ void MainWindow::slotAnalyzeNodeConnectivity() { if (!activeNodes() || !activeEdges()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } bool ok1 = false, ok2 = false; const int min = activeGraph->vertexNumberMin(); const int max = activeGraph->vertexNumberMax(); const int sourceNum = QInputDialog::getInt( this, tr("Node Connectivity"), tr("Select source node (%1..%2):") .arg(QString::number(min)) .arg(QString::number(max)), min, min, max, 1, &ok1); if (!ok1) { statusMessage(tr("Node connectivity calculation cancelled.")); return; } const int targetNum = QInputDialog::getInt( this, tr("Node Connectivity"), tr("Select target node (%1..%2):") .arg(QString::number(min), QString::number(max)), min, min, max, 1, &ok2); if (!ok2) { statusMessage(tr("Node connectivity calculation cancelled.")); return; } bool respectDirection = false; if (activeGraph->isDirected()) { bool ok = false; const QStringList connectivityTypes = { tr("Weak (ignores edge direction)"), tr("Strong (respects edge direction)") }; const QString choice = QInputDialog::getItem( this, tr("Node Connectivity"), tr("This is a directed network. Which kind of connectivity do you want to check?\n\n" "Weak: treats every edge as bidirectional.\n\n" "Strong: respects edge direction - counts nodes needed to block directed paths " "from the source to the target."), connectivityTypes, 0, false, &ok); if (!ok) { statusMessage(tr("Node connectivity calculation cancelled.")); return; } respectDirection = (connectivityTypes.indexOf(choice) == 1); } qCDebug(lcMainWindow) << "Computing node connectivity:" << sourceNum << "->" << targetNum << "respectDirection:" << respectDirection; auto result = std::make_shared(); runGraphOperationAsync( [this, sourceNum, targetNum, respectDirection, result]() { *result = activeGraph->graphNodeConnectivity(sourceNum, targetNum, respectDirection); }, tr("Computing node connectivity. Please wait..."), [this, sourceNum, targetNum, result]() { switch (result->status) { case Graph::NodeConnectivityStatus::Adjacent: slotHelpMessageToUser( USER_MSG_INFO, tr("Nodes %1 and %2 are directly connected.").arg(sourceNum).arg(targetNum), tr("Nodes %1 and %2 are directly connected.").arg(sourceNum).arg(targetNum), tr("Node connectivity is not defined for directly connected nodes: " "the edge between them means no removal of other nodes can ever " "separate them.")); break; case Graph::NodeConnectivityStatus::Ok: if (result->value > 0) { slotHelpMessageToUser( USER_MSG_INFO, tr("Node Connectivity: %1").arg(result->value), tr("Node Connectivity: %1").arg(result->value), tr("At least %1 other node(s) must be removed to disconnect " "%2 from %3.") .arg(result->value) .arg(targetNum) .arg(sourceNum)); } else { slotHelpMessageToUser( USER_MSG_INFO, tr("Node Connectivity: %1").arg(QString("\xE2\x88\x9E")), tr("Nodes %1 and %2 are already disconnected.").arg(sourceNum).arg(targetNum), tr("%1 cannot reach %2 at all, so no node removal is needed.") .arg(sourceNum) .arg(targetNum)); } break; case Graph::NodeConnectivityStatus::Invalid: default: slotHelpMessageToUser( USER_MSG_CRITICAL, tr("Invalid node connectivity request."), tr("Invalid node connectivity request."), tr("Source and target must be two distinct, existing nodes.")); break; } }); } /** * @brief Handles requests to compute the network's overall vertex connectivity: the minimum, * over every pair of nodes, of their local vertex connectivity. */ void MainWindow::slotAnalyzeConnectivity() { const int N = activeGraph->vertices(); if (N < 2) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } bool respectDirection = false; if (activeGraph->isDirected()) { bool ok = false; const QStringList connectivityTypes = { tr("Weak (ignores edge direction)"), tr("Strong (respects edge direction)") }; const QString choice = QInputDialog::getItem( this, tr("Graph Connectivity"), tr("This is a directed network. Which kind of connectivity do you want to check?\n\n" "Weak: treats every edge as bidirectional.\n\n" "Strong: respects edge direction."), connectivityTypes, 0, false, &ok); if (!ok) { statusMessage(tr("Graph connectivity calculation cancelled.")); return; } respectDirection = (connectivityTypes.indexOf(choice) == 1); } qCDebug(lcMainWindow) << "Computing graph connectivity, respectDirection:" << respectDirection; auto kappa = std::make_shared(0); runGraphOperationAsync( [this, respectDirection, kappa]() { *kappa = activeGraph->graphConnectivity(respectDirection); }, tr("Computing graph connectivity. Please wait..."), [this, kappa]() { if (*kappa > 0) { slotHelpMessageToUser( USER_MSG_INFO, tr("Graph Connectivity: %1").arg(*kappa), tr("Graph Connectivity: %1").arg(*kappa), tr("At least %1 node(s) must be removed to disconnect some pair " "of nodes in this network - its worst-case robustness to " "node removal.") .arg(*kappa)); } else { slotHelpMessageToUser( USER_MSG_INFO, tr("Graph Connectivity: 0"), tr("This network is already disconnected."), tr("Some pair of nodes is already unreachable, so no node removal " "is needed to disconnect the network further.")); } }); } /** * @brief Calls Graph:: writeCliqueCensus() to write the Clique Census * into a file, then displays it. */ void MainWindow::slotAnalyzeCommunitiesCliqueCensus() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } if (activeNodes() == 1) { slotHelpMessageToUserError("Only one node is present, therefore 1 clique"); return; } QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-clique-census-" + dateTime + ".html"; bool considerWeights = true; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, success]() { *success = activeGraph->writeCliqueCensus(fn, considerWeights); }, tr("Computing Clique Census. Please wait..."), [this, fn, success]() { if (!*success) { return; } if (appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Clique Census saved as: ") + QDir::toNativeSeparators(fn)); }); } SocNetV-3.7/src/mainwindow/analyze/mainwindow_analyze_distance.cpp000066400000000000000000000464171524402210300255740ustar00rootroot00000000000000/** * @file mainwindow_analyze_distance.cpp * @brief Implements MainWindow Analyze menu distance-based reports: diameter, distance, average distance, eccentricity, geodesic distribution, walks. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/graphtablewidget.h" #include "forms/dialogsimilaritypearson.h" #include "forms/dialogsimilaritymatches.h" #include "forms/dialogdissimilarities.h" #include "forms/dialogclusteringhierarchical.h" #include #include /** * @brief Handles requests to compute the graph/geodesic distance between two user-specified nodes * * The geodesic distance of two nodes is the length of the shortest path between them. * */ void MainWindow::slotAnalyzeDistance() { if (!activeNodes() || !activeEdges()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } bool ok1 = false, ok2 = false; int min = 1, max = 1, sourceNum = -1, targetNum = -1; min = activeGraph->vertexNumberMin(); max = activeGraph->vertexNumberMax(); sourceNum = QInputDialog::getInt( this, tr("Distance between two nodes"), tr("Select source node (%1..%2):") .arg(QString::number(min)) .arg(QString::number(max)), min, min, max, 1, &ok1); if (!ok1) { statusMessage("Distance calculation operation cancelled."); return; } targetNum = QInputDialog::getInt( this, tr("Distance between two nodes"), tr("Select target node (%1..%2):") .arg(QString::number(min), QString::number(max)), min, min, max, 1, &ok2); if (!ok2) { statusMessage(tr("Distance calculation operation cancelled.")); return; } qCDebug(lcMainWindow) << "Computing geodesic distance:" << sourceNum << "->" << targetNum; if (activeGraph->isSymmetric() && sourceNum > targetNum) { qSwap(sourceNum, targetNum); } askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto distanceGeodesic = std::make_shared(0); runGraphOperationAsync( [this, sourceNum, targetNum, considerWeights, inverseWeightsFinal, distanceGeodesic]() { *distanceGeodesic = activeGraph->graphDistanceGeodesic( sourceNum, targetNum, considerWeights, inverseWeightsFinal); }, tr("Computing geodesic distance. Please wait..."), [this, sourceNum, targetNum, considerWeights, inverseWeightsFinal, distanceGeodesic]() { if (*distanceGeodesic > 0 && *distanceGeodesic < RAND_MAX) { qCDebug(lcMainWindow) << "geodesic distance" << sourceNum << "->" << targetNum << "=" << *distanceGeodesic; // Reconstruct the actual shortest path so the user sees the intermediate nodes. // Cheap regardless of network size (single-source BFS/Dijkstra, not the full // APSP the call above just triggered) - safe to run synchronously here. const QList path = activeGraph->graphGeodesicShortestPath( sourceNum, targetNum, considerWeights, inverseWeightsFinal); // Format the path as "v1 β†’ v2 β†’ … β†’ vN" using node labels where available. QString pathStr; if (path.size() >= 2) { for (int i = 0; i < path.size(); ++i) { if (i > 0) pathStr += " \xE2\x86\x92 "; // β†’ (UTF-8 right arrow) const QString lbl = activeGraph->vertexLabel(path[i]).trimmed(); pathStr += lbl.isEmpty() ? QString::number(path[i]) : lbl; } } slotHelpMessageToUser( USER_MSG_INFO, tr("Geodesic Distance: %1").arg(*distanceGeodesic), tr("Geodesic Distance: %1").arg(*distanceGeodesic), tr("Nodes %1 and %2 are connected. The shortest path has length %3.\n\n" "Shortest path:\n%4") .arg(sourceNum) .arg(targetNum) .arg(*distanceGeodesic) .arg(pathStr.isEmpty() ? tr("(path unavailable)") : pathStr)); if (path.size() >= 2) graphicsWidget->selectPath(path); } else { qCDebug(lcMainWindow) << "geodesic distance" << sourceNum << "->" << targetNum << "is infinite."; slotHelpMessageToUser( USER_MSG_INFO, tr("Geodesic Distance: %1").arg(QString("\xE2\x88\x9E")), tr("Geodesic Distance: %1").arg(QString("\xE2\x88\x9E")), tr("Nodes %1 and %2 are not connected. " "In this case, their geodesic distance is considered to be infinite.") .arg(sourceNum) .arg(targetNum)); } }); } /** * @brief Displays the network diameter (largest geodesic) */ void MainWindow::slotAnalyzeDiameter() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto netDiameter = std::make_shared(0); auto isWeighted = std::make_shared(false); runGraphOperationAsync( [this, considerWeights, inverseWeightsFinal, netDiameter, isWeighted]() { *netDiameter = activeGraph->graphDiameter(considerWeights, inverseWeightsFinal); *isWeighted = activeGraph->isWeighted(); }, tr("Computing graph diameter. Please wait..."), [this, considerWeights, netDiameter, isWeighted]() { if (*isWeighted) { if (considerWeights) { slotHelpMessageToUser( USER_MSG_INFO, tr("Network diameter computed."), tr("Network diameter computed. \n\n" "D = %1") .arg(*netDiameter), tr("The diameter of a network is the maximum geodesic distance " "(maximum shortest path length) between any two nodes.\n\n" "Note, since this is a weighted network, " "the diameter can be greater than N.")); } else { slotHelpMessageToUser( USER_MSG_INFO, tr("Network diameter computed."), tr("Network diameter computed. \n\n" "D = %1") .arg(*netDiameter), tr("The diameter of a network is the maximum geodesic distance " "(maximum shortest path length) between any two nodes.\n\n" "Note, edge weights were disregarded during the computation. " "This is the diameter of the corresponding network without weights.")); } } else slotHelpMessageToUser( USER_MSG_INFO, tr("Network diameter computed."), tr("Network diameter computed. \n\n" "D = %1") .arg(*netDiameter), tr("The diameter of a network is the maximum geodesic distance " "(maximum shortest path length) between any two nodes.\n\n" "Note, since this is a non-weighted network, the diameter is always smaller than N-1.")); }); } /** * @brief Displays the average shortest path length (average graph distance) */ void MainWindow::slotAnalyzeDistanceAverage() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto averGraphDistance = std::make_shared(0); auto isConnected = std::make_shared(false); runGraphOperationAsync( [this, considerWeights, inverseWeightsFinal, dropIsolates, averGraphDistance, isConnected]() { *averGraphDistance = activeGraph->graphDistanceGeodesicAverage( considerWeights, inverseWeightsFinal, dropIsolates); // Cheap here: graphDistanceGeodesicAverage() just triggered the full APSP with // these exact params, so isConnected()'s cache hit is immediate - not a second // expensive computation. *isConnected = activeGraph->isConnected(); }, tr("Computing Average Graph Distance. Please wait..."), [this, averGraphDistance, isConnected]() { if (*isConnected) { slotHelpMessageToUser( USER_MSG_INFO, tr("Average graph distance computed."), tr("Average graph distance computed. \n\n" "d = %1") .arg(*averGraphDistance), tr("The average graph distance is the average length of shortest paths (geodesics) " "for all possible pairs of nodes.\n\n" "The average distance in this connected network " "is the sum of pair-wise distances divided by N * (N - 1).")); } else { slotHelpMessageToUser( USER_MSG_INFO, tr("Average distance computed."), tr("Average distance computed. \n\n" "d = %1") .arg(*averGraphDistance), tr("The average graph distance is the average length of shortest paths (geodesics) " "for all possible pairs of nodes.\n\n" "The average distance in this disconnected network " "is the sum of pair-wise distances divided by the number of existing geodesics.")); } }); } /** * @brief Computes and displays the distribution of geodesic distances across * all ordered pairs of connected nodes as an HTML report. * * Triggered only by explicit user action (Analyze β†’ Cohesion β†’ Geodesic Distance * Distribution). Uses the APSP cache when available; otherwise runs the full * APSP computation first. Writes the result to an HTML file and opens it in the * system browser or the built-in TextEditor according to app settings. */ void MainWindow::slotAnalyzeGeodesicDistribution() { qCDebug(lcMainWindow) << "MW::slotAnalyzeGeodesicDistribution()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } askAboutEdgeWeights(); QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-geodesic-distribution-" + dateTime + ".html"; const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, inverseWeightsFinal, success]() { *success = activeGraph->writeGeodesicDistribution(fn, considerWeights, inverseWeightsFinal); }, tr("Computing geodesic distance distribution. Please wait..."), [this, fn, success]() { if (!*success) { statusMessage(tr("Error: could not write geodesic distribution report.")); return; } if (appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Geodesic distance distribution saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * Writes Eccentricity indices into a file, then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeEccentricity() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-eccentricity-" + dateTime + ext; askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat, success]() { *success = activeGraph->writeEccentricity( fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat); }, tr("Computing Eccentricity. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) return; if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Eccentricities saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * Calculate and print the number of walks of a given length , between each pair of nodes. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeWalksLength() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } bool ok = false; int length = QInputDialog::getInt( this, "Number of walks", tr("Select desired length of walk: (2 to %1)").arg(activeNodes() - 1), 2, 2, activeNodes() - 1, 1, &ok); if (!ok) { statusMessage("Cancelled."); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-walks-length-" + QString::number(length) + "-" + dateTime + ext; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, length, reportFormat, success]() { *success = activeGraph->writeMatrixWalks(fn, length, false, reportFormat); }, tr("Computing walks of length %1. Please wait...").arg(length), [this, fn, length, reportFormat, success]() { if (!*success) { statusMessage(tr("Computation canceled, or could not write to file.")); return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Walks of length %1 matrix saved as: ").arg(length) + QDir::toNativeSeparators(fn)); }); } /** * @brief Calculate and print the total number of walks of any length, between each pair of nodes. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeWalksTotal() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } if (activeNodes() > 50) { switch (QMessageBox::critical( this, "Slow function warning", tr("Please note that this function is VERY SLOW on large networks (n>50), " "since it will calculate all powers of the sociomatrix up to n-1 " "in order to find out all possible walks. \n\n" "If you need to make a simple reachability test, " "we advise to use the Reachability Matrix function instead. \n\n" "Are you sure you want to continue?"), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel)) { case QMessageBox::Ok: break; case QMessageBox::Cancel: // Cancel was clicked return; break; default: // should never be reached break; } } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-walks-total-" + dateTime + ext; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, reportFormat, success]() { *success = activeGraph->writeMatrixWalks(fn, 0, false, reportFormat); }, tr("Computing total walks matrix. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { statusMessage(tr("Computation canceled, or could not write to file.")); return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage("Total walks matrix saved as: " + QDir::toNativeSeparators(fn)); }); } SocNetV-3.7/src/mainwindow/analyze/mainwindow_analyze_matrices.cpp000066400000000000000000000413731524402210300256050ustar00rootroot00000000000000/** * @file mainwindow_analyze_matrices.cpp * @brief Implements MainWindow Analyze menu matrix reports: adjacency (cocitation/inverse/transpose), degree, distances, geodesics, laplacian, reachability, and the symmetry check. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/graphtablewidget.h" #include "forms/dialogsimilaritypearson.h" #include "forms/dialogsimilaritymatches.h" #include "forms/dialogdissimilarities.h" #include "forms/dialogclusteringhierarchical.h" #include #include /** * @brief Called when user selects something in the Matrices * selectbox of the toolbox * @param selectedIndex */ void MainWindow::toolBoxAnalysisMatricesSelectChanged(const int &selectedIndex) { qCDebug(lcMainWindow) << "selected matrix analysis, text index: " << selectedIndex; switch (selectedIndex) { case 0: break; case 1: slotNetworkViewSociomatrix(); break; case 2: slotNetworkViewSociomatrixPlotText(); break; case 3: slotAnalyzeMatrixAdjacencyInverse(); break; case 4: slotAnalyzeMatrixAdjacencyTranspose(); break; case 5: slotAnalyzeMatrixAdjacencyCocitation(); break; case 6: slotAnalyzeMatrixDegree(); break; case 7: slotAnalyzeMatrixLaplacian(); break; }; qCDebug(lcMainWindow) << "Calling initComboBoxes() "; initComboBoxes(); } void MainWindow::slotAnalyzeSymmetryCheck() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } if (activeGraph->isSymmetric()) { slotHelpMessageToUser( USER_MSG_INFO, tr("Symmetric network."), tr("The adjacency matrix is symmetric.")); } else { slotHelpMessageToUser( USER_MSG_INFO, tr("Non symmetric network."), tr("The adjacency matrix is not symmetric.")); } statusMessage(QString(tr("Ready"))); } /** * @brief Writes the adjacency matrix inverse * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeMatrixAdjacencyInverse() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-adjacency-inverse-" + dateTime + ext; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, reportFormat, success]() { *success = activeGraph->writeMatrix(fn, MATRIX_ADJACENCY_INVERSE, true, false, false, "Rows", false, reportFormat); }, tr("Inverting adjacency matrix. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { statusMessage(tr("Computation canceled.")); return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Inverse matrix saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief Writes the transpose adjacency matrix * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeMatrixAdjacencyTranspose() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-adjacency-transpose-" + dateTime + ext; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, reportFormat, success]() { *success = activeGraph->writeMatrix(fn, MATRIX_ADJACENCY_TRANSPOSE, true, false, false, "Rows", false, reportFormat); }, tr("Transposing adjacency matrix. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { statusMessage(tr("Computation canceled.")); return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Transpose adjacency matrix saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief Writes the cocitation matrix * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeMatrixAdjacencyCocitation() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-cocitation-" + dateTime + ext; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, reportFormat, success]() { *success = activeGraph->writeMatrix(fn, MATRIX_COCITATION, true, false, false, "Rows", false, reportFormat); }, tr("Computing Cocitation matrix. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { statusMessage(tr("Computation canceled.")); return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Cocitation matrix saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief Writes the degree matrix of the graph * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeMatrixDegree() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-degree-" + dateTime + ext; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, reportFormat, success]() { *success = activeGraph->writeMatrix(fn, MATRIX_DEGREE, true, false, false, "Rows", false, reportFormat); }, tr("Computing Degree matrix. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { statusMessage(tr("Computation canceled.")); return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Degree matrix saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief Writes the Laplacian matrix of the graph * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeMatrixLaplacian() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } qCDebug(lcMainWindow) << "MW:slotAnalyzeMatrixLaplacian()"; const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-laplacian-" + dateTime + ext; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, reportFormat, success]() { *success = activeGraph->writeMatrix(fn, MATRIX_LAPLACIAN, true, false, false, "Rows", false, reportFormat); }, tr("Computing Laplacian matrix. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { statusMessage(tr("Computation canceled.")); return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Laplacian matrix saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief Invokes calculation of the matrix of geodesic distances for the loaded network, then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeMatrixDistances() { qCDebug(lcMainWindow) << "Request to compute the matrix of geodesic distances. Please wait..."; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-geodesic-distances-" + dateTime + ext; askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat, success]() { *success = activeGraph->writeMatrix(fn, MATRIX_DISTANCES, considerWeights, inverseWeightsFinal, dropIsolates, "Rows", false, reportFormat); }, tr("Computing geodesic distances. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { statusMessage(tr("Computation canceled.")); return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Geodesic Distances matrix saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief Invokes calculation of the geodedics matrix (the number of shortest paths * between each pair of nodes in the loaded network), then displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeMatrixGeodesics() { qCDebug(lcMainWindow) << "Request to compute the matrix of geodesics. Please wait..."; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-geodesics-" + dateTime + ext; askAboutEdgeWeights(); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, inverseWeightsFinal, dropIsolates, reportFormat, success]() { *success = activeGraph->writeMatrix(fn, MATRIX_GEODESICS, considerWeights, inverseWeightsFinal, dropIsolates, "Rows", false, reportFormat); }, tr("Computing geodesics (number of shortest paths) for each pair. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { statusMessage(tr("Computation canceled.")); return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Geodesics Matrix saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * Calls Graph::writeMatrix(fn, MATRIX_REACHABILITY) to calculate and print * the Reachability Matrix of the network. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeReachabilityMatrix() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-reachability-" + dateTime + ext; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, reportFormat, success]() { *success = activeGraph->writeMatrix(fn, MATRIX_REACHABILITY, true, false, false, "Rows", false, reportFormat); }, tr("Computing reachability matrix. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { statusMessage(tr("Computation canceled.")); return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Reachability matrix saved as: ") + QDir::toNativeSeparators(fn)); }); } SocNetV-3.7/src/mainwindow/analyze/mainwindow_analyze_prominence.cpp000066400000000000000000000154741524402210300261400ustar00rootroot00000000000000/** * @file mainwindow_analyze_prominence.cpp * @brief Implements MainWindow Analyze menu prominence distribution chart update and its toolbox combo box handler. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/graphtablewidget.h" #include "forms/dialogsimilaritypearson.h" #include "forms/dialogsimilaritymatches.h" #include "forms/dialogdissimilarities.h" #include "forms/dialogclusteringhierarchical.h" #include #include /** * @brief Called when user selects something in the Prominence selectbox * of the toolbox * @param selectedIndex * */ void MainWindow::toolBoxAnalysisProminenceSelectChanged(const int &selectedIndex) { qCDebug(lcMainWindow) << "selected prominence analysis, text index: " << selectedIndex; switch (selectedIndex) { case 0: break; case 1: slotAnalyzeCentralityDegree(); break; case 2: slotAnalyzeCentralityCloseness(); break; case 3: slotAnalyzeCentralityClosenessIR(); break; case 4: slotAnalyzeCentralityBetweenness(); break; case 5: slotAnalyzeCentralityStress(); break; case 6: slotAnalyzeCentralityEccentricity(); break; case 7: slotAnalyzeCentralityPower(); break; case 8: slotAnalyzeCentralityInformation(); break; case 9: slotAnalyzeCentralityEigenvector(); break; case 10: slotAnalyzeCentralityKatz(); break; case 11: slotAnalyzeCentralityBonacich(); break; case 12: slotAnalyzePrestigeDegree(); break; case 13: slotAnalyzePrestigePageRank(); break; case 14: slotAnalyzePrestigeProximity(); break; }; qCDebug(lcMainWindow) << "Calling initComboBoxes() "; initComboBoxes(); } /** * @brief Updates the prominence distribution miniChart * Called from Graph after computing the prominence index distribution. * @param series * @param axisX * @param min * @param max */ void MainWindow::slotAnalyzeProminenceDistributionChartUpdate(QAbstractSeries *series, QAbstractAxis *axisX, const qreal &min, const qreal &max, QAbstractAxis *axisY, const qreal &minF, const qreal &maxF) { Q_UNUSED(minF); qCDebug(lcMainWindow) << "Updating the prominence distribution miniChart"; if (series == Q_NULLPTR) { qCDebug(lcMainWindow) << "series is null! Resetting to trivial"; miniChart->resetToTrivial(); return; } // Set the style of the lines and bars switch (series->type()) { case QAbstractSeries::SeriesTypeBar: qCDebug(lcMainWindow) << "this an BarSeries"; break; case QAbstractSeries::SeriesTypeArea: qCDebug(lcMainWindow) << "this an AreaSeries"; break; default: break; } // Clear miniChart from old series. miniChart->removeAllSeries(); // Remove all axes miniChart->removeAllAxes(); // Add series to miniChart miniChart->addSeries(series); // Set Chart title and remove legend miniChart->setTitle(series->name() + QString(" distribution"), QFont("Times", 8)); miniChart->toggleLegend(false); QString chartHelpMsg = tr("Distribution of %1 values:\n" "Min value: %2 \n" "Max value: %3 \n" "Please note that, due to the small size of this widget, \n" "if you display a distribution in Bar Chart where there are \n" "more than 10 values, the widget will not show all bars. \n" "In this case, use Line or Area Chart (from Settings). \n" "In any case, the large chart in the HTML report \n" "is better than this widget...") .arg(series->name()) .arg(min, 0, 'g', appSettings["initReportsRealNumberPrecision"].toInt(0, 10)) .arg(max, 0, 'g', appSettings["initReportsRealNumberPrecision"].toInt(0, 10)); miniChart->setToolTip(chartHelpMsg); miniChart->setWhatsThis(chartHelpMsg); // if true, then bar chart appears with default X axis (1,2,3 ...) bool useDefaultAxes = false; if (!useDefaultAxes) { if (axisX != Q_NULLPTR) { qCDebug(lcMainWindow) << "MW::slotAnalyzeProminenceDistributionChartUpdate() - " "axisX not null. Setting it to miniChart"; miniChart->setAxisX(axisX, series); miniChart->setAxisXMin(0); miniChart->setAxisXLabelFont(); miniChart->setAxisXLinePen(); miniChart->setAxisXGridLinePen(); miniChart->setAxisXLabelsAngle(-90); } if (axisY != Q_NULLPTR) { qCDebug(lcMainWindow) << "MW::slotAnalyzeProminenceDistributionChartUpdate() - " "axisY not null. Setting it to miniChart"; miniChart->setAxisY(axisY, series); miniChart->setAxisYMin(0); miniChart->setAxisYLabelFont(); miniChart->setAxisYLinePen(); miniChart->setAxisYGridLinePen(); } } if ((axisX == Q_NULLPTR && axisY == Q_NULLPTR) || useDefaultAxes) { qCDebug(lcMainWindow) << "MW::slotAnalyzeProminenceDistributionChartUpdate() - " "axisX and axisY null. Calling createDefaultAxes()"; miniChart->createDefaultAxes(); qCDebug(lcMainWindow) << "MW::slotAnalyzeProminenceDistributionChartUpdate() - setting axis min"; miniChart->setAxisYMin(0); miniChart->setAxisXMin(0); // Apply our theme to axes: miniChart->setAxesThemeDefault(); miniChart->axes(Qt::Vertical).first()->setMax(maxF + 1.0); miniChart->setAxisXLabelsAngle(-90); // axisX->setShadesVisible(false); } } SocNetV-3.7/src/mainwindow/analyze/mainwindow_analyze_shared.cpp000066400000000000000000000114001524402210300252300ustar00rootroot00000000000000/** * @file mainwindow_analyze_shared.cpp * @brief Implements MainWindow::askAboutEdgeWeights() - the shared modal "use edge weights?" prompt reused across most Analyze menu actions. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/graphtablewidget.h" #include "forms/dialogsimilaritypearson.h" #include "forms/dialogsimilaritymatches.h" #include "forms/dialogdissimilarities.h" #include "forms/dialogclusteringhierarchical.h" #include #include /** * @brief If the network has weighted / valued edges, it asks the user * if the app should consider weights or not. */ void MainWindow::askAboutEdgeWeights(const bool userTriggered) { qCDebug(lcMainWindow) << "MW::askAboutEdgeWeights() - checking if graph weighted."; if (userTriggered) { if (!activeGraph->isWeighted()) { slotHelpMessageToUser(USER_MSG_INFO, tr("Non-Weighted Network"), tr("You do not work on a weighted network at the moment. \n" "Therefore, I will not consider edge weights during " "computations. \n" "This option applies only when you load or create " "a weighted network ")); optionsEdgeWeightConsiderAct->setChecked(false); return; } } else { if (!activeGraph->isWeighted()) { optionsEdgeWeightConsiderAct->setChecked(false); return; } } qCDebug(lcMainWindow) << "MW::askAboutEdgeWeights() - graph weighted - checking if we have asked user."; if (askedAboutWeights) { return; } qCDebug(lcMainWindow) << "MW::askAboutEdgeWeights() - graph weighted - let's ask the user."; switch ( slotHelpMessageToUser(USER_MSG_QUESTION, tr("Weighted Network"), tr("This is a weighted network. Consider edge weights?"), tr("The ties in this network have weights (non-unit values) assigned to them. " "Do you want me to take these edge weights into account (i.e. when computing distances) ?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) ) { case QMessageBox::Yes: optionsEdgeWeightConsiderAct->setChecked(true); break; case QMessageBox::No: optionsEdgeWeightConsiderAct->setChecked(false); break; default: // just for sanity optionsEdgeWeightConsiderAct->setChecked(false); return; break; } if (optionsEdgeWeightConsiderAct->isChecked()) { switch ( slotHelpMessageToUser( USER_MSG_QUESTION, tr("Inverse edge weights during calculations? "), tr("Inverse edge weights during calculations? "), tr("If the edge weights denote cost or real distances (i.e. miles between cities), " "press No, since the distance between two nodes should be the quickest " "or cheaper one. \n\n" "If the weights denote value or strength (i.e. votes or interaction): for " "distance-based measures (Closeness, Betweenness, Stress, Eccentricity, " "IR Closeness, Power Centrality), press Yes, so a strong tie acts like a " "short path. For measures based on counting walks instead of shortest paths " "(Eigenvector, Katz, Bonacich, PageRank Prestige), press No instead, so a " "strong tie keeps contributing more rather than less. " "See the manual's Edge Weights section for the full explanation."), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) ) { case QMessageBox::Yes: inverseWeights = true; break; case QMessageBox::No: inverseWeights = false; break; default: // just for sanity inverseWeights = true; return; break; } } askedAboutWeights = true; return; } SocNetV-3.7/src/mainwindow/analyze/mainwindow_analyze_similarity.cpp000066400000000000000000000357321524402210300261660ustar00rootroot00000000000000/** * @file mainwindow_analyze_similarity.cpp * @brief Implements MainWindow Analyze menu structural equivalence reports: similarity measures, dissimilarities/tie profile, Pearson, hierarchical clustering. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/graphtablewidget.h" #include "forms/dialogsimilaritypearson.h" #include "forms/dialogsimilaritymatches.h" #include "forms/dialogdissimilarities.h" #include "forms/dialogclusteringhierarchical.h" #include #include /** * @brief Called when the user selects something in the Structural Equivalence * selectbox of the toolbox * @param selectedIndex * */ void MainWindow::toolBoxAnalysisStrEquivalenceSelectChanged(const int &selectedIndex) { qCDebug(lcMainWindow) << "selected struct. equivalence analysis, text index: " << selectedIndex; switch (selectedIndex) { case 0: break; case 1: slotAnalyzeStrEquivalencePearsonDialog(); break; case 2: slotAnalyzeStrEquivalenceSimilarityMeasureDialog(); break; case 3: slotAnalyzeStrEquivalenceDissimilaritiesDialog(); break; case 4: slotAnalyzeStrEquivalenceClusteringHierarchicalDialog(); break; }; qCDebug(lcMainWindow) << "Calling initComboBoxes() "; initComboBoxes(); } /** * @brief Displays the DialogSimilarityMatches dialog. */ void MainWindow::slotAnalyzeStrEquivalenceSimilarityMeasureDialog() { qCDebug(lcMainWindow) << "MW::slotAnalyzeStrEquivalenceSimilarityMeasureDialog()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } m_dialogSimilarityMatches = new DialogSimilarityMatches(this); connect(m_dialogSimilarityMatches, &DialogSimilarityMatches::userChoices, this, &MainWindow::slotAnalyzeStrEquivalenceSimilarityByMeasure); m_dialogSimilarityMatches->exec(); } /** * @brief Calls Graph::writeMatrixSimilarityMatching() to write a * similarity matrix according to given measure into a file, and displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeStrEquivalenceSimilarityByMeasure(const QString &matrix, const QString &varLocation, const QString &measure, const bool &diagonal) { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString metric; if (measure.contains("Simple", Qt::CaseInsensitive)) metric = "simple-matching"; else if (measure.contains("Jaccard", Qt::CaseInsensitive)) metric = "jaccard"; else if (measure.contains("None", Qt::CaseInsensitive)) metric = "none"; else if (measure.contains("Hamming", Qt::CaseInsensitive)) metric = "hamming"; else if (measure.contains("Cosine", Qt::CaseInsensitive)) metric = "cosine"; else if (measure.contains("Euclidean", Qt::CaseInsensitive)) metric = "euclidean"; else if (measure.contains("Manhattan", Qt::CaseInsensitive)) metric = "manhattan"; else if (measure.contains("Pearson ", Qt::CaseInsensitive)) metric = "pearson"; else if (measure.contains("Chebyshev", Qt::CaseInsensitive)) metric = "chebyshev"; QString fn = appSettings["dataDir"] + "socnetv-report-equivalence-similarity-" + metric + "-" + dateTime + ext; bool considerWeights = true; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, measure, matrix, varLocation, diagonal, considerWeights, reportFormat, success]() { *success = activeGraph->writeMatrixSimilarityMatching( fn, measure, matrix, varLocation, diagonal, considerWeights, reportFormat); }, tr("Computing Similarity Matrix. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) return; if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Similarity matrix saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief Displays the DialogDissimilarities dialog. */ void MainWindow::slotAnalyzeStrEquivalenceDissimilaritiesDialog() { qCDebug(lcMainWindow) << "MW::slotAnalyzeStrEquivalenceDissimilaritiesDialog()"; m_dialogdissimilarities = new DialogDissimilarities(this); connect(m_dialogdissimilarities, &DialogDissimilarities::userChoices, this, &MainWindow::slotAnalyzeStrEquivalenceDissimilaritiesTieProfile); m_dialogdissimilarities->exec(); } /** * @brief Invokes calculation of pair-wise tie profile dissimilarities of the * network, then displays it. * @param metric * @param varLocation * @param diagonal * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeStrEquivalenceDissimilaritiesTieProfile(const QString &metric, const QString &varLocation, const bool &diagonal) { qCDebug(lcMainWindow) << "MW::slotAnalyzeStrEquivalenceDissimilaritiesTieProfile()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString metricStr; if (metric.contains("Simple", Qt::CaseInsensitive)) metricStr = "simple-matching"; else if (metric.contains("Jaccard", Qt::CaseInsensitive)) metricStr = "jaccard"; else if (metric.contains("None", Qt::CaseInsensitive)) metricStr = "none"; else if (metric.contains("Hamming", Qt::CaseInsensitive)) metricStr = "hamming"; else if (metric.contains("Cosine", Qt::CaseInsensitive)) metricStr = "cosine"; else if (metric.contains("Euclidean", Qt::CaseInsensitive)) metricStr = "euclidean"; else if (metric.contains("Manhattan", Qt::CaseInsensitive)) metricStr = "manhattan"; else if (metric.contains("Pearson ", Qt::CaseInsensitive)) metricStr = "pearson"; else if (metric.contains("Chebyshev", Qt::CaseInsensitive)) metricStr = "chebyshev"; QString fn = appSettings["dataDir"] + "socnetv-report-equivalence-dissimilarities-" + metricStr + "-" + dateTime + ext; askAboutEdgeWeights(); bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, metric, varLocation, diagonal, considerWeights, reportFormat, success]() { *success = activeGraph->writeMatrixDissimilarities(fn, metric, varLocation, diagonal, considerWeights, reportFormat); }, tr("Computing Tie Profile Dissimilarities. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) { return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Tie profile dissimilarities matrix saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief Calls the m_dialogSimilarityPearson to display the Pearson statistics dialog */ void MainWindow::slotAnalyzeStrEquivalencePearsonDialog() { qCDebug(lcMainWindow) << "MW::slotAnalyzeStrEquivalencePearsonDialog()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } m_dialogSimilarityPearson = new DialogSimilarityPearson(this); connect(m_dialogSimilarityPearson, &DialogSimilarityPearson::userChoices, this, &MainWindow::slotAnalyzeStrEquivalencePearson); m_dialogSimilarityPearson->exec(); } /** * @brief Calls Graph::writeMatrixSimilarityPearson() to write Pearson * Correlation Coefficients into a file, and displays it. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. */ void MainWindow::slotAnalyzeStrEquivalencePearson(const QString &matrix, const QString &varLocation, const bool &diagonal) { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-equivalence-pearson-coefficients-" + dateTime + ext; bool considerWeights = true; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, considerWeights, matrix, varLocation, diagonal, reportFormat, success]() { *success = activeGraph->writeMatrixSimilarityPearson( fn, considerWeights, matrix, varLocation, diagonal, reportFormat); }, tr("Computing Pearson Correlation Coefficients. Please wait..."), [this, fn, reportFormat, success]() { if (!*success) return; if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Pearson correlation coefficients matrix saved as: ") + QDir::toNativeSeparators(fn)); }); } /** * @brief Displays the slotAnalyzeStrEquivalenceClusteringHierarchicalDialog dialog. */ void MainWindow::slotAnalyzeStrEquivalenceClusteringHierarchicalDialog() { qCDebug(lcMainWindow) << "MW::slotAnalyzeStrEquivalenceClusteringHierarchicalDialog()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString preselectMatrix = "Adjacency"; if (!activeGraph->isWeighted()) { preselectMatrix = "Distances"; } m_dialogClusteringHierarchical = new DialogClusteringHierarchical(this, preselectMatrix); connect(m_dialogClusteringHierarchical, &DialogClusteringHierarchical::userChoices, this, &MainWindow::slotAnalyzeStrEquivalenceClusteringHierarchical); m_dialogClusteringHierarchical->exec(); } /** * @brief Called from DialogClusteringHierarchical with user choices. Calls * Graph::writeClusteringHierarchical() to compute and write HCA and displays the report. * @param matrix * @param similarityMeasure * @param linkageCriterion * @param diagonal */ void MainWindow::slotAnalyzeStrEquivalenceClusteringHierarchical(const QString &matrix, const QString &varLocation, const QString &metric, const QString &method, const bool &diagonal, const bool &diagram) { qCDebug(lcMainWindow) << "MW::slotAnalyzeStrEquivalenceClusteringHierarchical()"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-equivalence-hierarchical-clustering-" + dateTime + ".html"; bool considerWeights = activeGraph->isWeighted(); bool inverseWeights = false; bool dropIsolates = true; auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, varLocation, matrix, metric, method, diagonal, diagram, considerWeights, inverseWeights, dropIsolates, success]() { *success = activeGraph->writeClusteringHierarchical(fn, varLocation, matrix, metric, method, diagonal, diagram, considerWeights, inverseWeights, dropIsolates); }, tr("Computing Hierarchical Cluster Analysis. Please wait..."), [this, fn, success]() { if (!*success) { return; } if (appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Hierarchical Cluster Analysis saved as: ") + QDir::toNativeSeparators(fn)); }); } SocNetV-3.7/src/mainwindow/dispatch/000077500000000000000000000000001524402210300174375ustar00rootroot00000000000000SocNetV-3.7/src/mainwindow/dispatch/mainwindow_dispatch.cpp000066400000000000000000000364311524402210300242050ustar00rootroot00000000000000/** * @file mainwindow_dispatch.cpp * @brief Implements MainWindow async-dispatch/busy-guard helpers (WS15): status messages, progress dialog polish, busy state, runGraphOperationAsync, and the generator confirmation-size prompt. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "forms/dialogsettings.h" #include /** * @brief Shows a message in the status bar, with the given duration * * Called by Graph::statusMessage to display some message to the user * * @param message */ void MainWindow::statusMessage(const QString message) { statusBar()->showMessage(message, appSettings["initStatusBarDuration"].toInt(0)); statusBar()->repaint(); } /** * @brief Fixes known bugs in QProgressDialog class. i.e. Workaround for macOS-only Qt bug: QTBUG-65750, QTBUG-70357. QProgressDialog too small and too narrow to fit the text of its label * @param dialog */ void MainWindow::polishProgressDialog(QProgressDialog *dialog) { #ifdef Q_OS_MAC // Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357. const int margin = dialog->fontMetrics().maxWidth(); dialog->resize(dialog->width() + 2 * margin, dialog->height()); dialog->show(); #else Q_UNUSED(dialog); #endif } /** * @brief Enables/disables the menu bar, toolbar, and canvas while a graphThread operation runs. * * Fixes a live, reproducible crash (WS15 P2, roadmap_ws15_cancellation_progress_unification.md): * graph-mutating actions (e.g. "New") run directly on the GUI thread with no dispatch to * graphThread and no guard, so one could run concurrently with an in-flight graphThread * computation - a genuine use-after-free race on Graph's own data. The Qt::ApplicationModal * progress dialog was assumed to already prevent this but doesn't reliably (root cause not fully * understood, see the roadmap); this is an unconditional second layer that doesn't depend on * understanding that gap. graphicsWidget is included because add-node/add-edge via mouse * (mousePressEvent/mouseDoubleClickEvent) bypasses the menu/toolbar entirely. * * menuBar()/toolBar's own setEnabled(false) only disables those container widgets - it blocks * clicks but leaves each QAction's own isEnabled() (and therefore its keyboard shortcut, e.g. * Ctrl+N) untouched, since actions aren't Qt children of the menus/toolbars they're added to. * Root cause found live 2026-08-08: clicking Cancel on the busy dialog doesn't stop the * still-running graphThread computation, but Ctrl+N straight afterwards could still fire and * race it - a real, reproducible crash on both macOS and Linux. Fixed here by also disabling * every QAction reachable from the menu bar/toolbar (recursively through submenus) while busy, * and restoring only the ones this function itself disabled - not blanket re-enabling everything, * since plenty of actions are legitimately disabled elsewhere for unrelated reasons (e.g. "no * network loaded"). This alone closes the crash path even though the dialog itself still hides * on Cancel before the computation actually stops (a separate, lower-severity UX follow-up). * * @param busy true to disable (operation starting), false to re-enable (operation complete) */ void MainWindow::setAppBusy(bool busy) { menuBar()->setEnabled(!busy); toolBar->setEnabled(!busy); graphicsWidget->setEnabled(!busy); // leftPanel hosts the toolbox comboboxes (edit/analysis/visualization selects, e.g. // toolBoxAnalysisStrEquivalenceSelect) - plain QComboBoxes, not QActions, so the // menuBar/toolBar QAction sweep below never reaches them. Without this, a toolbox // selection can re-trigger a runGraphOperationAsync operation while a prior one is // still running on graphThread, racing on shared Graph:: matrix members (AM/SCM/etc.) leftPanel->setEnabled(!busy); if (busy) { std::function &)> disableActions = [this, &disableActions](const QList &actions) { for (QAction *action : actions) { if (action->isEnabled()) { action->setEnabled(false); m_actionsDisabledForBusy << action; } if (action->menu()) { disableActions(action->menu()->actions()); } } }; disableActions(menuBar()->actions()); disableActions(toolBar->actions()); } else { for (QAction *action : m_actionsDisabledForBusy) { action->setEnabled(true); } m_actionsDisabledForBusy.clear(); } } /** * @brief Runs a slow Graph operation on graphThread without blocking the GUI thread (fix #254). * * `activeGraph` already lives on graphThread (see MainWindow::initGraph()) - this dispatches * `operation` there via a queued invocation instead of introducing any new thread or using * QtConcurrent::run(). That matters: DistanceEngine::compute() already parallelises internally * via QtConcurrent::blockingMap over the *global* thread pool, so wrapping the outer call in * QtConcurrent::run() as well would submit a second task to that same pool and risk contending * with blockingMap's own worker tasks. Routing through graphThread sidesteps that entirely - * blockingMap's behaviour is completely unaffected; only the identity of the thread that calls * and blocks on it changes (graphThread instead of the GUI thread). * * Shows an indeterminate progress dialog (no setValue() calls - cheap regardless of network * size, unlike the granular showProgressBar mechanism) for the duration. Because it's * ApplicationModal, it also prevents the user from mutating the graph from the GUI thread while * `operation` runs on graphThread - required, since DistanceEngine and friends mutate Graph's * member state directly and are not internally synchronized against concurrent access. * * The dialog's Cancel button is wired to the same Graph::slotCancelComputation() the existing * granular progress dialog uses. Note this carries over an existing limitation, not a new one: * cancellation can't interrupt mid-blockingMap (see the comment in * DistanceEngine::runAllSources()) - it can only take effect at the checkpoints initRun()/ * finalize() already have. * * @param operation The Graph call to run, e.g. [this, args...](){ activeGraph->foo(args...); } * β€” capture only plain values read from UI widgets *before* calling this * method, never QAction/QWidget pointers to be dereferenced inside the * lambda body, since that body executes on graphThread, not the GUI thread. * @param waitMessage Status bar message and progress dialog text shown immediately. * @param doneMessage Status bar message shown on completion (optional). For anything beyond a * plain status message on completion (e.g. opening a generated report file, * which must wait for `operation` to actually finish and only happen if it * succeeded), use the std::function overload below instead. */ void MainWindow::runGraphOperationAsync(std::function operation, const QString &waitMessage, const QString &doneMessage) { runGraphOperationAsync(operation, waitMessage, [this, doneMessage]() { if (!doneMessage.isEmpty()) statusMessage(doneMessage); }); } /** * @brief Warns before network generation above a safety edge-count threshold. * * Generating tens of millions of edges can exhaust memory and crash the app before any * progress dialog even appears - checked against the *expected* edge count up front, so * nothing partially-built needs to be unwound if the user declines. * * @param expectedEdges Estimated edge count the generator would produce. * @param generatorLabel Human-readable generator name, used in the warning message. * @param scripted True when called from --interactive-script: there's no one to answer * a modal prompt, so this refuses outright and logs instead, matching * the malformed-command pattern used elsewhere in the dispatcher. * @return true if generation should proceed (under the limit, or the user chose to proceed * anyway), false if it was refused/declined. */ bool MainWindow::confirmGenerationSize(qint64 expectedEdges, const QString &generatorLabel, bool scripted) { static constexpr qint64 kMaxSafeGeneratedEdges = 2000000; if (expectedEdges <= kMaxSafeGeneratedEdges) { return true; } if (scripted) { qWarning() << "Refusing" << generatorLabel << "- expected edges" << expectedEdges << "exceeds the safety limit of" << kMaxSafeGeneratedEdges; return false; } return slotHelpMessageToUser( USER_MSG_QUESTION, tr("Large network"), tr("This %1 would create approximately %2 edges, " "which exceeds the safety limit of %3.") .arg(generatorLabel) .arg(expectedEdges) .arg(kMaxSafeGeneratedEdges), tr("Generating a network this large has been observed to exhaust available " "memory and crash the application. Proceed anyway?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; } /** * @brief Overload of runGraphOperationAsync() for completions that need more than a status * message - e.g. opening a report file that `operation` just wrote, which must only happen * after `operation` actually finishes (and, if `operation` reports success/failure via a * captured flag, only on success). See the primary overload above for the full explanation of * why this dispatches via graphThread rather than QtConcurrent::run(). * * @param operation Same contract as the primary overload - GUI state must be captured as * plain values *before* the call, not read from inside the lambda. * @param waitMessage Status bar message and progress dialog text shown immediately. * @param onComplete Runs on the GUI thread after `operation` finishes. If `operation` needs to * report success/failure, capture a shared flag (e.g. `std::shared_ptr`) * in both lambdas - see the writeCentralityX() call sites for the pattern. */ void MainWindow::runGraphOperationAsync(std::function operation, const QString &waitMessage, std::function onComplete) { // Must happen before the busy dialog exists (let alone is shown), not just before dispatch: // this is the one guaranteed reset point every wrapped operation goes through. Some chains // also reset internally (DistanceEngine's progress sink; randomNetErdosCreate()'s own reset, // kept for its --interactive-script benchmark path which bypasses this wrapper entirely) but // most don't - without this central call, a single earlier cancel would silently no-op every // subsequent wrapped operation's cancelCheck() forever after. See WS15 P3 Phase 2 // (roadmap_ws15_cancellation_progress_unification.md). activeGraph->resetProgressCanceled(); statusMessage(waitMessage); QProgressDialog *busyDialog = new QProgressDialog(waitMessage, tr("Cancel"), 0, 0, this); busyDialog->setWindowModality(Qt::ApplicationModal); busyDialog->setMinimumDuration(0); busyDialog->setAutoClose(false); busyDialog->setAutoReset(false); polishProgressDialog(busyDialog); // Qt::DirectConnection: runs slotCancelComputation() synchronously on this (GUI) thread at // click-time, instead of queueing onto graphThread's event loop - which is exactly what's // blocked for the whole duration of the computation this button is meant to interrupt. See // WS15 P1 (roadmap_ws15_cancellation_progress_unification.md). connect(busyDialog, &QProgressDialog::canceled, activeGraph, &Graph::slotCancelComputation, Qt::DirectConnection); // The Cancel button is wired by Qt itself to QProgressDialog::cancel(), which // unconditionally hides the dialog before emitting canceled() - setAutoClose(false)/ // setAutoReset(false) above don't gate this path at all, only setValue() reaching // maximum() does. Without this, the dialog disappears immediately even though the // computation may keep running for a while (coarse cancelCheck granularity in some // operations), letting the user believe it already stopped (WS15 Finding 8). Re-show it, // relabeled and disabled, until the operation's own completion continuation below calls // reset()/deleteLater() for real. connect(busyDialog, &QProgressDialog::canceled, busyDialog, [busyDialog]() { busyDialog->setLabelText(tr("Canceling - please wait for the operation to stop...")); busyDialog->setEnabled(false); busyDialog->show(); }, Qt::DirectConnection); busyDialog->show(); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); setAppBusy(true); QMetaObject::invokeMethod(activeGraph, [this, operation, onComplete, busyDialog]() { operation(); QMetaObject::invokeMethod(this, [this, onComplete, busyDialog]() { QApplication::restoreOverrideCursor(); setAppBusy(false); // reset(), not close(): QProgressDialog::close() triggers its internal cancel() // path and emits canceled() -> Graph::slotCancelComputation() -> m_progressCanceled // stays true until the next resetProgressCanceled() call, silently no-op'ing every // operation after the first. // // reset() alone does NOT hide the dialog here: per QProgressDialog's docs, reset() // only hides it when autoClose is true, and setAutoClose(false) is set above (see // the cancel-button re-show workaround, WS15 Finding 8). Without an explicit hide(), // the dialog stayed mapped on screen - invisible-in-Qt's-model but still on-screen - // until deleteLater()'s deferred deletion eventually ran. For onComplete callbacks // that open their own modal QMessageBox synchronously (e.g. Node/Graph Connectivity's // result dialog), that deferred deletion doesn't happen until the new modal's nested // event loop is dismissed, so the progress dialog visibly lingered behind it the // whole time. busyDialog->reset(); busyDialog->hide(); busyDialog->deleteLater(); if (onComplete) onComplete(); }, Qt::QueuedConnection); }, Qt::QueuedConnection); } SocNetV-3.7/src/mainwindow/edit/000077500000000000000000000000001524402210300165655ustar00rootroot00000000000000SocNetV-3.7/src/mainwindow/edit/mainwindow_edit_edge.cpp000066400000000000000000001255461524402210300234530ustar00rootroot00000000000000/** * @file mainwindow_edit_edge.cpp * @brief Implements MainWindow Edit menu edge operations: add/remove, properties, appearance, symmetrize/dichotomize/undirect transforms. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "graphicsnode.h" #include "graphicsedge.h" #include "widgets/graphtablewidget.h" #include "forms/dialognodeedit.h" #include "forms/dialogedgeedit.h" #include "forms/dialogbulkedit.h" #include "forms/dialognodefind.h" #include "forms/dialogedgedichotomization.h" #include "forms/dialogfilteredgesbyweight.h" #include "forms/dialogfilternodesbycentrality.h" #include "forms/dialogfilterbyattribute.h" #include "forms/dialogquerybuilder.h" #include /** * @brief Called when user selects something in the Edge Transform * selectbox of the toolbox * @param selectedIndex */ void MainWindow::toolBoxEditEdgeTransformSelectChanged(const int &selectedIndex) { qCDebug(lcMainWindow) << "selected edge transform, index: " << selectedIndex; switch (selectedIndex) { case 0: break; case 1: slotEditEdgeSymmetrizeAll(); break; case 2: slotEditEdgeSymmetrizeStrongTies(); break; case 3: slotEditEdgeSymmetrizeCocitation(); break; case 4: slotEditEdgeDichotomizationDialog(); break; }; } /** * @brief Opens DialogBulkEdit for the currently selected edges (canvas shortcut). */ void MainWindow::slotEditEdgeSetPropertyForSelection() { const QList selectedEdges = activeGraph->getSelectedEdges(); const int count = selectedEdges.size(); if (count == 0) { statusMessage(tr("No edges selected.")); return; } // Collect unique custom attribute keys across the selection QSet keySet; for (const SelectedEdge &e : selectedEdges) for (const QString &k : activeGraph->edgeCustomAttributes(e.first, e.second).keys()) keySet.insert(k); const QStringList existingKeys(keySet.begin(), keySet.end()); auto dlg = std::make_unique( DialogBulkEdit::Scope::Edges, existingKeys, QStringList(), QStringList(), count, false, this); connect(dlg.get(), &DialogBulkEdit::userChoices, this, [this, selectedEdges](const QString &property, const QString &value) { runGraphOperationAsync( [this, selectedEdges, property, value]() { for (const SelectedEdge &e : selectedEdges) { if (property == QLatin1String("Label")) { activeGraph->edgeLabelSet(e.first, e.second, value); } else if (property == QLatin1String("Weight")) { activeGraph->edgeWeightSet(e.first, e.second, value.toDouble()); } else if (property == QLatin1String("Color")) { activeGraph->edgeColorSet(e.first, e.second, value); } else { activeGraph->edgeCustomAttributesSet( e.first, e.second, {{property, value}}); } } }, tr("Applying bulk edit..."), [this, selectedEdges, property]() { statusMessage(tr("Set '%1' on %2 edge(s).").arg(property).arg(selectedEdges.size())); if (m_tableDock && m_tableDock->isVisible()) m_tableWidget->refresh(activeGraph); }); }); dlg->exec(); } /** * @brief Displays information about the clicked edge on the statusbar * * Called by Graph when the user clicks on an edge or when we need to init the LCDs (i.e. clearing the graph). * * @param edge * @param openMenu */ void MainWindow::slotEditEdgeClicked(const MyEdge &edge, const bool &openMenu) { int v1 = edge.source; int v2 = edge.target; qreal weight = edge.weight; qreal reverseWeight = edge.rWeight; int type = edge.type; if (v1 == 0 || v2 == 0) { rightPanelClickedEdgeNameLCD->setText("-"); rightPanelClickedEdgeWeightLabel->setVisible(false); rightPanelClickedEdgeWeightLCD->setVisible(false); rightPanelClickedEdgeReciprocalWeightLabel->setVisible(false); rightPanelClickedEdgeReciprocalWeightLCD->setVisible(false); return; } rightPanelClickedEdgeWeightLabel->setVisible(true); rightPanelClickedEdgeWeightLCD->setVisible(true); QString edgeName; if (type == EdgeType::Undirected) { statusMessage(QString(tr("Undirected edge %1 <--> %2 of weight %3 has been selected. " "Click anywhere else to unselect it.")) .arg(v1) .arg(v2) .arg(weight)); rightPanelClickedEdgeNameLCD->setText(QString::number(v1) + QString(" -- ") + QString::number(v2)); rightPanelClickedEdgeWeightLabel->setText(tr("Weight:")); rightPanelClickedEdgeWeightLCD->setText(QString::number(weight)); rightPanelClickedEdgeReciprocalWeightLabel->setVisible(false); rightPanelClickedEdgeReciprocalWeightLCD->setVisible(false); if (openMenu) { edgeName = QString("EDGE: ") + QString::number(v1) + QString(" -- ") + QString::number(v2); } } else if (type == EdgeType::Reciprocated) { statusMessage(QString(tr("Reciprocated edge %1 <--> %2 has been selected. " "Weight %1 --> %2 = %3, " "Weight %2 --> %1 = %4. " "Click anywhere else to unselect it.")) .arg(v1) .arg(v2) .arg(weight) .arg(reverseWeight)); rightPanelClickedEdgeNameLCD->setText(QString::number(v1) + QString(" <-->") + QString::number(v2)); rightPanelClickedEdgeWeightLabel->setText(tr("Weight:")); rightPanelClickedEdgeWeightLCD->setText(QString::number(weight)); rightPanelClickedEdgeReciprocalWeightLabel->setText("Recipr.:"); rightPanelClickedEdgeReciprocalWeightLabel->setVisible(true); rightPanelClickedEdgeReciprocalWeightLCD->setText(QString::number(reverseWeight)); rightPanelClickedEdgeReciprocalWeightLCD->setVisible(true); if (openMenu) { edgeName = QString("RECIPROCATED EDGE: ") + QString::number(v1) + QString(" <-->") + QString::number(v2); } } else { statusMessage(QString(tr("Directed edge %1 --> %2 of weight %3 has been selected. " "Click again to unselect it.")) .arg(v1) .arg(v2) .arg(weight)); rightPanelClickedEdgeNameLCD->setText(QString::number(v1) + QString(" -->") + QString::number(v2)); rightPanelClickedEdgeWeightLabel->setText(tr("Weight:")); rightPanelClickedEdgeWeightLCD->setText(QString::number(weight)); rightPanelClickedEdgeReciprocalWeightLabel->setVisible(false); rightPanelClickedEdgeReciprocalWeightLCD->setVisible(false); if (openMenu) { edgeName = QString("DIRECTED EDGE: ") + QString::number(v1) + QString(" -->") + QString::number(v2); } } if (openMenu) { slotEditEdgeOpenContextMenu(edgeName); } } /** * @brief Shows a context menu when the user right-clicks directly on an edge. * * The menu is selection-aware β€” it reflects the total number of currently * selected edges: * - Single edge selected: offers Edge Properties (per-edge dialog for label, * weight, color, custom attributes) plus Remove, Weight, Label, Color actions. * - Multiple edges selected: offers "Edit Selection in Data Table" and * "Set property on selected edges…" at the top, followed by per-edge actions. * * @param str A human-readable identifier for the edge shown as the menu title * (e.g. "3 --> 7"). */ void MainWindow::slotEditEdgeOpenContextMenu(const QString &str) { qCDebug(lcMainWindow) << "MW: slotEditEdgeOpenContextMenu() for" << str << "at" << QCursor::pos().x() << "," << QCursor::pos().y(); const int edgesSelected = activeGraph->getSelectedEdgesCount(); QMenu *edgeContextMenu = new QMenu(str, this); edgeContextMenu->addAction(str); edgeContextMenu->addSeparator(); if (edgesSelected > 1) { // Multiple edges selected: offer bulk actions first edgeContextMenu->addAction(editNodeEditSelectionInTableAct); edgeContextMenu->addAction(editEdgeSetPropertyForSelectionAct); edgeContextMenu->addSeparator(); } else { // Single edge: per-edge properties edgeContextMenu->addAction(editEdgePropertiesAct); edgeContextMenu->addSeparator(); } edgeContextMenu->addAction(editEdgeRemoveAct); edgeContextMenu->addAction(editEdgeWeightAct); edgeContextMenu->addAction(editEdgeLabelAct); edgeContextMenu->addAction(editEdgeColorAct); edgeContextMenu->exec(QCursor::pos()); delete edgeContextMenu; } /** * @brief Opens dialogs for the user to specify the source and the target node of a new edge * * Called when user clicks on the MW button/menu item "Add edge" * */ void MainWindow::slotEditEdgeAdd() { qCDebug(lcMainWindow) << "Request to add a new edge through UI. Opening source/target node dialogs..."; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } int sourceNode = -1, targetNode = -1; qreal weight = 1; // weight of this new edge should be one... bool ok = false; int min = activeGraph->vertexNumberMin(); int max = activeGraph->vertexNumberMax(); if (min == max) return; // if there is only one node->no edge if (!activeGraph->vertexClicked()) { sourceNode = QInputDialog::getInt( this, "Create new edge, Step 1", tr("This will draw a new edge between two nodes. \n" "Enter source node (" + QString::number(min).toLatin1() + "..." + QString::number(max).toLatin1() + "):"), min, 1, max, 1, &ok); if (!ok) { statusMessage("Add edge operation cancelled."); return; } } else sourceNode = activeGraph->vertexClicked(); qCDebug(lcMainWindow) << "sourceNode:" << sourceNode; if (!activeGraph->vertexExists(sourceNode)) { qCDebug(lcMainWindow) << "Cannot find sourceNode" << sourceNode; slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error. That node does not exist!"), tr("Error. That node does not exist!"), tr("Are you sure you entered the correct node number?")); return; } targetNode = QInputDialog::getInt(this, "Create new edge, Step 2", tr("Source node:") + QString::number(sourceNode) + tr(" \nNow enter a target node [") + QString::number(min).toLatin1() + "..." + QString::number(max).toLatin1() + "]:", min, min, max, 1, &ok); if (!ok) { statusMessage("Add edge target operation cancelled."); return; } if (!activeGraph->vertexExists(targetNode)) { qCDebug(lcMainWindow) << "Cannot find targetNode" << targetNode; slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error. That node does not exist!"), tr("Error. That node does not exist!"), tr("Are you sure you entered the correct node number?")); return; } weight = QInputDialog::getDouble( this, "Create new edge, Step 3", tr("Source and target nodes accepted. \n" "Please, enter the weight of new edge: "), 1.0, -100.0, 100.0, 1, &ok); if (!ok) { statusMessage("Add edge operation cancelled."); return; } // Check if this edge already exists... if (activeGraph->edgeExists(sourceNode, targetNode) != 0) { qCDebug(lcMainWindow, "edge exists. Aborting"); slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error. That edge already exists!"), tr("Error. That edge already exists!"), tr("Are you sure you entered the correct node numbers?")); return; } slotEditEdgeCreate(sourceNode, targetNode, weight); } /** * @brief Handles UI requests to create new edges, when the user clicks the menu item or doubles-clicks two nodes * * @param source * @param target * @param weight */ void MainWindow::slotEditEdgeCreate(const int &source, const int &target, const qreal &weight) { qCDebug(lcMainWindow) << "User requested to create a new edge" << source << "->" << target << "weight" << weight << "Setting user settings and calling Graph to to do the job..."; bool bezier = false; bool result = activeGraph->edgeCreate( source, target, weight, appSettings["initEdgeColor"], (editEdgeUndirectedAllAct->isChecked()) ? 2 : 0, (editEdgeUndirectedAllAct->isChecked()) ? false : ((appSettings["initEdgeArrows"] == "true") ? true : false), bezier); if (result) { statusMessage(tr("New edge %1 -> %2 created, weight %3.").arg(source).arg(target).arg(weight)); } } /** * @brief Opens the Edge Properties dialog for the currently clicked edge. */ void MainWindow::slotEditEdgePropertiesDialog() { qCDebug(lcMainWindow) << "MainWindow::slotEditEdgePropertiesDialog()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const QList selectedEdges = activeGraph->getSelectedEdges(); const int selectedEdgesCount = selectedEdges.size(); if (selectedEdgesCount == 0) { statusMessage(tr("Select an edge first to edit its properties.")); return; } if (selectedEdgesCount > 1) { slotEditEdgeSetPropertyForSelection(); return; } // Exactly one edge selected β€” open the full properties dialog int v1 = selectedEdges.constFirst().first; int v2 = selectedEdges.constFirst().second; // A reciprocated edge is one GraphicsEdge item, but two independent directed arcs in the // model (each with its own weight/label/color) - ask which direction to edit rather than // silently always editing whichever direction was created first. Same pattern as the // direction-choice dialog in slotEditEdgeRemove(). See #251, #264. activeGraph->edgeClickedSet(v1, v2); if (activeGraph->edgeClicked().type == EdgeType::Reciprocated) { QStringList items; QString arcA = QString::number(v1) + " -->" + QString::number(v2); QString arcB = QString::number(v2) + " -->" + QString::number(v1); items << arcA << arcB; bool ok = false; QString selectedArc = QInputDialog::getItem( this, tr("Select edge"), tr("This is a reciprocated edge. " "Select direction to edit properties for:"), items, 0, false, &ok); if (!ok) { statusMessage(tr("Edge properties operation cancelled.")); return; } if (selectedArc == arcB) { const int tmp = v1; v1 = v2; v2 = tmp; } } const QString label = activeGraph->edgeLabel(v1, v2); const double weight = static_cast(activeGraph->edgeWeight(v1, v2)); const QColor color = QColor(activeGraph->edgeColor(v1, v2)); const QHash attrs = activeGraph->edgeCustomAttributes(v1, v2); DialogEdgeEdit *dialog = new DialogEdgeEdit(this, v1, v2, label, weight, color, attrs); connect(dialog, &DialogEdgeEdit::userChoices, this, [this, v1, v2](const QString &label, const double &weight, const QColor &color, const QHash &customAttributes) { slotEditEdgeProperties(v1, v2, label, weight, color, customAttributes); }); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->exec(); } /** * @brief Applies updated edge properties received from DialogEdgeEdit. */ void MainWindow::slotEditEdgeProperties(const int &v1, const int &v2, const QString &label, const double &weight, const QColor &color, const QHash &customAttributes) { qCDebug(lcMainWindow) << "MainWindow::slotEditEdgeProperties()"; if (v1 == 0 || v2 == 0) return; activeGraph->edgeLabelSet(v1, v2, label); activeGraph->edgeWeightSet(v1, v2, static_cast(weight)); activeGraph->edgeColorSet(v1, v2, color.name()); activeGraph->edgeCustomAttributesSet(v1, v2, customAttributes); statusMessage(tr("Updated properties of edge %1 \u2192 %2.").arg(v1).arg(v2)); } /** * @brief Removes a clicked edge. Otherwise asks the user to specify one edge. */ void MainWindow::slotEditEdgeRemove() { qCDebug(lcMainWindow) << "Removing selected edges..."; if (!activeNodes() || activeEdges() == 0) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_EDGES); return; } int min = 0, max = 0, sourceNode = -1, targetNode = -1; bool ok = false; bool removeOpposite = false; int selectedEdgeCount = activeGraph->getSelectedEdgesCount(); qCDebug(lcMainWindow) << "Selected edges:" << selectedEdgeCount; if (!selectedEdgeCount) { min = activeGraph->vertexNumberMin(); max = activeGraph->vertexNumberMax(); qCDebug(lcMainWindow) << "MW::slotEditEdgeRemove() - No edge selected. " "Prompting user to select..."; sourceNode = QInputDialog::getInt( this, tr("Remove edge"), tr("Source node: (") + QString::number(min) + "..." + QString::number(max) + "):", min, 1, max, 1, &ok); if (!ok) { statusMessage("Remove edge operation cancelled."); return; } targetNode = QInputDialog::getInt( this, tr("Remove edge"), tr("Target node: (") + QString::number(min) + "..." + QString::number(max) + "):", min, 1, max, 1, &ok); if (!ok) { statusMessage("Remove edge operation cancelled."); return; } if (activeGraph->edgeExists(sourceNode, targetNode, false) != 0) { removeOpposite = false; if (activeGraph->isUndirected()) { removeOpposite = true; } } else { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error. Cannot find that edge!"), tr("Error. Cannot find that edge!"), tr("Are you sure you entered the correct node numbers?")); return; } } else { if (selectedEdgeCount > 1) { qCDebug(lcMainWindow) << "MW::slotEditEdgeRemove() - Multiple edges selected. " "Calling Graph to remove all of them..."; activeGraph->edgeRemoveSelectedAll(); return; } // Exactly one edge is selected, but it may have been selected via rubber-band rather // than an individual click - in that case edgeClicked() below still holds stale (or // default 0,0) state from whatever was last individually clicked, not the actual // selected edge. Sync it explicitly so the reciprocated-edge dialog below shows the // right node numbers regardless of how the edge was selected. { const SelectedEdge selected = activeGraph->getSelectedEdges().first(); activeGraph->edgeClickedSet(selected.first, selected.second); } qCDebug(lcMainWindow) << "MW::slotEditEdgeRemove() - One edge selected: " << activeGraph->edgeClicked().source << "->" << activeGraph->edgeClicked().target; if (activeGraph->edgeClicked().type == EdgeType::Reciprocated) { QStringList items; QString arcA = QString::number(activeGraph->edgeClicked().source) + " -->" + QString::number(activeGraph->edgeClicked().target); QString arcB = QString::number(activeGraph->edgeClicked().target) + " -->" + QString::number(activeGraph->edgeClicked().source); items << arcA << arcB << "Both"; ok = false; QString selectedArc = QInputDialog::getItem( this, tr("Select edge"), tr("This is a reciprocated edge. " "Select direction to remove:"), items, 0, false, &ok); if (selectedArc == arcA) { sourceNode = activeGraph->edgeClicked().source; targetNode = activeGraph->edgeClicked().target; } else if (selectedArc == arcB) { sourceNode = activeGraph->edgeClicked().target; targetNode = activeGraph->edgeClicked().source; } else { // both sourceNode = activeGraph->edgeClicked().source; targetNode = activeGraph->edgeClicked().target; removeOpposite = true; } } else { sourceNode = activeGraph->edgeClicked().source; targetNode = activeGraph->edgeClicked().target; } } activeGraph->edgeRemove(sourceNode, targetNode, removeOpposite); qCDebug(lcMainWindow) << "MW::slotEditEdgeRemove() -" << "View items now:" << graphicsWidget->items().size() << "Scene items now:" << graphicsWidget->scene()->items().size(); } /** * @brief Changes the label of an edge. */ void MainWindow::slotEditEdgeLabel() { qCDebug(lcMainWindow) << "MW::slotEditEdgeLabel()"; if (!activeEdges()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_EDGES); return; } int sourceNode = -1, targetNode = -1; bool ok = false; int min = activeGraph->vertexNumberMin(); int max = activeGraph->vertexNumberMax(); if (!activeGraph->edgeClicked().source || !activeGraph->edgeClicked().target) { // no edge clicked. Ask user to define an edge. sourceNode = QInputDialog::getInt(this, "Change edge label", tr("Select edge source node: (" + QString::number(min).toLatin1() + "..." + QString::number(max).toLatin1() + "):"), min, 1, max, 1, &ok); if (!ok) { statusMessage("Change edge label operation cancelled."); return; } targetNode = QInputDialog::getInt(this, "Change edge label...", tr("Select edge target node: (" + QString::number(min).toLatin1() + "..." + QString::number(max).toLatin1() + "):"), min, 1, max, 1, &ok); if (!ok) { statusMessage("Change edge label operation cancelled."); return; } if (!activeGraph->edgeExists(sourceNode, targetNode)) { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error. Cannot find that edge!"), tr("Error. Cannot find that edge!"), tr("Are you sure you entered the correct node numbers?")); return; } } else { // edge has been clicked. sourceNode = activeGraph->edgeClicked().source; targetNode = activeGraph->edgeClicked().target; } QString label = QInputDialog::getText(this, tr("Change edge label"), tr("Enter label: ")); if (!label.isEmpty()) { qCDebug(lcMainWindow) << "MW::slotEditEdgeLabel() - " << sourceNode << "->" << targetNode << " new label " << label; activeGraph->edgeLabelSet(sourceNode, targetNode, label); slotOptionsEdgeLabelsVisibility(true); statusMessage(tr("Changed edge label. ")); } else { statusMessage(tr("Change edge label aborted. ")); } } /** * @brief Changes the color of all edges weighted below threshold to parameter color * * If color is not valid, it opens a QColorDialog * If threshold == RAND_MAX it changes the color of all edges. * * @param color = QColor() * @param threshold = RAND_MAX */ void MainWindow::slotEditEdgeColorAll(QColor color, const int threshold) { qCDebug(lcMainWindow) << "Changing the color of all matching edges to color: " << color.name() << " threshold " << threshold; if (!color.isValid()) { QString text; if (threshold < RAND_MAX) { text = "Change the color of edges weighted < " + QString::number(threshold); } else text = "Change the color of all edges"; color = QColorDialog::getColor(appSettings["initEdgeColor"], this, text); } if (color.isValid()) { qCDebug(lcMainWindow) << "new edge color: " << color.name() << " threshold " << threshold; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); if (threshold < 0) { appSettings["initEdgeColorNegative"] = color.name(); } else if (threshold == 0) { appSettings["initEdgeColorZero"] = color.name(); } else { appSettings["initEdgeColor"] = color.name(); } activeGraph->edgeColorAllSet(color.name(), threshold); QApplication::restoreOverrideCursor(); statusMessage(tr("Changed edges color. ")); } else { // user pressed Cancel statusMessage(tr("edges color change aborted. ")); } } /** * @brief Changes the color of the clicked edge. * If no edge is clicked, then it asks the user to specify one. */ void MainWindow::slotEditEdgeColor() { qCDebug(lcMainWindow) << "MW::slotEditEdgeColor()"; if (!activeEdges()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_EDGES); return; } int sourceNode = -1, targetNode = -1; bool ok = false; int min = activeGraph->vertexNumberMin(); int max = activeGraph->vertexNumberMax(); if (!activeGraph->edgeClicked().source || !activeGraph->edgeClicked().target) { // no edge clicked. Ask user to define an edge. sourceNode = QInputDialog::getInt(this, "Change edge color", tr("Select edge source node: (" + QString::number(min).toLatin1() + "..." + QString::number(max).toLatin1() + "):"), min, 1, max, 1, &ok); if (!ok) { statusMessage("Change edge color operation cancelled."); return; } targetNode = QInputDialog::getInt(this, "Change edge color...", tr("Select edge target node: (" + QString::number(min).toLatin1() + "..." + QString::number(max).toLatin1() + "):"), min, 1, max, 1, &ok); if (!ok) { statusMessage("Change edge color operation cancelled."); return; } if (!activeGraph->edgeExists(sourceNode, targetNode)) { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error. Cannot find that edge!"), tr("Error. Cannot find that edge!"), tr("Are you sure you entered the correct node numbers?")); return; } } else { // edge has been clicked. sourceNode = activeGraph->edgeClicked().source; targetNode = activeGraph->edgeClicked().target; } QString curColor = activeGraph->edgeColor(sourceNode, targetNode); if (!QColor(curColor).isValid()) { curColor = appSettings["initEdgeColor"]; } QColor color = QColorDialog::getColor( curColor, this, tr("Select new color....")); if (color.isValid()) { QString newColor = color.name(); qCDebug(lcMainWindow) << "MW::slotEditEdgeColor() - " << sourceNode << "->" << targetNode << " newColor " << newColor; activeGraph->edgeColorSet(sourceNode, targetNode, newColor); statusMessage(tr("Edge color changed.")); } else { statusMessage(tr("Change edge color aborted. ")); } } /** * @brief Changes the weight of the clicked edge. * If no edge is clicked, asks the user to specify an Edge. */ void MainWindow::slotEditEdgeWeight() { if (!activeEdges()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_EDGES); return; } qCDebug(lcMainWindow, "MW::slotEditEdgeWeight()"); int sourceNode = -1, targetNode = -1; qreal newWeight = 1.0; int min = activeGraph->vertexNumberMin(); int max = activeGraph->vertexNumberMax(); bool changeBothEdges = false; bool ok = false; // Check if an edge has been clicked/selected. if (activeGraph->edgeClicked().source == 0 || activeGraph->edgeClicked().target == 0) { // No edge clicked/selected. Show dialog to select the edge by source/target nodes. sourceNode = QInputDialog::getInt( this, "Edge weight", tr("Select edge source node: (" + QString::number(min).toLatin1() + "..." + QString::number(max).toLatin1() + "):"), min, 1, max, 1, &ok); if (!ok) { statusMessage("Change edge weight operation cancelled."); return; } targetNode = QInputDialog::getInt( this, "Edge weight", tr("Select edge target node: (" + QString::number(min).toLatin1() + "..." + QString::number(max).toLatin1() + "):"), min, 1, max, 1, &ok); if (!ok) { statusMessage("Change edge weight operation cancelled."); return; } qCDebug(lcMainWindow, "source %i target %i", sourceNode, targetNode); } else { // An edge is clicked/selected. qCDebug(lcMainWindow) << "MW: slotEditEdgeWeight() - an Edge has already been clicked"; // Check if clicked edge is reciprocated if (activeGraph->edgeClicked().type == EdgeType::Reciprocated) { // Clicked edge is reciprocated. // We need the user to let us know if she wants to change a single edge or both QStringList items; QString arcA = QString::number(activeGraph->edgeClicked().source) + " -->" + QString::number(activeGraph->edgeClicked().target); QString arcB = QString::number(activeGraph->edgeClicked().target) + " -->" + QString::number(activeGraph->edgeClicked().source); items << arcA << arcB << "Both"; ok = false; QString selectedArc = QInputDialog::getItem(this, tr("Select edge"), tr("This is a reciprocated edge. " "Select direction:"), items, 0, false, &ok); if (selectedArc == arcA) { sourceNode = activeGraph->edgeClicked().source; targetNode = activeGraph->edgeClicked().target; } else if (selectedArc == arcB) { sourceNode = activeGraph->edgeClicked().target; targetNode = activeGraph->edgeClicked().source; } else { // both sourceNode = activeGraph->edgeClicked().source; targetNode = activeGraph->edgeClicked().target; changeBothEdges = true; } } else { // Clicked edge is not reciprocated. We are good to go. sourceNode = activeGraph->edgeClicked().source; targetNode = activeGraph->edgeClicked().target; } qCDebug(lcMainWindow) << "MW: slotEditEdgeWeight() from " << sourceNode << " to " << targetNode; } qreal oldWeight = 0; QString dialogTitle = "Edge " + QString::number(sourceNode) + "->" + QString::number(targetNode); bool undirected = activeGraph->isUndirected(); // Get the new edge weight -- only if the edge exists. if ((oldWeight = activeGraph->edgeWeight(sourceNode, targetNode)) != 0) { // Fix the dialog title. if (changeBothEdges || undirected) { dialogTitle = "Edge " + QString::number(sourceNode) + "<->" + QString::number(targetNode); } // Prompt the user for the new edge weight newWeight = (qreal)QInputDialog::getDouble( this, dialogTitle, tr("New edge weight: "), oldWeight, -RAND_MAX, RAND_MAX, 2, &ok); if (ok) { activeGraph->edgeWeightSet(sourceNode, targetNode, newWeight, undirected || changeBothEdges); } else { statusMessage(QString(tr("Change edge weight cancelled."))); return; } } } /** * @brief Symmetrizes the ties between every two connected nodes. * If there is an arc from Node A to Node B, * then a new arc from Node B to Node is created of the same weight. * Thus, all arcs become reciprocal and the network becomes symmetric * with a symmetric adjacency matrix */ void MainWindow::slotEditEdgeSymmetrizeAll() { if (activeEdges() == 0) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_EDGES); return; } qCDebug(lcMainWindow) << "Request to symmetrize all edges..."; activeGraph->setSymmetric(); slotHelpMessageToUser(USER_MSG_INFO, tr("All ties have been symmetrized."), tr("All ties between nodes have been symmetrized."), tr("The network is now symmetric. ")); } /** * @brief Adds a new cocitation symmetric relation to the network * * In the new relation, there are ties only between pairs of nodes who were cocited by others. */ void MainWindow::slotEditEdgeSymmetrizeCocitation() { if (activeEdges() == 0) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_EDGES); return; } qCDebug(lcMainWindow) << "Request to add a new symmetric relation using cocited nodes..."; runGraphOperationAsync( [this]() { activeGraph->relationAddCocitation(); }, tr("Computing cocitation relation..."), [this]() { slotHelpMessageToUser(USER_MSG_INFO, tr("New cocitation relation added. Ready"), tr("New cocitation relation has been added to the network."), tr("In the new relation, there are ties only between pairs of nodes who were cocited by others.")); }); } /** * @brief Opens up the edge dichotomization dialog */ void MainWindow::slotEditEdgeDichotomizationDialog() { // @TODO: Check if the network is already binary and abord? if (activeEdges() == 0) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_EDGES); return; } qCDebug(lcMainWindow) << "MW: slotEditEdgeDichotomizationDialog() - " "spawning edgeDichotomizationDialog"; m_edgeDichotomizationDialog = new DialogEdgeDichotomization(this); connect(m_edgeDichotomizationDialog, &DialogEdgeDichotomization::userChoices, this, &MainWindow::slotEditEdgeDichotomization); m_edgeDichotomizationDialog->exec(); } /** * @brief Calls Graph::graphDichotomization() to create a new binary relation * in a valued network using edge dichotomization according to threshold value. */ void MainWindow::slotEditEdgeDichotomization(const qreal threshold) { if (activeEdges() == 0) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_EDGES); return; } qCDebug(lcMainWindow, "MW: slotEditEdgeDichotomization() calling graphDichotomization()"); activeGraph->graphDichotomization(threshold); slotHelpMessageToUser(USER_MSG_INFO, tr("New binary relation added."), tr("New dichotomized relation created"), tr("A new relation called \"%1\" has been added to the network, " "using the given dichotomization threshold. ") .arg("Binary")); statusMessage(tr("Edge dichotomization finished. ")); } /** * @brief MainWindow::slotEditEdgeSymmetrizeStrongTies */ void MainWindow::slotEditEdgeSymmetrizeStrongTies() { if (activeEdges() == 0) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_EDGES); return; } qCDebug(lcMainWindow) << "MW::slotEditEdgeSymmetrizeStrongTies() - calling addRelationSymmetricStrongTies()"; int oldRelationsCounter = activeGraph->relations(); // callIt stays false (silent return, no info dialog) if the multi-relation dialog below is // dismissed without picking 1 or 2 - matches the original switch's lack of a default case. bool callIt = true; bool allRelations = false; if (oldRelationsCounter > 0) { switch ( slotHelpMessageToUser(USER_MSG_QUESTION_CUSTOM, tr("Select"), tr("Symmetrize social network by examining strong ties"), tr("This network has multiple relations. " "Symmetrize by examining reciprocated ties across all relations or just the current relation?"), QMessageBox::NoButton, QMessageBox::NoButton, tr("all relations"), tr("current relation"))) { case 1: allRelations = true; break; case 2: allRelations = false; break; default: callIt = false; break; } } if (!callIt) return; runGraphOperationAsync( [this, allRelations]() { activeGraph->addRelationSymmetricStrongTies(allRelations); }, tr("Symmetrizing strong ties..."), [this]() { slotHelpMessageToUser(USER_MSG_INFO, tr("New symmetric relation created from strong ties"), tr("New relation created from strong ties"), tr("A new relation \"%1\" has been added to the network. " "by counting reciprocated ties only. " "This relation is binary and symmetric. ") .arg("Strong Ties")); }); } /** * @brief Transforms all directed arcs to undirected edges. * The result is a undirected and symmetric network */ void MainWindow::slotEditEdgeUndirectedAll(const bool &toggle) { qCDebug(lcMainWindow) << "MW: slotEditEdgeUndirectedAll() - calling Graph::graphUndirectedSet()"; if (toggle) { activeGraph->setUndirected(true); optionsEdgeArrowsAct->setChecked(false); if (activeEdges() != 0) { statusMessage(tr("Undirected data mode. " "All existing directed edges transformed to " "undirected. Ready")); } else { statusMessage(tr("Undirected data mode. " "Any edge you add will be undirected. Ready")); } } else { activeGraph->setDirected(true); optionsEdgeArrowsAct->trigger(); optionsEdgeArrowsAct->setChecked(true); if (activeEdges() != 0) { statusMessage(tr("Directed data mode. " "All existing undirected edges transformed to " "directed. Ready")); } else { statusMessage(tr("Directed data mode. " "Any new edge you add will be directed. Ready")); } } } /** * @brief Toggles between directed (mode=0) and undirected edges (mode=1) * * @param mode */ void MainWindow::slotEditEdgeMode(const int &mode) { if (mode == 1) { qCDebug(lcMainWindow) << "Changing edge mode to undirected. Informing Graph..."; activeGraph->setUndirected(true); qCDebug(lcMainWindow) << "Setting optionsEdgeArrowsAct to false"; optionsEdgeArrowsAct->setChecked(false); if (activeEdges() != 0) { statusMessage(tr("Undirected data mode. " "All existing directed edges transformed to " "undirected. Ready")); } else { statusMessage(tr("Undirected data mode. " "Any edge you add will be undirected. Ready")); } } else { qCDebug(lcMainWindow) << "Changing edge mode to directed. Informing Graph..."; activeGraph->setDirected(true); qCDebug(lcMainWindow) << "Triggering optionsEdgeArrowsAct checkbox"; optionsEdgeArrowsAct->trigger(); qCDebug(lcMainWindow) << "Setting optionsEdgeArrowsAct to true"; optionsEdgeArrowsAct->setChecked(true); if (activeEdges() != 0) { statusMessage(tr("Directed data mode. " "All existing undirected edges transformed to " "directed.")); } else { statusMessage(tr("Directed data mode. " "Any new edge you add will be directed.")); } } } SocNetV-3.7/src/mainwindow/edit/mainwindow_edit_filters.cpp000066400000000000000000000432631524402210300242120ustar00rootroot00000000000000/** * @file mainwindow_edit_filters.cpp * @brief Implements MainWindow node/edge filtering (isolates, weight, ego network, attribute, query builder) and the nodes-to-edges transform. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "graphicsnode.h" #include "graphicsedge.h" #include "widgets/graphtablewidget.h" #include "forms/dialognodeedit.h" #include "forms/dialogedgeedit.h" #include "forms/dialogbulkedit.h" #include "forms/dialognodefind.h" #include "forms/dialogedgedichotomization.h" #include "forms/dialogfilteredgesbyweight.h" #include "forms/dialogfilternodesbycentrality.h" #include "forms/dialogfilterbyattribute.h" #include "forms/dialogquerybuilder.h" #include "widgets/filterbarwidget.h" #include /** * @brief Called when user selects a filter action in the Filter * selectbox of the toolbox Network group * @param selectedIndex */ void MainWindow::toolBoxFilterSelectChanged(const int &selectedIndex) { qCDebug(lcMainWindow) << "selected filter action, index: " << selectedIndex; switch (selectedIndex) { case 0: break; case 1: slotFilterNodesByEgoNetwork(); break; case 2: slotFilterNodesBySelection(); break; case 3: slotFilterNodesDialogByCentrality(); break; case 4: slotFilterNodesByAttribute(); break; case 5: slotEditFilterEdgesByWeightDialog(); break; case 6: slotFilterNodesRestoreAll(); break; case 7: slotEditFilterEdgesReset(); break; }; // Reset to placeholder after dispatching toolBoxFilterSelect->blockSignals(true); toolBoxFilterSelect->setCurrentIndex(0); toolBoxFilterSelect->blockSignals(false); } /** * @brief Shows a dialog where the user can specify criteria to filter nodes * */ void MainWindow::slotFilterNodesDialogByCentrality() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } // NOTE: Filter state is snapshot-based. vertexFilterRestoreAll() undoes // the last filter; filterNodesRestoreAllAct is enabled after apply. // // TODO Deferred (future issues): // - Auto-compute selected index if not yet computed (#216). // - Persist last-used index and threshold (#216). // - Add "Compute missing indices" shortcut in dialog (#216). QVector computedMask; const int count = prominenceIndexList.size(); computedMask.reserve(count); for (int i = 0; i < count; ++i) { const IndexType t = static_cast(i + 1); computedMask.append(activeGraph->isCentralityIndexComputed(t)); } DialogFilterNodesByCentrality dlg(prominenceIndexList, computedMask, this); // Not connected straight to activeGraph anymore: that resolved to a queued cross-thread // connection (dialog on GUI thread, Graph on graphThread) with no busy-guard and no progress // feedback - the same unprotected-async-dispatch shape WS15 P2 closed for computations, // just for filtering. Routed through a local lambda + runGraphOperationAsync instead. connect(&dlg, &DialogFilterNodesByCentrality::userChoices, this, [this](const float threshold, const bool overThreshold, const IndexType centralityIndex) { runGraphOperationAsync( [this, threshold, overThreshold, centralityIndex]() { activeGraph->vertexFilterByCentrality(threshold, overThreshold, centralityIndex); }, tr("Filtering by centrality..."), [this]() { filterNodesRestoreAllAct->setEnabled(true); m_filterChips.append({tr("Nodes: centrality filter"), FilterCondition::Scope::Nodes}); m_filterBar->addChip(tr("Nodes: centrality filter"), FilterCondition::Scope::Nodes); }); }); dlg.exec(); } /** * @brief Focuses the view on the currently selected nodes and edges between them. * * Hides all nodes not in the current selection. Only edges whose both * endpoints are selected remain visible. Implements #210. */ void MainWindow::slotFilterNodesBySelection() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const QList selection = activeGraph->getSelectedVertices(); if (selection.isEmpty()) { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("No nodes selected"), tr("Please select at least one node first.")); return; } runGraphOperationAsync( [this, selection]() { activeGraph->vertexFilterBySelection(selection); }, tr("Filtering by selection..."), [this]() { filterNodesRestoreAllAct->setEnabled(true); m_filterChips.append({tr("Nodes: selection"), FilterCondition::Scope::Nodes}); m_filterBar->addChip(tr("Nodes: selection"), FilterCondition::Scope::Nodes); }); } /** * @brief Triggers ego network focus mode on the currently selected node. */ void MainWindow::slotFilterNodesByEgoNetwork() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int v1 = activeGraph->vertexClicked(); if (v1 == 0) { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("No node selected"), tr("Please click on a node first.")); return; } runGraphOperationAsync( [this, v1]() { activeGraph->vertexFilterByEgoNetwork(v1); }, tr("Filtering ego network..."), [this]() { filterNodesRestoreAllAct->setEnabled(true); m_filterChips.append({tr("Nodes: ego network"), FilterCondition::Scope::Nodes}); m_filterBar->addChip(tr("Nodes: ego network"), FilterCondition::Scope::Nodes); }); } /** * @brief Opens the Filter By Attribute dialog and applies the chosen condition. * * Handles both node and edge attribute filtering depending on the scope * selected in the dialog. */ void MainWindow::slotFilterNodesByAttribute() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const QStringList nodeKeys = activeGraph->graphHasVertexCustomAttributes(); const QStringList edgeKeys = activeGraph->graphHasEdgeCustomAttributes(); if (nodeKeys.isEmpty() && edgeKeys.isEmpty()) { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("No custom attributes"), tr("This network has no custom node or edge attributes. " "Add attributes via the Data Table (Ctrl+D) using " "\"Add attribute…\", or via the Node / Edge Properties dialogs.")); return; } DialogFilterByAttribute dlg(nodeKeys, edgeKeys, this); connect(&dlg, &DialogFilterByAttribute::userChoices, this, [this](const FilterCondition &cond) { runGraphOperationAsync( [this, cond]() { if (cond.scope == FilterCondition::Scope::Edges) { activeGraph->edgeFilterByAttribute(cond); } else if (cond.scope == FilterCondition::Scope::Both) { activeGraph->vertexFilterByAttribute(cond); activeGraph->edgeFilterByAttribute(cond); } else { activeGraph->vertexFilterByAttribute(cond); } }, tr("Filtering by attribute..."), [this, cond]() { if (cond.scope == FilterCondition::Scope::Edges) { m_filterChips.append({cond.label(), FilterCondition::Scope::Edges}); m_filterBar->addChip(cond.label(), FilterCondition::Scope::Edges); } else if (cond.scope == FilterCondition::Scope::Both) { // Split into two chips so each scope can be closed independently. FilterCondition nc = cond; nc.scope = FilterCondition::Scope::Nodes; FilterCondition ec = cond; ec.scope = FilterCondition::Scope::Edges; m_filterChips.append({nc.label(), FilterCondition::Scope::Nodes}); m_filterChips.append({ec.label(), FilterCondition::Scope::Edges}); m_filterBar->addChip(nc.label(), FilterCondition::Scope::Nodes); m_filterBar->addChip(ec.label(), FilterCondition::Scope::Edges); } else { m_filterChips.append({cond.label(), FilterCondition::Scope::Nodes}); m_filterBar->addChip(cond.label(), FilterCondition::Scope::Nodes); } if (cond.scope != FilterCondition::Scope::Edges) filterNodesRestoreAllAct->setEnabled(true); if (cond.scope == FilterCondition::Scope::Edges || cond.scope == FilterCondition::Scope::Both) editFilterEdgesRestoreAllAct->setEnabled(true); }); }); dlg.exec(); if (dlg.wasQueryBuilderRequested()) slotFilterByQueryBuilder(); } /** * @brief Opens the Query Builder dialog for multi-condition AND filtering. * * Scope (Nodes / Edges) is selected inside the dialog. * Applies one compound filter that counts as a single chip and one snapshot. */ void MainWindow::slotFilterByQueryBuilder() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const QStringList nodeKeys = activeGraph->graphHasVertexCustomAttributes(); const QStringList edgeKeys = activeGraph->graphHasEdgeCustomAttributes(); if (nodeKeys.isEmpty() && edgeKeys.isEmpty()) { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("No custom attributes"), tr("This network has no custom node or edge attributes. " "Add attributes via the Data Table (Ctrl+D).")); return; } DialogQueryBuilder *dlg = new DialogQueryBuilder(nodeKeys, edgeKeys, this); dlg->setAttribute(Qt::WA_DeleteOnClose); connect(dlg, &DialogQueryBuilder::userChoices, this, [this](const GraphQuery &query) { if (query.conditions.isEmpty()) return; const FilterCondition::Scope scope = query.conditions.first().scope; const int n = query.conditions.size(); runGraphOperationAsync( [this, query, scope]() { if (scope == FilterCondition::Scope::Edges) { activeGraph->edgeFilterByQuery(query); } else { activeGraph->vertexFilterByQuery(query); } }, tr("Applying query filter..."), [this, scope, n]() { if (scope == FilterCondition::Scope::Edges) { const QString label = tr("Edges: query (%1 condition(s))").arg(n); m_filterChips.append({label, FilterCondition::Scope::Edges}); m_filterBar->addChip(label, FilterCondition::Scope::Edges); editFilterEdgesRestoreAllAct->setEnabled(true); } else { const QString label = tr("Nodes: query (%1 condition(s))").arg(n); m_filterChips.append({label, FilterCondition::Scope::Nodes}); m_filterBar->addChip(label, FilterCondition::Scope::Nodes); filterNodesRestoreAllAct->setEnabled(true); } }); }); dlg->exec(); } /** * @brief Restores all nodes hidden by the last filter. */ void MainWindow::slotFilterNodesRestoreAll() { // Find the last non-edge chip and remove it via the unified stack mechanism. int lastNodeIdx = -1; for (int i = m_filterChips.size() - 1; i >= 0; --i) { if (m_filterChips[i].second != FilterCondition::Scope::Edges) { lastNodeIdx = i; break; } } if (lastNodeIdx < 0) return; runGraphOperationAsync( [this, lastNodeIdx]() { activeGraph->vertexFilterRemoveAt(lastNodeIdx); }, tr("Restoring filtered nodes..."), [this, lastNodeIdx]() { m_filterChips.removeAt(lastNodeIdx); m_filterBar->clearAllChips(); for (const auto &chip : std::as_const(m_filterChips)) m_filterBar->addChip(chip.first, chip.second); bool hasNodeFilters = false; for (const auto &chip : std::as_const(m_filterChips)) if (chip.second != FilterCondition::Scope::Edges) { hasNodeFilters = true; break; } filterNodesRestoreAllAct->setEnabled(hasNodeFilters); }); } /** * @brief Toggles the status of all isolated vertices * * @param checked */ void MainWindow::slotEditFilterNodesIsolates(bool checked) { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const bool toggleTo = !editFilterNodesIsolatesAct->isChecked(); runGraphOperationAsync( [this, toggleTo]() { activeGraph->vertexIsolatedAllToggle(toggleTo); }, tr("Toggling isolated nodes..."), [this, checked]() { if (checked) { statusMessage(tr("Isolated nodes disabled.")); } else { statusMessage(tr("Isolated nodes enabled.")); } }); } /** * @brief Shows a dialog where the user can specify how to filter edges by their weight * * All edges weighted more (or less) than the specified weight will be disabled. */ void MainWindow::slotEditFilterEdgesByWeightDialog() { // Note: We do not check if there are active edges, because the user might have disabled all edges previously. // Create a new edge filtering dialog m_DialogEdgeFilterByWeight = new DialogFilterEdgesByWeight(this); // Was two separate connections to userChoices: one straight to activeGraph (a queued // cross-thread dispatch with no busy-guard/progress feedback - the same shape WS15 P2 closed // for computations), and a second, entirely independent one doing the GUI chip bookkeeping // immediately, with no actual dependency on the filter having completed. Merged into one // handler via runGraphOperationAsync, so the chip only appears once the filter is done. connect(m_DialogEdgeFilterByWeight, &DialogFilterEdgesByWeight::userChoices, this, [this](const qreal threshold, const bool overThreshold) { runGraphOperationAsync( [this, threshold, overThreshold]() { activeGraph->edgeFilterByWeight(threshold, overThreshold); }, tr("Filtering edges by weight..."), [this]() { editFilterEdgesRestoreAllAct->setEnabled(true); m_filterChips.append({tr("Edges: weight filter"), FilterCondition::Scope::Edges}); m_filterBar->addChip(tr("Edges: weight filter"), FilterCondition::Scope::Edges); }); }); // Show the dialog m_DialogEdgeFilterByWeight->exec(); } /** * @brief Restores all edges hidden by the weight filter. */ void MainWindow::slotEditFilterEdgesReset() { // Collect edge-scope chip indices first (highest index first, so each // vertexFilterRemoveAt below - and the matching m_filterChips removal in // onComplete - operates on the correct stack position without the two // lists needing to be mutated in lockstep inside the same loop anymore. QList edgeChipIndices; for (int i = m_filterChips.size() - 1; i >= 0; --i) { if (m_filterChips[i].second == FilterCondition::Scope::Edges) edgeChipIndices.append(i); } runGraphOperationAsync( [this, edgeChipIndices]() { for (int i : edgeChipIndices) activeGraph->vertexFilterRemoveAt(i); }, tr("Resetting edge filters..."), [this, edgeChipIndices]() { for (int i : edgeChipIndices) m_filterChips.removeAt(i); m_filterBar->clearAllChips(); for (const auto &chip : std::as_const(m_filterChips)) m_filterBar->addChip(chip.first, chip.second); editFilterEdgesRestoreAllAct->setEnabled(false); }); } /** * @brief Toggles the status of all unilateral edges * * @param checked */ void MainWindow::slotEditFilterEdgesUnilateral(bool checked) { if (!activeEdges() && editFilterEdgesUnilateralAct->isChecked()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_EDGES); return; } const bool toggleTo = !editFilterEdgesUnilateralAct->isChecked(); runGraphOperationAsync( [this, toggleTo]() { activeGraph->edgeFilterUnilateral(toggleTo); }, tr("Filtering unilateral edges..."), [this, checked]() { if (checked) { statusMessage(tr("Unilateral (weak) edges disabled.")); } else { statusMessage(tr("Unilateral (weak) edges enabled.")); } }); } void MainWindow::slotEditTransformNodes2Edges() { } SocNetV-3.7/src/mainwindow/edit/mainwindow_edit_node.cpp000066400000000000000000001145031524402210300234630ustar00rootroot00000000000000/** * @file mainwindow_edit_node.cpp * @brief Implements MainWindow Edit menu node operations: add/remove, properties, appearance, selection, subgraph-to-shape transforms. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "graphicsnode.h" #include "graphicsedge.h" #include "widgets/graphtablewidget.h" #include "forms/dialognodeedit.h" #include "forms/dialogedgeedit.h" #include "forms/dialogbulkedit.h" #include "forms/dialognodefind.h" #include "forms/dialogedgedichotomization.h" #include "forms/dialogfilteredgesbyweight.h" #include "forms/dialogfilternodesbycentrality.h" #include "forms/dialogfilterbyattribute.h" #include "forms/dialogquerybuilder.h" #include /** * @brief Called when user selects something in the Subgraph from Selected * Nodes selectbox of the toolbox * @param selectedIndex */ void MainWindow::toolBoxEditNodeSubgraphSelectChanged(const int &selectedIndex) { qCDebug(lcMainWindow) << "selected subgraph creation, text index: " << selectedIndex; switch (selectedIndex) { case 0: break; case 1: slotEditNodeSelectedToClique(); break; case 2: slotEditNodeSelectedToStar(); break; case 3: slotEditNodeSelectedToCycle(); break; case 4: slotEditNodeSelectedToLine(); break; }; qCDebug(lcMainWindow) << "Calling initComboBoxes() "; initComboBoxes(); } /** * @brief Selects all nodes */ void MainWindow::slotEditNodeSelectAll() { qCDebug(lcMainWindow) << "Request to select all nodes..."; graphicsWidget->selectAll(); statusMessage(tr("Selected nodes: %1") .arg(activeGraph->getSelectedVerticesCount())); } /** * @brief Selects no nodes. */ void MainWindow::slotEditNodeSelectNone() { qCDebug(lcMainWindow) << "Clearing node selection..."; graphicsWidget->selectNone(); statusMessage(QString(tr("Selection cleared"))); } /** * @brief Automatically runs, when the user moves a node on the canvas, to * update new vertex coordinates in Graph, and show a status message. * * Called from GraphicsWidget * * @param nodeNumber * @param x * @param y */ void MainWindow::slotEditNodePosition(const int &nodeNumber, const int &x, const int &y) { qCDebug(lcMainWindow, "Updating position for node %i - x: %i, y: %i", nodeNumber, x, y); activeGraph->vertexPosSet(nodeNumber, x, y); } /** * @brief Adds a new random node * * Called when the "Add Node" btn is clicked */ void MainWindow::slotEditNodeAdd() { qCDebug(lcMainWindow) << "Request to add a new random node..."; activeGraph->vertexCreateAtPosRandom(true); statusMessage(tr("New random positioned node (numbered %1) added.") .arg(activeGraph->vertexNumberMax())); } /** * @brief Opens the Find Node dialog */ void MainWindow::slotEditNodeFindDialog() { qCDebug(lcMainWindow) << "Showing find node dialog..."; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } //@TODO - prominenceIndexList should be either // the list of all computes indices // or the last computed indice // or empty if the user has not computed any index yet. m_nodeFindDialog = new DialogNodeFind(this, prominenceIndexList); connect(m_nodeFindDialog, &DialogNodeFind::userChoices, this, &MainWindow::slotEditNodeFind); m_nodeFindDialog->exec(); statusMessage(tr("Node find dialog opened. Enter your choices. ")); return; } /** * @brief Finds one or more nodes, according to their number, label or centrality score. * * @param list * @param searchType * @param indexStr */ void MainWindow::slotEditNodeFind(const QStringList &nodeList, const QString &searchType, const QString &indexStr) { qCDebug(lcMainWindow) << "Request to find nodes:" << nodeList << "search type:" << searchType << "indexStr" << indexStr; int indexType = 0; if (searchType == "numbers") { activeGraph->vertexFindByNumber(nodeList); } else if (searchType == "labels") { activeGraph->vertexFindByLabel(nodeList); } else if (searchType == "score") { indexType = activeGraph->getProminenceIndexByName(indexStr); const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolates = editFilterNodesIsolatesAct->isChecked(); const bool inverseWeightsFinal = inverseWeights; runGraphOperationAsync( [this, indexType, nodeList, considerWeights, inverseWeightsFinal, dropIsolates]() { activeGraph->vertexFindByIndexScore(indexType, nodeList, considerWeights, inverseWeightsFinal, dropIsolates); }, tr("Finding nodes by index score. Please wait...")); } return; } /** * @brief Handles requests to delete a node and the attached objects (edges, etc). * * If the user has clicked on a node, it deletes it * Else it asks for a nodeNumber to remove. */ void MainWindow::slotEditNodeRemove() { qCDebug(lcMainWindow) << "Request to remove a node..."; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } if (activeGraph->relations() > 1) { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error. Cannot remove node!"), tr("Error. Cannot remove this node!"), tr("This a network with more than 1 relations. If you remove " "a node from the active relation, and then ask me to go " "to the previous or the next relation, then I would crash " "because I would try to display edges from a deleted node." "You cannot remove nodes in multirelational networks.")); return; } // if there are already multiple nodes selected, erase them int nodesSelected = activeGraph->getSelectedVerticesCount(); if (nodesSelected > 0) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); qCDebug(lcMainWindow) << "multiple nodes selected to be removed"; foreach (int nodeNumber, activeGraph->getSelectedVertices()) { activeGraph->vertexRemove(nodeNumber); } editNodeRemoveAct->setText(tr("Remove Node")); statusMessage(tr("Removed %1 nodes.").arg(nodesSelected)); QApplication::restoreOverrideCursor(); } else { int nodeNumber = -1, min = -1, max = -1; bool ok = false; min = activeGraph->vertexNumberMin(); max = activeGraph->vertexNumberMax(); if (min == -1 || max == -1) { qCDebug(lcMainWindow, "ERROR in finding min max nodeNumbers. Abort"); return; } else { nodeNumber = QInputDialog::getInt( this, tr("Remove node"), tr("Choose a node to remove between (" + QString::number(min).toLatin1() + "..." + QString::number(max).toLatin1() + "):"), min, 1, max, 1, &ok); if (!ok) { statusMessage("Remove node operation cancelled."); return; } } qCDebug(lcMainWindow, "removing vertex with number %i from Graph", nodeNumber); activeGraph->vertexRemove(nodeNumber); qCDebug(lcMainWindow, "Completed. Node %i removed completely.", nodeNumber); statusMessage(tr("Node removed completely.")); } } /** * @brief Opens the Node Properties dialog for the selected nodes. * If no nodes are selected, prompts the user for a node number. */ void MainWindow::slotEditNodePropertiesDialog() { qCDebug(lcMainWindow) << "Request to open the node properties dialog..."; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int selectedNodesCount = activeGraph->getSelectedVerticesCount(); if (selectedNodesCount == 0) { statusMessage(tr("Select a node first to edit its properties.")); return; } if (selectedNodesCount > 1) { slotEditNodeSetPropertyForSelection(); return; } // Exactly one node selected β€” open the full properties dialog const int nodeNumber = activeGraph->getSelectedVertices().constFirst(); const QString label = activeGraph->vertexLabel(nodeNumber); const QColor color = activeGraph->vertexColor(nodeNumber); const QString shape = activeGraph->vertexShape(nodeNumber); const int size = activeGraph->vertexSize(nodeNumber); const QString iconPath = activeGraph->vertexShapeIconPath(nodeNumber); const QHash customAttributes = activeGraph->vertexCustomAttributes(nodeNumber); qCDebug(lcMainWindow) << "opening DialogNodeEdit for node" << nodeNumber; std::unique_ptr m_nodeEditDialog = std::make_unique( this, nodeShapeList, iconPathList, label, size, color, shape, iconPath, customAttributes); connect(m_nodeEditDialog.get(), &DialogNodeEdit::userChoices, this, &MainWindow::slotEditNodeProperties); m_nodeEditDialog->exec(); } /** * @brief Applies the selected properties to one or multiple nodes in the graph. * * This slot updates the properties of the selected nodes or a single node, as * specified by the user in DialogNodeEdit. It updates the label, size, color, shape, and custom * attributes of the nodes. * * @param label The new label for the node(s). * @param size The new size for the node(s). * @param color The new color for the node(s). * @param shape The new shape for the node(s). * @param iconPath The path to the icon for the node(s). * @param customAttributes A hash of custom attributes to set for the node(s). */ void MainWindow::slotEditNodeProperties(const QString &label, const int &size, const QColor &color, const QString &shape, const QString &iconPath, const QHash &customAttributes) { int selectedNodesCount = activeGraph->getSelectedVerticesCount(); qCDebug(lcMainWindow) << "Request to update node properties - new properties: " << " label " << label << " size " << size << " color " << color << " shape " << shape << " vertexClicked " << activeGraph->vertexClicked() << " selectedNodesCount " << selectedNodesCount << "customAttributes" << customAttributes; if (selectedNodesCount == 0 && activeGraph->vertexClicked() != 0) { // no node selected but user entered a node number in a dialog if (label != "" && appSettings["initNodeLabelsVisibility"] != "true") slotOptionsNodeLabelsVisibility(true); activeGraph->vertexLabelSet(activeGraph->vertexClicked(), label); activeGraph->vertexColorSet(activeGraph->vertexClicked(), color.name()); activeGraph->vertexSizeSet(activeGraph->vertexClicked(), size); activeGraph->vertexShapeSet(activeGraph->vertexClicked(), shape, iconPath); activeGraph->vertexCustomAttributesSet(activeGraph->vertexClicked(), customAttributes); statusMessage(tr("Updated the properties of node %1. ").arg(activeGraph->vertexClicked())); } else { // some nodes are selected int nodeNumber = 0; foreach (nodeNumber, activeGraph->getSelectedVertices()) { qCDebug(lcMainWindow) << "node " << nodeNumber; if (!label.isEmpty()) { if (selectedNodesCount > 1) { activeGraph->vertexLabelSet( nodeNumber, label + QString::number(nodeNumber)); } else { activeGraph->vertexLabelSet(nodeNumber, label); } // turn on labels visibility if they are hidden if (appSettings["initNodeLabelsVisibility"] != "true") { slotOptionsNodeLabelsVisibility(true); } } activeGraph->vertexColorSet(nodeNumber, color.name()); activeGraph->vertexSizeSet(nodeNumber, size); activeGraph->vertexShapeSet(nodeNumber, shape, iconPath); activeGraph->vertexCustomAttributesSet(nodeNumber, customAttributes); } statusMessage(tr("Updated the properties of %1 nodes. ").arg(selectedNodesCount)); } } /** * @brief Raises the Data Table dock and pre-selects rows matching the current canvas selection. */ void MainWindow::slotEditNodeEditSelectionInTable() { if (!m_tableDock || !m_tableWidget) return; m_tableDock->show(); m_tableDock->raise(); const QList nodes = activeGraph->getSelectedVertices(); const QList edges = activeGraph->getSelectedEdges(); // Only switch to Edges tab when the selection is entirely edges (no nodes) if (!edges.isEmpty() && nodes.isEmpty()) m_tableWidget->showEdgesTab(); else m_tableWidget->showNodesTab(); m_tableWidget->syncNodeSelection(nodes); m_tableWidget->syncEdgeSelection(edges); } /** * @brief Opens DialogBulkEdit for the currently selected nodes (canvas shortcut). */ void MainWindow::slotEditNodeSetPropertyForSelection() { const QList selectedNodes = activeGraph->getSelectedVertices(); const int count = selectedNodes.size(); if (count == 0) { statusMessage(tr("No nodes selected.")); return; } // Collect unique custom attribute keys across the selection QSet keySet; for (const int v : selectedNodes) for (const QString &k : activeGraph->vertexCustomAttributes(v).keys()) keySet.insert(k); const QStringList existingKeys(keySet.begin(), keySet.end()); auto dlg = std::make_unique( DialogBulkEdit::Scope::Nodes, existingKeys, nodeShapeList, iconPathList, count, false, this); connect(dlg.get(), &DialogBulkEdit::userChoices, this, [this, selectedNodes](const QString &property, const QString &value) { // Resolved once here (doesn't depend on which vertex) rather than per-iteration inside // the dispatched lambda below, which also keeps that lambda from reading // nodeShapeList/iconPathList (MainWindow-owned) on graphThread at all. QString shapeIconPath; if (property == QLatin1String("Shape")) { const int idx = nodeShapeList.indexOf(value); shapeIconPath = (idx >= 0 && idx < iconPathList.size()) ? iconPathList[idx] : QString(); } runGraphOperationAsync( [this, selectedNodes, property, value, shapeIconPath]() { for (const int v : selectedNodes) { if (property == QLatin1String("Label")) { activeGraph->vertexLabelSet(v, value); } else if (property == QLatin1String("Size")) { activeGraph->vertexSizeSet(v, value.toInt()); } else if (property == QLatin1String("Color")) { activeGraph->vertexColorSet(v, value); } else if (property == QLatin1String("Shape")) { activeGraph->vertexShapeSet(v, value, shapeIconPath); } else { activeGraph->vertexCustomAttributeSet(v, property, value); } } }, tr("Applying bulk edit..."), [this, selectedNodes, property]() { statusMessage(tr("Set '%1' on %2 node(s).").arg(property).arg(selectedNodes.size())); if (m_tableDock && m_tableDock->isVisible()) m_tableWidget->refresh(activeGraph); }); }); dlg->exec(); } /** * @brief Creates a complete subgraph (clique) from selected nodes. */ void MainWindow::slotEditNodeSelectedToClique() { qCDebug(lcMainWindow) << "MW::slotEditNodeSelectedToClique()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } int selectedNodesCount = activeGraph->getSelectedVerticesCount(); if (selectedNodesCount < 3) { slotHelpMessageToUser(USER_MSG_INFO, tr("Error. Not enough nodes selected."), tr("Cannot create new clique because you have " "not selected enough nodes."), tr("Select at least three nodes first.")); return; } runGraphOperationAsync( [this]() { activeGraph->verticesCreateSubgraph(QList(), SUBGRAPH_CLIQUE); }, tr("Creating subgraph. Please wait..."), [this, selectedNodesCount]() { if (activeGraph->progressCanceled()) { return; } slotHelpMessageToUser(USER_MSG_INFO, tr("Clique created."), tr("A new clique has been created from ") + QString::number(selectedNodesCount) + tr(" nodes")); }); } /** * @brief Creates a star subgraph from selected nodes. * User must choose a central node. */ void MainWindow::slotEditNodeSelectedToStar() { qCDebug(lcMainWindow) << "MW::slotEditNodeSelectedToStar()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } int selectedNodesCount = activeGraph->getSelectedVerticesCount(); if (selectedNodesCount < 3) { slotHelpMessageToUser(USER_MSG_INFO, tr("Not enough nodes selected."), tr("Cannot create new star subgraph because you have " "not selected enough nodes."), tr("Select at least three nodes first.")); return; } int center; bool ok = false; int min = activeGraph->getSelectedVerticesMin(); int max = activeGraph->getSelectedVerticesMax(); center = QInputDialog::getInt( this, "Create star subgraph", tr("To create a star subgraph from selected nodes, \n" "enter the number of the central actor (" + QString::number(min).toLatin1() + "..." + QString::number(max).toLatin1() + "):"), min, 1, max, 1, &ok); if (!ok) { statusMessage("Create star subgraph cancelled."); return; } runGraphOperationAsync( [this, center]() { activeGraph->verticesCreateSubgraph(QList(), SUBGRAPH_STAR, center); }, tr("Creating subgraph. Please wait..."), [this, selectedNodesCount]() { if (activeGraph->progressCanceled()) { return; } slotHelpMessageToUser(USER_MSG_INFO, tr("Star subgraph created."), tr("A new star subgraph has been created with ") + QString::number(selectedNodesCount) + tr(" nodes.")); }); } /** * @brief Creates a cycle subgraph from selected nodes. */ void MainWindow::slotEditNodeSelectedToCycle() { qCDebug(lcMainWindow) << "MW::slotEditNodeSelectedToCycle()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } int selectedNodesCount = activeGraph->getSelectedVerticesCount(); if (selectedNodesCount < 3) { slotHelpMessageToUser(USER_MSG_INFO, tr("Not enough nodes selected."), tr("Cannot create new cycle subgraph because you have " "not selected enough nodes."), tr("Select at least three nodes first.")); return; } runGraphOperationAsync( [this]() { activeGraph->verticesCreateSubgraph(QList(), SUBGRAPH_CYCLE); }, tr("Creating subgraph. Please wait..."), [this, selectedNodesCount]() { if (activeGraph->progressCanceled()) { return; } slotHelpMessageToUser(USER_MSG_INFO, tr("Cycle subgraph created."), tr("A new cycle subgraph has been created with ") + QString::number(selectedNodesCount) + tr(" select nodes.")); }); } /** * @brief Creates a line subgraph from selected nodes. */ void MainWindow::slotEditNodeSelectedToLine() { qCDebug(lcMainWindow) << "MW::slotEditNodeSelectedToLine()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } int selectedNodesCount = activeGraph->getSelectedVerticesCount(); if (selectedNodesCount < 3) { slotHelpMessageToUser(USER_MSG_INFO, tr("Not enough nodes selected."), tr("Cannot create new line subgraph because you have " "not selected enough nodes."), tr("Select at least three nodes first.")); return; } runGraphOperationAsync( [this]() { activeGraph->verticesCreateSubgraph(QList(), SUBGRAPH_LINE); }, tr("Creating subgraph. Please wait..."), [this, selectedNodesCount]() { if (activeGraph->progressCanceled()) { return; } slotHelpMessageToUser(USER_MSG_INFO, tr("Line subgraph created."), tr("A new line subgraph has been created with ") + QString::number(selectedNodesCount) + tr(" selected nodes.")); }); } /** * @brief Changes the color of all nodes to parameter color * * If the color is invalid, opens a QColorDialog to * select a new node color for all nodes. * * @param color */ void MainWindow::slotEditNodeColorAll(QColor color) { if (!color.isValid()) { color = QColorDialog::getColor(QColor(appSettings["initNodeColor"]), this, "Change the color of all nodes"); } if (color.isValid()) { appSettings["initNodeColor"] = color.name(); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); qCDebug(lcMainWindow) << "MW::slotEditNodeColorAll() : " << appSettings["initNodeColor"]; activeGraph->vertexColorSet(0, appSettings["initNodeColor"]); QApplication::restoreOverrideCursor(); statusMessage(tr("Change all nodes' color. ")); } else { // user pressed Cancel statusMessage(tr("Invalid color. ")); } } /** * @brief Changes the size of nodes to newSize. * * If newSize = 0 asks the user a new size for all nodes * If normalized = true, changes node sizes according to their amount * * @param newSize * @param normalized */ void MainWindow::slotEditNodeSizeAll(int newSize, const bool &normalized) { Q_UNUSED(normalized); qCDebug(lcMainWindow) << "MW: slotEditNodeSizeAll() - " << " newSize " << newSize; if (newSize == 0 && !normalized) { bool ok = true; newSize = QInputDialog::getInt( this, "Change node size", tr("Select new size for all nodes:"), appSettings["initNodeSize"].toInt(0, 10), 1, 100, 1, &ok); if (!ok) { statusMessage("Change node size operation cancelled."); return; } } appSettings["initNodeSize"] = QString::number(newSize); activeGraph->vertexSizeSet(0, newSize); statusMessage(tr("Ready")); return; } /** * @brief Change the shape of a node or all nodes. * If shape == null, prompts the user a list of available node shapes to select. * Then changes the shape of all nodes/vertices accordingly. * If vertex is non-zero, changes the shape of that node only. * Called when user clicks on Edit->Node > Change all nodes shapes * Called from DialogSettings when the user has selected a new default node shape * @param shape * @param vertex */ void MainWindow::slotEditNodeShape(const int &vertex, QString shape, QString nodeIconPath) { qCDebug(lcMainWindow) << "MW::slotEditNodeShape() - vertex " << vertex << "(0 means all)" << "new shape" << shape << "nodeIconPath" << nodeIconPath; if (shape.isNull()) { bool ok = false; int curShapeIndex = nodeShapeList.indexOf(appSettings["initNodeShape"]); if (curShapeIndex == -1) { curShapeIndex = 1; } shape = QInputDialog::getItem(this, "Node shape", "Select a shape for all nodes: ", nodeShapeList, curShapeIndex, true, &ok); if (!ok) { // user pressed Cancel statusMessage(tr("Change node shapes aborted.")); return; } if (shape == "custom") { nodeIconPath = QFileDialog::getOpenFileName( this, tr("Select an icon"), getLastPath(), tr("Images (*.png *.jpg *.jpeg *.svg);;All (*.*)")); if (nodeIconPath.isNull()) { // user pressed Cancel statusMessage(tr("Change node shapes aborted.")); return; } } else { nodeIconPath = iconPathList[nodeShapeList.indexOf(shape)]; } } if (vertex == 0) { // change all nodes shapes activeGraph->vertexShapeSet(-1, shape, nodeIconPath); appSettings["initNodeShape"] = shape; appSettings["initNodeIconPath"] = nodeIconPath; statusMessage(tr("All shapes have been changed.")); } else { // only one activeGraph->vertexShapeSet(vertex, shape, nodeIconPath); statusMessage(tr("Node shape has been changed.")); } } /** * @brief Changes the size of one or all node numbers. * Called from Edit menu option and DialogSettings * if newSize=0, asks the user to enter a new node number font size * if v1=0, it changes all node numbers * @param v1 * @param newSize */ void MainWindow::slotEditNodeNumberSize(int v1, int newSize, const bool prompt) { bool ok = false; qCDebug(lcMainWindow) << "MW::slotEditNodeNumberSize - newSize " << newSize; if (prompt) { newSize = QInputDialog::getInt(this, "Change text size", tr("Change all node numbers size to: (1-16)"), appSettings["initNodeNumberSize"].toInt(0, 10), 1, 16, 1, &ok); if (!ok) { statusMessage(tr("Change font size: Aborted.")); return; } } if (v1) { // change one node number only activeGraph->vertexNumberSizeSet(v1, newSize); } else { // change all appSettings["initNodeNumberSize"] = QString::number(newSize); activeGraph->vertexNumberSizeSet(0, newSize); } statusMessage(tr("Changed node numbers size.")); } /** * @brief Changes the text color of all node numbers * Called from Edit menu option and Settings dialog. * If color is invalid, asks the user to enter a new node number color * @param color */ void MainWindow::slotEditNodeNumbersColor(const int &v1, QColor color) { qCDebug(lcMainWindow) << "MW:slotEditNodeNumbersColor() - new color " << color; if (!color.isValid()) { color = QColorDialog::getColor(QColor(appSettings["initNodeNumberColor"]), this, "Change the color of all node numbers"); } if (color.isValid()) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); if (v1) { activeGraph->vertexNumberColorSet(v1, color.name()); } else { appSettings["initNodeNumberColor"] = color.name(); activeGraph->vertexNumberColorSet(0, color.name()); } QApplication::restoreOverrideCursor(); statusMessage(tr("Node number color changed. ")); } else { // user pressed Cancel statusMessage(tr("Invalid color. ")); } } /** * @brief Changes the distance of one or all node numbers from their nodes. * Called from Edit menu option and DialogSettings * if newDistance=0, asks the user to enter a new node number distance * if v1=0, it changes all node number distances * @param v1 * @param newDistance */ void MainWindow::slotEditNodeNumberDistance(int v1, int newDistance) { bool ok = false; qCDebug(lcMainWindow) << "MW::slotEditNodeNumberDistance - newSize " << newDistance; if (!newDistance) { newDistance = QInputDialog::getInt( this, "Change node number distance", tr("Change all node numbers distance from their nodes to: (1-16)"), appSettings["initNodeNumberDistance"].toInt(0, 10), 1, 16, 1, &ok); if (!ok) { statusMessage(tr("Change node number distance aborted.")); return; } } if (v1) { // change one node number distance only activeGraph->vertexNumberDistanceSet(v1, newDistance); } else { // change all appSettings["initNodeNumberDistance"] = QString::number(newDistance); activeGraph->vertexNumberDistanceSet(0, newDistance); } statusMessage(tr("Changed node number distance.")); } /** * @brief Changes the size of one or all node Labels. * Called from Edit menu option and DialogSettings * if newSize=0, asks the user to enter a new node Label font size * if v1=0, it changes all node Labels * @param v1 * @param newSize */ void MainWindow::slotEditNodeLabelSize(const int v1, int newSize) { bool ok = false; qCDebug(lcMainWindow) << "MW::slotEditNodeLabelSize - newSize " << newSize; if (!newSize) { newSize = QInputDialog::getInt(this, "Change text size", tr("Change all node labels text size to: (1-16)"), appSettings["initNodeLabelSize"].toInt(0, 10), 1, 32, 1, &ok); if (!ok) { statusMessage(tr("Change font size: Aborted.")); return; } } if (v1) { // change one node Label only activeGraph->vertexLabelSizeSet(v1, newSize); } else { // change all appSettings["initNodeLabelSize"] = QString::number(newSize); activeGraph->vertexLabelSizeSet(0, newSize); } statusMessage(tr("Changed node label size.")); } /** * @brief Changes the color of all node labels. * Asks the user to enter a new node label color */ void MainWindow::slotEditNodeLabelsColor(QColor color) { qCDebug(lcMainWindow) << "MW::slotEditNodeNumbersColor() - new color " << color; if (!color.isValid()) { color = QColorDialog::getColor(QColor(appSettings["initNodeLabelColor"]), this, "Change the color of all node labels"); } if (color.isValid()) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); activeGraph->vertexLabelColorSet(0, color.name()); appSettings["initNodeLabelColor"] = color.name(); optionsNodeLabelsVisibilityAct->setChecked(true); QApplication::restoreOverrideCursor(); statusMessage(tr("Label colors changed. ")); } else { // user pressed Cancel statusMessage(tr("Invalid color. ")); } } /** * @brief MainWindow::slotEditNodeLabelDistance * Changes the distance of one or all node label from their nodes. * Called from Edit menu option and DialogSettings * if newDistance=0, asks the user to enter a new node label distance * if v1=0, it changes all node label distances * @param v1 * @param newDistance */ void MainWindow::slotEditNodeLabelDistance(int v1, int newDistance) { bool ok = false; qCDebug(lcMainWindow) << "MW::slotEditNodeLabelDistance - newSize " << newDistance; if (!newDistance) { newDistance = QInputDialog::getInt( this, "Change node label distance", tr("Change all node labels distance from their nodes to: (1-16)"), appSettings["initNodeLabelDistance"].toInt(0, 10), 1, 16, 1, &ok); if (!ok) { statusMessage(tr("Change node label distance aborted.")); return; } } if (v1) { // change one node label distance only activeGraph->vertexLabelDistanceSet(v1, newDistance); } else { // change all appSettings["initNodeLabelDistance"] = QString::number(newDistance); activeGraph->vertexLabelDistanceAllSet(newDistance); } statusMessage(tr("Changed node label distance.")); } /** * @brief Shows a context menu when the user right-clicks directly on a node. * * The menu is always selection-aware β€” it reflects the total number of currently * selected nodes, not just the node that was right-clicked: * - Single node selected: offers Node Properties (full per-node dialog) and * "Edit Selection in Data Table" to navigate to that row in the table. * - Multiple nodes selected: offers "Edit Selection in Data Table", * "Set property on selected nodes…", and structural transforms instead of * per-node properties (which would be ambiguous for mixed selections). * - Always offers Add Edge, Remove, filter actions, and Ego Radial Layout. */ void MainWindow::slotEditNodeOpenContextMenu() { qCDebug(lcMainWindow, "MW: slotEditNodeOpenContextMenu() for node %i at %i, %i", activeGraph->vertexClicked(), QCursor::pos().x(), QCursor::pos().y()); QMenu *nodeContextMenu = new QMenu(QString::number(activeGraph->vertexClicked()), this); Q_CHECK_PTR(nodeContextMenu); // displays "out of memory" if needed int nodesSelected = activeGraph->getSelectedVerticesCount(); if (nodesSelected == 1) { nodeContextMenu->addAction( tr("## NODE ") + QString::number(activeGraph->vertexClicked()) + " ## "); } else { nodeContextMenu->addAction( tr("## NODE ") + QString::number(activeGraph->vertexClicked()) + " ## " + tr(" (selected nodes: ") + QString::number(nodesSelected) + ")"); } nodeContextMenu->addSeparator(); if (nodesSelected == 1) { // Single node: full per-node properties dialog is appropriate nodeContextMenu->addAction(editNodePropertiesAct); } else { // Multiple nodes: bulk-set shortcut instead of the ambiguous single-node dialog nodeContextMenu->addAction(editNodeSetPropertyForSelectionAct); } // Always offer "Edit in Data Table" so the user can inspect or bulk-edit // the selected row(s) regardless of how many nodes are selected nodeContextMenu->addAction(editNodeEditSelectionInTableAct); nodeContextMenu->addSeparator(); nodeContextMenu->addAction(editEdgeAddAct); nodeContextMenu->addSeparator(); nodeContextMenu->addAction(editNodeRemoveAct); nodeContextMenu->addSeparator(); nodeContextMenu->addAction(filterNodesBySelectionAct); nodeContextMenu->addAction(filterNodesByEgoNetworkAct); nodeContextMenu->addAction(filterNodesRestoreAllAct); nodeContextMenu->addSeparator(); nodeContextMenu->addAction(tr("Ego Radial layout"), this, &MainWindow::slotLayoutEgoRadial); nodeContextMenu->addSeparator(); // QCursor::pos() is good only for menus not related with node coordinates nodeContextMenu->exec(QCursor::pos()); delete nodeContextMenu; } /** * @brief Displays information about the given node on the statusbar. * * Usually called by Graph, after the user clicks on a node. * * @param number * @param p * @param label * @param inDegree * @param outDegree */ void MainWindow::slotEditNodeInfoStatusBar(const int &number, const QPointF &p, const QString &label, const int &inDegree, const int &outDegree) { qCDebug(lcMainWindow) << "Updating node info in status bar..."; rightPanelClickedNodeLCD->setText(QString::number(number)); const bool nodeClicked = (number != 0); rightPanelClickedNodeInDegreeLabel->setVisible(nodeClicked); rightPanelClickedNodeInDegreeLCD->setVisible(nodeClicked); rightPanelClickedNodeOutDegreeLabel->setVisible(nodeClicked); rightPanelClickedNodeOutDegreeLCD->setVisible(nodeClicked); if (nodeClicked) { rightPanelClickedNodeInDegreeLCD->setText(QString::number(inDegree)); rightPanelClickedNodeOutDegreeLCD->setText(QString::number(outDegree)); } if (number != 0) { statusMessage(QString(tr("Position (%1, %2): Node %3, label %4 - " "In-Degree: %5, Out-Degree: %6")) .arg(ceil(p.x())) .arg(ceil(p.y())) .arg(number) .arg((label == "") ? "unset" : label) .arg(inDegree) .arg(outDegree)); } else { statusMessage(tr("Position (%1,%2): Double-click to create a new node.") .arg(p.x()) .arg(p.y())); } } SocNetV-3.7/src/mainwindow/edit/mainwindow_edit_relation.cpp000066400000000000000000000170631524402210300243560ustar00rootroot00000000000000/** * @file mainwindow_edit_relation.cpp * @brief Implements MainWindow relation management (add/rename/clear/switch) and the directed-graph toggle. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "graphicsnode.h" #include "graphicsedge.h" #include "widgets/graphtablewidget.h" #include "forms/dialognodeedit.h" #include "forms/dialogedgeedit.h" #include "forms/dialogbulkedit.h" #include "forms/dialognodefind.h" #include "forms/dialogedgedichotomization.h" #include "forms/dialogfilteredgesbyweight.h" #include "forms/dialogfilternodesbycentrality.h" #include "forms/dialogfilterbyattribute.h" #include "forms/dialogquerybuilder.h" #include /** * @brief Clears the relations combo. */ void MainWindow::slotEditRelationsClear() { qCDebug(lcMainWindow) << "Clearing relations combo..."; editRelationChangeCombo->clear(); } /** * @brief Prompts the user to enter the name of a new relation * * On success, emits signal to Graph to change to the new relation. */ void MainWindow::slotEditRelationAddPrompt() { bool ok; QString newRelationName; int relationsCounter = activeGraph->relations(); qCDebug(lcMainWindow) << "Prompting the user for the new relation name to be added to the relations combo..."; // // Prompt the user for the new relation name // // Check if this is the first time, in order to show a more comprehensive message if (relationsCounter == 1 && activeNodes() == 0) { newRelationName = QInputDialog::getText( this, tr("Add new relation"), tr("Enter a name for a new relation between the actors.\n" "A relation is a collection of ties of a " "specific kind between the network actors.\n" "For instance, enter \"friendship\" if the " "edges of this relation refer to the set of \n" "friendships between pairs of actors."), QLineEdit::Normal, QString(), &ok); } else { newRelationName = QInputDialog::getText( this, tr("Add new relation"), tr("Enter a name for the new relation (or press Cancel):"), QLineEdit::Normal, QString(), &ok); } // // Check which button was pressed // if (ok) { // user pressed OK // Check if new relation name if (!newRelationName.isEmpty()) { // a relation name entered // Check if it is already used by another relation. if (editRelationChangeCombo->findText(newRelationName) > -1) { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error. Relation name is used!"), tr("The relation name is already used."), tr("Please use another relation name that is not already used.")); return; } // Emit signal to Graph to add the relation and change to it bool changeRelation = true; emit signalRelationAddAndChange(newRelationName, changeRelation); } else { // no name entered slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error. No relation name entered!"), tr("You did not type a name for this new relation")); return; } } else { // user pressed Cancel statusMessage(QString(tr("New relation cancelled."))); return; } statusMessage(QString(tr("New relation named %1, added.")) .arg(newRelationName)); } /** * @brief Adds a new relation to the relations combo * * Called from Graph when the network file parser or another Graph method * demands a new relation to be added to the UI combo. * * @param newRelationName */ void MainWindow::slotEditRelationAdd(const QString &newRelationName) { qCDebug(lcMainWindow) << "Adding new relation to relations combo:" << newRelationName; if (!newRelationName.isNull()) { editRelationChangeCombo->addItem(newRelationName); // Enable prev/next widgets, if they are disabled. if (!editRelationPreviousAct->isEnabled() && editRelationChangeCombo->count() > 1) { editRelationPreviousAct->setEnabled(true); editRelationNextAct->setEnabled(true); } statusMessage(QString(tr("Added a new relation named: %1.")) .arg(newRelationName)); } } /** * @brief Changes the editRelations combo box index to relIndex * * If relIndex==RAND_MAX the index is set to the last relation index * * @param relIndex */ void MainWindow::slotEditRelationChange(const int &relIndex) { if (relIndex == RAND_MAX) { qCDebug(lcMainWindow) << "relIndex==RANDMAX. Changing relation combo to last relation..."; editRelationChangeCombo->setCurrentIndex( (editRelationChangeCombo->count() - 1)); } else { qCDebug(lcMainWindow) << "Changing relation combo to index" << relIndex; editRelationChangeCombo->setCurrentIndex(relIndex); } } /** * @brief Prompts the user to enter a new name for the current relation */ void MainWindow::slotEditRelationRename() { bool ok = false; qCDebug(lcMainWindow) << "Request to rename current relation:" << editRelationChangeCombo->currentText() << "Prompting for new name..."; // // Get new name from user // QString newName = QInputDialog::getText( this, tr("Rename current relation"), tr("Enter a new name for this relation."), QLineEdit::Normal, QString(), &ok); // // Check entered name // if (newName.isEmpty() || !ok) { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Not a valid name."), tr("Error"), tr("You did not enter a valid name for this relation.")); return; } // // Change name in combo - this will trigger the signal to activeGraph // editRelationChangeCombo->setCurrentText(newName); } /** * @brief Updates the edge-mode combo and arrows action to reflect the directed * state of the newly selected relation, without calling setDirected/setUndirected. * * Connected to Graph::signalGraphDirectedChanged, which is emitted by relationSet() * whenever the user switches to a different relation. * * @param directed true if the new relation is directed */ void MainWindow::slotEditGraphDirectedChanged(const bool &directed) { qCDebug(lcMainWindow) << "MainWindow::slotEditGraphDirectedChanged - directed:" << directed; // Block the combo's signal so we don't re-enter slotEditEdgeMode and // accidentally call setDirected/setUndirected (which mutate edges). toolBoxEditEdgeModeSelect->blockSignals(true); toolBoxEditEdgeModeSelect->setCurrentIndex(directed ? 0 : 1); toolBoxEditEdgeModeSelect->blockSignals(false); optionsEdgeArrowsAct->setChecked(directed); } SocNetV-3.7/src/mainwindow/edit/mainwindow_edit_selection.cpp000066400000000000000000000302641524402210300245240ustar00rootroot00000000000000/** * @file mainwindow_edit_selection.cpp * @brief Implements MainWindow canvas selection/drag-mode handling, the generic context menu, subgraph extraction, and the Data Table view. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "graphicsnode.h" #include "graphicsedge.h" #include "widgets/graphtablewidget.h" #include "forms/dialognodeedit.h" #include "forms/dialogedgeedit.h" #include "forms/dialogbulkedit.h" #include "forms/dialognodefind.h" #include "forms/dialogedgedichotomization.h" #include "forms/dialogfilteredgesbyweight.h" #include "forms/dialogfilternodesbycentrality.h" #include "forms/dialogfilterbyattribute.h" #include "forms/dialogquerybuilder.h" #include /** * @brief Toggles the interactive/selection mouse drag mode * @param checked */ void MainWindow::slotEditDragModeSelection(bool checked) { qCDebug(lcMainWindow) << "User changed drag mode, checked" << checked; editMouseModeScrollAct->setChecked(false); if (editMouseModeInteractiveAct->isChecked()) { graphicsWidget->setDragMode(QGraphicsView::RubberBandDrag); graphicsWidget->setInteractive(true); } else { graphicsWidget->setDragMode(QGraphicsView::NoDrag); graphicsWidget->setInteractive(false); } } /** * @brief Toggles the non-interactive scrollhand drag mode. * @param checked */ void MainWindow::slotEditDragModeScroll(bool checked) { qCDebug(lcMainWindow) << "User changed scroll mode, checked" << checked; editMouseModeInteractiveAct->setChecked(false); graphicsWidget->setInteractive(false); if (editMouseModeScrollAct->isChecked()) { graphicsWidget->setDragMode(QGraphicsView::ScrollHandDrag); } else { graphicsWidget->setDragMode(QGraphicsView::NoDrag); } } /** * @brief Shows a context menu when the user right-clicks on an empty area of the canvas * (i.e. not directly on a node or edge). * * The menu is selection-aware: * - When exactly one node is selected and no edges: offers Node Properties. * - When multiple nodes are selected: offers Remove + structural transforms (clique, star, …). * - When any nodes or edges are selected (β‰₯ 1 total): offers "Edit Selection in Data Table", * "Set property on selected nodes…", and/or "Set property on selected edges…". * - Always offers Add Node, Add Edge, and the Options sub-menu. * * @param mPos The canvas position where the right-click occurred (unused β€” menu is shown * at cursor position via QCursor::pos()). */ void MainWindow::slotEditOpenContextMenu(const QPointF &mPos) { Q_UNUSED(mPos); QMenu *contextMenu = new QMenu(" Menu", this); Q_CHECK_PTR(contextMenu); // displays "out of memory" if needed int nodesSelected = activeGraph->getSelectedVerticesCount(); int edgesSelected = activeGraph->getSelectedEdgesCount(); int totalSelected = nodesSelected + edgesSelected; contextMenu->addAction("## Selected nodes: " + QString::number(nodesSelected) + " edges: " + QString::number(edgesSelected) + " ## "); contextMenu->addSeparator(); if (nodesSelected > 0) { if (nodesSelected == 1 && edgesSelected == 0) { // Single node: offer per-node properties dialog contextMenu->addAction(editNodePropertiesAct); } contextMenu->addSeparator(); contextMenu->addAction(editNodeRemoveAct); if (nodesSelected > 1) { editNodeRemoveAct->setText(tr("Remove ") + QString::number(nodesSelected) + tr(" nodes")); contextMenu->addSeparator(); contextMenu->addAction(editNodeSelectedToCliqueAct); contextMenu->addAction(editNodeSelectedToStarAct); contextMenu->addAction(editNodeSelectedToCycleAct); contextMenu->addAction(editNodeSelectedToLineAct); } else { editNodeRemoveAct->setText(tr("Remove ") + QString::number(nodesSelected) + tr(" node")); } contextMenu->addSeparator(); } // Table / bulk actions β€” shown whenever at least one item is selected if (totalSelected >= 1) { contextMenu->addAction(editNodeEditSelectionInTableAct); if (nodesSelected > 0) contextMenu->addAction(editNodeSetPropertyForSelectionAct); if (edgesSelected > 0) contextMenu->addAction(editEdgeSetPropertyForSelectionAct); contextMenu->addSeparator(); } contextMenu->addAction(editNodeAddAct); contextMenu->addSeparator(); contextMenu->addAction(editEdgeAddAct); contextMenu->addSeparator(); QMenu *options = new QMenu("Options", this); contextMenu->addMenu(options); options->addAction(openSettingsAct); options->addSeparator(); options->addAction(editNodeSizeAllAct); options->addAction(editNodeShapeAll); options->addAction(editNodeColorAll); options->addAction(optionsNodeNumbersVisibilityAct); options->addAction(optionsNodeLabelsVisibilityAct); options->addSeparator(); options->addAction(editEdgeColorAllAct); options->addSeparator(); options->addAction(changeBackColorAct); options->addAction(backgroundImageAct); // QCursor::pos() is good only for menus not related with node coordinates contextMenu->exec(QCursor::pos()); delete contextMenu; } /** * @brief Syncs the Data Table selection to match the canvas selection. * * Connected to GraphicsWidget::userSelectedItems. Always updates the table's * selection model (even when the dock is hidden) so that resolveNodeTargets / * resolveEdgeTargets return correct rows when the user later opens the dock * and clicks "Set property..." or "Remove attribute...". * * When only nodes are selected, switches the visible tab to Nodes; when only * edges are selected, switches to Edges. */ void MainWindow::slotCacheSelection(const QList &nodes, const QList &edges) { if (!m_tableWidget) return; // Auto-switch tab when dock is visible: // only go to Edges tab when the selection is entirely edges (no nodes). if (m_tableDock && m_tableDock->isVisible()) { if (!edges.isEmpty() && nodes.isEmpty()) m_tableWidget->showEdgesTab(); else m_tableWidget->showNodesTab(); } m_tableWidget->syncNodeSelection(nodes); m_tableWidget->syncEdgeSelection(edges); } /** * @brief Updates the UI (LCDs and Actions) after a change in the user-selected nodes/edges * * @param nodes * @param edges */ void MainWindow::slotEditSelectionChanged(const int &selNodes, const int &selEdges) { qCDebug(lcMainWindow) << "Updating UI for new selection"; rightPanelSelectedNodesLCD->setText(QString::number(selNodes)); rightPanelSelectedEdgesLCD->setText(QString::number(selEdges)); if (selNodes > 1) { editNodeRemoveAct->setText(tr("Remove ") + QString::number(selNodes) + tr(" nodes")); editNodeSelectedToCliqueAct->setEnabled(true); editNodeSelectedToCliqueAct->setText(tr("Create a clique from ") + QString::number(selNodes) + tr(" selected nodes")); editNodeSelectedToStarAct->setEnabled(true); editNodeSelectedToStarAct->setText(tr("Create a star from ") + QString::number(selNodes) + tr(" selected nodes")); editNodeSelectedToCycleAct->setEnabled(true); editNodeSelectedToCycleAct->setText(tr("Create a cycle from ") + QString::number(selNodes) + tr(" selected nodes")); editNodeSelectedToLineAct->setEnabled(true); editNodeSelectedToLineAct->setText(tr("Create a line from ") + QString::number(selNodes) + tr(" selected nodes")); } else { editNodeRemoveAct->setText(tr("Remove Node")); editNodeSelectedToCliqueAct->setText(tr("Create a clique from selected nodes")); editNodeSelectedToCliqueAct->setEnabled(false); editNodeSelectedToStarAct->setText(tr("Create a star from selected nodes")); editNodeSelectedToStarAct->setEnabled(false); editNodeSelectedToCycleAct->setText(tr("Create a cycle from selected nodes")); editNodeSelectedToCycleAct->setEnabled(false); editNodeSelectedToLineAct->setText(tr("Create a line from selected nodes")); editNodeSelectedToLineAct->setEnabled(false); } // Enable selection focus whenever at least 1 node is selected filterNodesBySelectionAct->setEnabled(selNodes >= 1); // Enable ego network focus only when exactly 1 node is selected filterNodesByEgoNetworkAct->setEnabled(selNodes == 1); // Enable subgraph-from-selection whenever at least 1 node is selected editSubgraphExtractFromSelectionAct->setEnabled(selNodes >= 1); // // NOTE: // DO NOT display a message on the status bar on high frequently called functions like this // } /** * @brief Extracts the currently visible nodes and their inter-edges into an * independent Graph object and saves it to a user-chosen file. */ void MainWindow::slotEditSubgraphExtract() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } bool ok = false; const QString defaultName = tr("Subgraph of %1").arg(activeGraph->getName()); const QString subgraphName = QInputDialog::getText( this, tr("Name the subgraph"), tr("Subgraph name:"), QLineEdit::Normal, defaultName, &ok); if (!ok || subgraphName.trimmed().isEmpty()) return; // sub is written on graphThread (inside the dispatched lambda) and read back here in // onComplete (GUI thread) - a std::shared_ptr box makes that value visible across the // thread hop, same pattern the writeCentralityX() slots use for their success flag. const QString name = subgraphName.trimmed(); auto sub = std::make_shared(nullptr); runGraphOperationAsync( [this, name, sub]() { *sub = activeGraph->subgraphExtract(name); }, tr("Extracting subgraph..."), [this, name, sub]() { if (!*sub) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } saveSubgraphToFile(*sub, name); }); } /** * @brief Extracts currently selected nodes and their inter-edges into an * independent Graph object and saves it to a user-chosen file. */ void MainWindow::slotEditSubgraphExtractFromSelection() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } bool ok = false; const QString defaultName = tr("Subgraph of %1").arg(activeGraph->getName()); const QString subgraphName = QInputDialog::getText( this, tr("Name the subgraph"), tr("Subgraph name:"), QLineEdit::Normal, defaultName, &ok); if (!ok || subgraphName.trimmed().isEmpty()) return; const QString name = subgraphName.trimmed(); auto sub = std::make_shared(nullptr); runGraphOperationAsync( [this, name, sub]() { *sub = activeGraph->subgraphExtractFromSelection(name); }, tr("Extracting subgraph..."), [this, name, sub]() { if (!*sub) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } saveSubgraphToFile(*sub, name); }); } /** * @brief Shows or hides the Data Table dock panel (#225). * * When shown, the node and edge models are immediately refreshed from the * current graph state. * * @param checked true β†’ show panel; false β†’ hide panel. */ void MainWindow::slotViewDataTable(bool checked) { if (checked) { m_tableDock->show(); m_tableWidget->refresh(activeGraph); } else { m_tableDock->hide(); } } SocNetV-3.7/src/mainwindow/edit/mainwindow_edit_shared.cpp000066400000000000000000000165141524402210300240070ustar00rootroot00000000000000/** * @file mainwindow_edit_shared.cpp * @brief Implements small MainWindow Edit-menu-adjacent helpers: saving a subgraph selection to file, and active node/edge counts. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "forms/dialogsettings.h" #include /** * @brief Shows a format-selection save dialog for @p sub, writes the file, then * deletes the graph object. * * Offered formats: GraphML (full fidelity), Pajek, Adjacency. * Warns the user when the chosen format cannot preserve custom attributes or * when only the active relation will be written. * * Takes ownership of @p sub β€” the pointer is invalid after this call. */ void MainWindow::saveSubgraphToFile(Graph *sub, const QString &subgraphName) { // Build filesystem-safe default base name. QString sanitized = subgraphName; sanitized.replace(QRegularExpression("[/\\\\:*?\"<>|\\s]+"), "_"); // Default to GraphML (preserves all attributes and relations). const QString defaultPath = QFileInfo(getLastPath()).dir().filePath(sanitized + ".graphml"); const QString filterGraphML = tr("GraphML (*.graphml *.xml)"); const QString filterPajek = tr("Pajek (*.net *.paj)"); const QString filterAdjacency = tr("Adjacency (*.csv *.sm *.adj)"); const QString filterDot = tr("GraphViz DOT (*.dot)"); const QString filterDL = tr("UCINET DL (*.dl *.dat)"); const QString filterEdgeListW = tr("Weighted Edge List (*.wlst)"); const QString filterEdgeListS = tr("Simple Edge List (*.lst)"); const QString allFormats = filterGraphML + ";;" + filterPajek + ";;" + filterAdjacency + ";;" + filterDot + ";;" + filterDL + ";;" + filterEdgeListW + ";;" + filterEdgeListS + ";;All (*)"; QString selectedFilter; QString fn = QFileDialog::getSaveFileName( this, tr("Save Subgraph As…"), defaultPath, allFormats, &selectedFilter); if (fn.isEmpty()) { statusMessage(tr("Saving aborted.")); delete sub; return; } // Map selected filter β†’ FileType and canonical extension. int fileType = FileType::GRAPHML; QString defaultExt = ".graphml"; if (selectedFilter.startsWith("Pajek")) { fileType = FileType::PAJEK; defaultExt = ".net"; } else if (selectedFilter.startsWith("Adjacency")) { fileType = FileType::ADJACENCY; defaultExt = ".csv"; } else if (selectedFilter.startsWith("GraphViz")) { fileType = FileType::GRAPHVIZ; defaultExt = ".dot"; } else if (selectedFilter.startsWith("UCINET")) { fileType = FileType::UCINET; defaultExt = ".dl"; } else if (selectedFilter.startsWith("Weighted Edge")) { fileType = FileType::EDGELIST_WEIGHTED; defaultExt = ".wlst"; } else if (selectedFilter.startsWith("Simple Edge")) { fileType = FileType::EDGELIST_SIMPLE; defaultExt = ".lst"; } // else: GraphML or "All (*)" β†’ keep GRAPHML if (QFileInfo(fn).suffix().isEmpty()) fn.append(defaultExt); setLastPath(fn); // --- Fidelity warnings ------------------------------------------------- const bool hasCustomAttrs = !sub->graphHasVertexCustomAttributes().isEmpty() || !sub->graphHasEdgeCustomAttributes().isEmpty(); const bool multiRelation = sub->relations() > 1; // GraphML and DOT both preserve custom attributes; all other formats lose them. const bool loseCustomAttrs = fileType != FileType::GRAPHML && fileType != FileType::GRAPHVIZ && hasCustomAttrs; if (loseCustomAttrs) { const int answer = slotHelpMessageToUser( USER_MSG_QUESTION, tr("Custom attributes will not be saved"), tr("Format limitation: custom node/edge attributes"), tr("The chosen format does not support custom node or edge attributes. " "Those attributes will be lost in the exported file.\n\n" "Use GraphML to preserve all attributes.\n\n" "Continue with the chosen format?")); if (answer != QMessageBox::Yes) { statusMessage(tr("Saving aborted.")); delete sub; return; } } // Formats that export only the active relation (DL and Pajek handle multi-relation natively). const bool activeRelOnly = fileType == FileType::ADJACENCY || fileType == FileType::GRAPHVIZ || fileType == FileType::EDGELIST_WEIGHTED || fileType == FileType::EDGELIST_SIMPLE; if (activeRelOnly && multiRelation) { QString fmtName; switch (fileType) { case FileType::ADJACENCY: fmtName = tr("Adjacency"); break; case FileType::GRAPHVIZ: fmtName = tr("GraphViz DOT"); break; case FileType::EDGELIST_WEIGHTED: fmtName = tr("Weighted Edge List"); break; case FileType::EDGELIST_SIMPLE: fmtName = tr("Simple Edge List"); break; default: break; } slotHelpMessageToUser( USER_MSG_INFO, tr("Only active relation will be exported"), tr("Multi-relation graph β€” %1 format").arg(fmtName), tr("The %1 format supports a single relation. " "Only the currently active relation \"%2\" will be written.") .arg(fmtName) .arg(sub->relationCurrentName())); } // --- Format-specific options ------------------------------------------- bool saveEdgeWeights = true; if (fileType == FileType::ADJACENCY && sub->isWeighted()) { const int answer = slotHelpMessageToUser( USER_MSG_QUESTION, tr("Save edge weights?"), tr("Weighted graph"), tr("This graph has weighted edges. " "Include edge weights in the adjacency file?\n\n" "Select Yes to save weights, No to write 0/1 values only.")); saveEdgeWeights = (answer == QMessageBox::Yes); } // --- Write ------------------------------------------------------------- sub->saveToFile(fn, fileType, saveEdgeWeights); const int n = sub->vertices(); const int e = sub->edgesEnabled(); delete sub; statusMessage(tr("Subgraph saved: %1 nodes, %2 edges β†’ %3") .arg(n) .arg(e) .arg(QFileInfo(fn).fileName())); } /** * Returns the amount of enabled/active edges on the scene. */ int MainWindow::activeEdges() { qCDebug(lcMainWindow) << "MW::activeEdges()"; return activeGraph->edgesEnabled(); } /** * Returns the number of active nodes on the scene. */ int MainWindow::activeNodes() { return activeGraph->vertices(); } SocNetV-3.7/src/mainwindow/help/000077500000000000000000000000001524402210300165705ustar00rootroot00000000000000SocNetV-3.7/src/mainwindow/help/mainwindow_help.cpp000066400000000000000000000516661524402210300224760ustar00rootroot00000000000000/** * @file mainwindow_help.cpp * @brief Implements MainWindow Help menu and the shared user-message dialog helpers (slotHelpMessageToUser* - despite the name, used throughout the app, not just by Help actions). * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "forms/dialogsettings.h" #include "forms/dialogsysteminfo.h" #include /** * @brief Compares two version strings component-by-component. * * Handles versions with 1, 2, or 3 components (e.g. "3.3", "3.3.1", "3.10"). * Returns: * -1 if a < b * 0 if a == b * +1 if a > b */ static int compareVersions(const QString &a, const QString &b) { const QStringList aParts = a.split('.'); const QStringList bParts = b.split('.'); const int len = qMax(aParts.size(), bParts.size()); for (int i = 0; i < len; ++i) { const int av = (i < aParts.size()) ? aParts[i].toInt() : 0; const int bv = (i < bParts.size()) ? bParts[i].toInt() : 0; if (av < bv) return -1; if (av > bv) return +1; } return 0; } /** * @brief Helper function to display a popup with useful info * @param text */ void MainWindow::slotHelpMessageToUserInfo(const QString text) { slotHelpMessageToUser(USER_MSG_INFO, tr("Useful information"), text); } /** * @brief Helper function to display a popup with an error message * @param text */ void MainWindow::slotHelpMessageToUserError(const QString text) { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error"), text); } /** * @brief Displays a popup with the given text/info and a status message * * @param type * @param statusMsg * @param text * @param info * @param buttons * @param defBtn * @param btn1 * @param btn2 * @return */ int MainWindow::slotHelpMessageToUser(const int type, const QString statusMsg, const QString text, const QString info, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defBtn, const QString btn1, const QString btn2, const QString btn3) { int response = 0; QMessageBox msgBox; msgBox.setMinimumWidth(400); QPushButton *pbtn1, *pbtn2; switch (type) { case USER_MSG_INFO: if (!statusMsg.isNull()) statusMessage(statusMsg); msgBox.setWindowTitle("Information"); msgBox.setText(text); if (!info.isNull()) msgBox.setInformativeText(info); msgBox.setIcon(QMessageBox::Information); if (buttons == QMessageBox::NoButton) { msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setDefaultButton(QMessageBox::Ok); } else { msgBox.setStandardButtons(buttons); msgBox.setDefaultButton(defBtn); } msgBox.setDefaultButton(defBtn); response = msgBox.exec(); break; case USER_MSG_CRITICAL: if (!statusMsg.isNull()) statusMessage(statusMsg); msgBox.setWindowTitle("Error"); msgBox.setText(text); if (!info.isNull()) msgBox.setInformativeText(info); // msgBox.setTextFormat(Qt::RichText); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setDefaultButton(QMessageBox::Ok); response = msgBox.exec(); break; case USER_MSG_CRITICAL_NO_NETWORK: statusMessage(tr("Nothing to do! Load or create a social network first")); msgBox.setWindowTitle("Error"); msgBox.setText( tr("No network! \n" "Load social network data or create a new social network first. \n")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setDefaultButton(QMessageBox::Ok); response = msgBox.exec(); break; case USER_MSG_CRITICAL_NO_EDGES: statusMessage(tr("Nothing to do! Load social network data or create edges first")); msgBox.setWindowTitle("Error"); msgBox.setText( tr("No edges! \n" "Load social network data or create some edges first. \n")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setDefaultButton(QMessageBox::Ok); response = msgBox.exec(); break; case USER_MSG_QUESTION: if (!statusMsg.isNull()) statusMessage(statusMsg); msgBox.setWindowTitle("Question"); msgBox.setText(text); if (!info.isNull()) msgBox.setInformativeText(info); if (buttons == QMessageBox::NoButton) { msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Yes); } else { msgBox.setStandardButtons(buttons); msgBox.setDefaultButton(defBtn); } msgBox.setIcon(QMessageBox::Question); response = msgBox.exec(); break; case USER_MSG_QUESTION_CUSTOM: // a custom question with just two/three buttons if (!statusMsg.isNull()) statusMessage(statusMsg); msgBox.setWindowTitle("Question"); msgBox.setText(text); if (!info.isNull()) msgBox.setInformativeText(info); pbtn1 = msgBox.addButton(btn1, QMessageBox::ActionRole); pbtn2 = msgBox.addButton(btn2, QMessageBox::ActionRole); if (!btn3.isNull() && !btn3.isEmpty()) { QPushButton *pbtn3 = msgBox.addButton(btn3, QMessageBox::ActionRole); msgBox.setIcon(QMessageBox::Question); response = msgBox.exec(); if (msgBox.clickedButton() == pbtn1) response = 1; else if (msgBox.clickedButton() == pbtn2) response = 2; else if (msgBox.clickedButton() == pbtn3) response = 3; } else { msgBox.setIcon(QMessageBox::Question); response = msgBox.exec(); if (msgBox.clickedButton() == pbtn1) response = 1; else if (msgBox.clickedButton() == pbtn2) response = 2; } break; default: // just for sanity if (!statusMsg.isNull()) statusMessage(statusMsg); msgBox.setText(text); msgBox.setIcon(QMessageBox::Information); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setDefaultButton(QMessageBox::Ok); response = msgBox.exec(); break; } return response; } /** * Displays a random tip */ void MainWindow::slotHelpTips() { int randomTip = rand() % (tips.size()); // Pick a tip. QMessageBox::about(this, tr("Tip Of The Day"), tips[randomTip]); } /** Creates our tips. */ void MainWindow::slotHelpCreateTips() { tips += tr("To create a new node: \n" "- double-click somewhere on the canvas \n" "- or press the keyboard shortcut CTRL+. (dot)\n" "- or press the Add Node button on the left panel"); tips += tr("SocNetV can work with either undirected or directed data. " "When you start SocNetV for the first time, the application uses " "the 'directed data' mode; every edge you create is directed. " "To enter the 'undirected data' mode, press CTRL+E+U or enable the " "menu option Edit->Edges->Undirected Edges "); tips += tr("If your screen is small, and the canvas appears even smaller " "hide the Control and/or Statistics panel. Then the canvas " "will expand to the whole application window. " "Open the Settings/Preferences dialog->Window options and " "disable the two panels."); tips += tr("A scale-free network is a network whose degree distribution follows a power law. " "SocNetV generates random scale-free networks according to the " "BarabΓ‘si–Albert (BA) model using a preferential attachment mechanism."); tips += tr("To delete a node permanently: \n" "- right-click on it and select Remove Node \n" "- or press CTRL+ALT+. and enter its number\n" "- or press the Remove Node button on the Control Panel"); tips += tr("To rotate the network: \n" " - drag the bottom slider to left or right \n" " - or click the buttons on the corners of the bottom slider\n" " - or press CTRL and the left or right arrow."); tips += tr("To create a new edge between nodes A and B: \n" "- double-click on node A, then double-click on node B.\n" "- or middle-click on node A, and again on node B.\n" "- or right-click on the node, then select Add Edge from the popup.\n" "- or press the keyboard shortcut CTRL+/ \n" "- or press the Add Edge button on the Control Panel"); tips += tr("Add a label to an edge by right-clicking on it " "and selecting Change Label."); tips += tr("You can change the background color of the canvas. " "Do it from the menu Options > View or " "permanently save this setting in Settings/Preferences."); tips += tr("Default node colors, shapes and sizes can be changed. " "Open the Settings/Preferences dialog and use the " "options on the Node tab."); tips += tr("The Statistics Panel shows network-level information (i.e. density) " "as well as info about any node you clicked on (inDegrees, " "outDegrees, clustering)."); tips += tr("You can move any node by left-clicking and dragging it with your mouse. " "If you want you can move multiple nodes at once. Left-click on empty space " "on the canvas and drag to create a rectangle selection around them. " "Then left-click on one of the selected nodes and drag it."); tips += tr("To save the node positions in a network, you need to save your data " "in a format which supports node positions, suchs as GraphML or Pajek."); tips += tr("Embed visualization models on the network from the options in " "the Layout menu or the select boxes on the left Control Panel. "); tips += tr("To change the label of a node right-click on it, and click " "Selected Node Properties from the popup menu."); tips += tr("All basic operations of SocNetV are available from the left Control panel " "or by right-clicking on a Node or an Edge or on canvas empty space."); tips += tr("Node info (number, position, degree, etc) is displayed on the Status bar, " "when you left-click on it."); tips += tr("Edge information is displayed on the Status bar, when you left-click on it."); tips += tr("Save your work often, especially when working with large data sets. " "SocNetV alogorithms are faster when working with saved data. "); tips += tr("You can change the precision of real numbers in reports. " "Go to Settings > General and change it under Reports > Real number precision. "); tips += tr("The Closeness Centrality (CC) of a node v, is the inverse sum of " "the shortest distances between v and every other node. CC is " "interpreted as the ability to access information through the " "\'grapevine\' of network members. Nodes with high closeness " "centrality are those who can reach many other nodes in few steps. " "This index can be calculated in both graphs and digraphs. " "It can also be calculated in weighted graphs although the weight of " "each edge (v,u) in E is always considered to be 1. "); tips += tr("The Information Centrality (IC) index counts all paths between " "nodes weighted by strength of tie and distance. " "This centrality measure developed by Stephenson and Zelen (1989) " "focuses on how information might flow through many different paths. " "This index should be calculated only for undirected graphs. " "Note: To compute this index, SocNetV drops all isolated nodes."); } /** * @brief * Opens the system web browser to load the online Manual */ void MainWindow::slotHelp() { statusMessage(tr("Opening the SocNetV Manual in your default web browser....")); QDesktopServices::openUrl(QUrl("https://socnetv.org/manual/?utm_source=application&utm_medium=banner&utm_campaign=socnetv" + VERSION)); } /** * @brief On user demand, makes a network request to SocNetV website to * download the latest version text file. */ void MainWindow::slotHelpCheckUpdateDialog() { QString url = "https://socnetv.org/latestversion.txt"; qCDebug(lcMainWindow) << "Will make a 'check for updates' request to url:" << url; slotNetworkManagerRequest(QUrl(url), NetworkRequestType::CheckUpdate); } /** * @brief Parses the reply from the network request we do in slotHelpCheckUpdateDialog */ void MainWindow::slotHelpCheckUpdateParse() { qCDebug(lcMainWindow) << "MW::slotHelpCheckUpdateParse()"; QNetworkReply *reply = qobject_cast(sender()); QByteArray ba = reply->readAll(); reply->deleteLater(); QString replyContent = QString(ba).simplified(); if (replyContent.isEmpty()) { slotHelpMessageToUserError( "Empty response from https://socnetv.org. " "Could not get the latest version number. Please try again later..."); return; } // Validate: remote version must look like digits and dots only static const QRegularExpression versionRx(R"(^\d+(\.\d+){0,2}$)"); if (!versionRx.match(replyContent).hasMatch()) { slotHelpMessageToUserError( "Could not understand the version number I got from https://socnetv.org. " "Please, try again later..."); return; } const QString remoteVersion = replyContent; // Strip pre-release suffixes from local version (beta, rc, dev) QString localVersion = VERSION; static const QRegularExpression preReleaseSuffixRx(R"([-.]?(beta|rc|dev)\d*)", QRegularExpression::CaseInsensitiveOption); localVersion.remove(preReleaseSuffixRx); qCDebug(lcMainWindow) << "MW::slotHelpCheckUpdateParse() - localVersion:" << localVersion << "remoteVersion:" << remoteVersion; const int cmp = compareVersions(remoteVersion, localVersion); if (cmp > 0) { // Remote is newer switch (slotHelpMessageToUser( USER_MSG_QUESTION, tr("Newer SocNetV version available!"), tr("

Your version: ") + VERSION + "

" + tr("

Remote version: ") + remoteVersion + "

", tr("

There is a newer SocNetV version available!

" "

Do you want to download the latest version now?

" "

Press Yes, and I will open your default web browser for you " "to download the latest SocNetV package...

"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)) { case QMessageBox::Yes: statusMessage(tr("Opening SocNetV website in your default web browser....")); QDesktopServices::openUrl(QUrl( "https://socnetv.org/downloads" "?utm_source=application&utm_medium=banner&utm_campaign=socnetv" + VERSION)); break; default: break; } } else { // Up to date (cmp == 0) or somehow ahead (cmp < 0, e.g. on a dev build) slotHelpMessageToUserInfo( tr("

Your version: ") + VERSION + "

" + tr("

Remote version: ") + remoteVersion + "

" + tr("

You are running the latest and greatest version of SocNetV.
" "Nothing to do!

")); } } /** * @brief Shows a dialog with system information for bug reporting purposes */ void MainWindow::slotHelpSystemInfo() { qCDebug(lcMainWindow) << "MW: slotHelpSystemInfo()"; m_systemInfoDialog = new DialogSystemInfo(this); m_systemInfoDialog->exec(); } /** Displays the following message!! */ void MainWindow::slotHelpAbout() { int randomCookie = rand() % fortuneCookie.size(); QMessageBox::about( this, tr("About SocNetV"), tr("Social Network Visualizer (SocNetV)") + tr("

Version: ") + VERSION + "

" + tr("

Website: https://socnetv.org

") + tr("

(C) 2005-2026 by Dimitris B. Kalamaras

") + tr("

Have questions? Contact us!

") + tr("

Fortune cookie:
\"") + fortuneCookie[randomCookie] + "\"" + tr("

License:

") + tr("

This program is free software; you can redistribute it " "and/or modify it under the terms of the GNU General " "Public License as published by the Free Software Foundation; " "either version 3 of the License, or (at your option) " "any later version.

") + tr("

This program is distributed in the hope that it " "will be useful, but WITHOUT ANY WARRANTY; " "without even the implied warranty of MERCHANTABILITY " "or FITNESS FOR A PARTICULAR PURPOSE. " "See the GNU General Public License for more details.

") + tr("

You should have received a copy of the GNU " "General Public License along with this program; " "If not, see http://www.gnu.org/licenses/

")); } /** Creates the fortune cookies displayed on the above message. */ void MainWindow::createFortuneCookies() { fortuneCookie += "sic itur ad astra / sic transit gloria mundi ?
" "--Unknown"; fortuneCookie += "The truth is not my business. I am a statistician... I don’t like words like \"correct\" and \"truth\". " "Statistics is about measuring against convention.
" "Walter Radermacher, Eurostat director, interview to NY Times, 2012."; fortuneCookie += "Losers of yesterday, the winners of tomorrow...
" "--B.Brecht"; fortuneCookie += "I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. " "I watched C-beams glitter in the dark near the Tannhauser gate. " "All those moments will be lost in time... like tears in rain... Time to die.
" "Replicant Roy Batty, Blade Runner (1982)"; fortuneCookie += "Patriotism is the virtue of the wicked...
" "--O. Wilde"; fortuneCookie += "No tengo nunca mas, no tengo siempre. En la arena
" "la victoria dejo sus piers perdidos.
" "Soy un pobre hombre dispuesto a amar a sus semejantes.
" "No se quien eres. Te amo. No doy, no vendo espinas.
" "--Pablo Neruda"; fortuneCookie += "Man must not check reason by tradition, but contrawise, " "must check tradition by reason.
--Leo Tolstoy"; fortuneCookie += "Only after the last tree has been cut down,
" "only after the last river has been poisoned,
" "only after the last fish has been caught,
" "only then will you realize that money cannot be eaten.
" "--The Cree People"; fortuneCookie += "Stat rosa pristina nomine, nomina nuda tenemus
" " --Unknown"; fortuneCookie += "Jupiter and Saturn, Oberon, Miranda
" "And Titania, Neptune, Titan.
" "Stars can frighten.
Syd Barrett"; fortuneCookie += "In theory, there is no difference between theory and practice.
" "In practice, there is.
--Yogi Berra"; } /** Displays a short message about the Qt Toolbox. */ void MainWindow::slotAboutQt() { QMessageBox::aboutQt(this, "About Qt - SocNetV"); } SocNetV-3.7/src/mainwindow/init/000077500000000000000000000000001524402210300166035ustar00rootroot00000000000000SocNetV-3.7/src/mainwindow/init/mainwindow_init_actions.cpp000066400000000000000000005332621524402210300242410ustar00rootroot00000000000000/** * @file mainwindow_init_actions.cpp * @brief Implements MainWindow::initActions() - constructs every QAction in the application. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/filterbarwidget.h" #include "widgets/graphtablewidget.h" #include #include /** * @brief Initializes all QActions of the application * * Take a breath, the listing below is HUGE. * */ void MainWindow::initActions() { qCDebug(lcMainWindow) << "initializing actions..."; /** Network menu actions */ networkNewAct = new QAction(QIcon(":/images/new_folder_48px.svg"), tr("&New"), this); networkNewAct->setShortcut(Qt::CTRL | Qt::Key_N); networkNewAct->setStatusTip(tr("Create a new network")); networkNewAct->setToolTip(tr("New network")); networkNewAct->setWhatsThis(tr("New\n\n" "Creates a new social network. " "First, checks if current network needs to be saved.")); connect(networkNewAct, SIGNAL(triggered()), this, SLOT(slotNetworkNew())); networkOpenAct = new QAction(QIcon(":/images/open_48px.svg"), tr("&Open"), this); networkOpenAct->setShortcut(Qt::CTRL | Qt::Key_O); networkOpenAct->setToolTip(tr("Open network")); networkOpenAct->setStatusTip(tr("Open a GraphML formatted file of social network data.")); networkOpenAct->setWhatsThis(tr("Open\n\n" "Opens a file of a social network in GraphML format")); connect(networkOpenAct, SIGNAL(triggered()), this, SLOT(slotNetworkFileChoose())); for (int i = 0; i < MaxRecentFiles; ++i) { recentFileActs[i] = new QAction(this); recentFileActs[i]->setVisible(false); connect(recentFileActs[i], SIGNAL(triggered()), this, SLOT(slotNetworkFileLoadRecent())); } networkImportGMLAct = new QAction(QIcon(":/images/open_48px.svg"), tr("&GML"), this); networkImportGMLAct->setStatusTip(tr("Import GML-formatted file")); networkImportGMLAct->setWhatsThis(tr("Import GML\n\n" "Imports a social network from a GML-formatted file")); connect(networkImportGMLAct, SIGNAL(triggered()), this, SLOT(slotNetworkImportGML())); networkImportPajekAct = new QAction(QIcon(":/images/open_48px.svg"), tr("&Pajek"), this); networkImportPajekAct->setStatusTip(tr("Import Pajek-formatted file")); networkImportPajekAct->setWhatsThis(tr("Import Pajek \n\n" "Imports a social network from a Pajek-formatted file")); connect(networkImportPajekAct, SIGNAL(triggered()), this, SLOT(slotNetworkImportPajek())); networkImportAdjAct = new QAction(QIcon(":/images/open_48px.svg"), tr("&Adjacency Matrix"), this); networkImportAdjAct->setStatusTip(tr("Import Adjacency matrix")); networkImportAdjAct->setWhatsThis(tr("Import Sociomatrix \n\n" "Imports a social network from an Adjacency matrix-formatted file")); connect(networkImportAdjAct, SIGNAL(triggered()), this, SLOT(slotNetworkImportAdjacency())); networkImportGraphvizAct = new QAction(QIcon(":/images/open_48px.svg"), tr("Graph&Viz (.dot)"), this); networkImportGraphvizAct->setStatusTip(tr("Import dot file")); networkImportGraphvizAct->setWhatsThis(tr("Import GraphViz \n\n" "Imports a social network from a GraphViz formatted file")); connect(networkImportGraphvizAct, SIGNAL(triggered()), this, SLOT(slotNetworkImportGraphviz())); networkImportUcinetAct = new QAction(QIcon(":/images/open_48px.svg"), tr("&UCINET (.dl)..."), this); networkImportUcinetAct->setStatusTip(tr("ImportDL-formatted file (UCINET)")); networkImportUcinetAct->setWhatsThis(tr("Import UCINET\n\n" "Imports social network data from a DL-formatted file")); connect(networkImportUcinetAct, SIGNAL(triggered()), this, SLOT(slotNetworkImportUcinet())); networkImportListAct = new QAction(QIcon(":/images/open_48px.svg"), tr("&Edge list"), this); networkImportListAct->setStatusTip(tr("Import an edge list file. ")); networkImportListAct->setWhatsThis( tr("Import edge list\n\n" "Import a network from an edgelist file. " "SocNetV supports EdgeList files with edge weights " "as well as simple EdgeList files where the edges are non-value (see manual)")); connect(networkImportListAct, SIGNAL(triggered()), this, SLOT(slotNetworkImportEdgeList())); networkImportTwoModeSM = new QAction(QIcon(":/images/open_48px.svg"), tr("&Two Mode Sociomatrix"), this); networkImportTwoModeSM->setStatusTip(tr("Import two-mode sociomatrix (affiliation network) file")); networkImportTwoModeSM->setWhatsThis(tr("Import Two-Mode Sociomatrix \n\n" "Imports a two-mode network from a sociomatrix file. " "Two-mode networks are described by affiliation " "network matrices, where A(i,j) codes the " "events/organizations each actor is affiliated with.")); connect(networkImportTwoModeSM, SIGNAL(triggered()), this, SLOT(slotNetworkImportTwoModeSM())); networkSaveAct = new QAction(QIcon(":/images/file_download_48px.svg"), tr("&Save"), this); networkSaveAct->setShortcut(QKeySequence::Save); networkSaveAct->setStatusTip(tr("Save social network to a file")); networkSaveAct->setWhatsThis(tr("Save.\n\n" "Saves the social network to file")); connect(networkSaveAct, SIGNAL(triggered()), this, SLOT(slotNetworkSave())); networkSaveAsAct = new QAction(QIcon(":/images/file_download_48px.svg"), tr("Save As..."), this); networkSaveAsAct->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S); networkSaveAsAct->setStatusTip(tr("Save network under a new filename")); networkSaveAsAct->setWhatsThis(tr("Save As\n\n" "Saves the social network under a new filename")); connect(networkSaveAsAct, SIGNAL(triggered()), this, SLOT(slotNetworkSaveAs())); networkExportImageAct = new QAction(QIcon(":/images/export_photo_48px.svg"), tr("Export to I&mage..."), this); networkExportImageAct->setStatusTip(tr("Export the visible part of the network to image")); networkExportImageAct->setWhatsThis(tr("Export to Image\n\n" "Exports the visible part of the current social network to an image")); connect(networkExportImageAct, SIGNAL(triggered()), this, SLOT(slotNetworkExportImageDialog())); networkExportPDFAct = new QAction(QIcon(":/images/export_pdf_48px.svg"), tr("E&xport to PDF..."), this); networkExportPDFAct->setStatusTip(tr("Export the visible part of the network to a PDF file")); networkExportPDFAct->setWhatsThis(tr("Export to PDF\n\n" "Exports the visible part of the current social network to a PDF document.")); connect(networkExportPDFAct, SIGNAL(triggered()), this, SLOT(slotNetworkExportPDFDialog())); networkExportSMAct = new QAction(QIcon(":/images/file_download_48px.svg"), tr("&Adjacency Matrix"), this); networkExportSMAct->setStatusTip(tr("Export social network to an adjacency/sociomatrix file")); networkExportSMAct->setWhatsThis(tr("Export network to Adjacency format\n\n" "Exports the social network to an " "adjacency matrix-formatted file")); connect(networkExportSMAct, SIGNAL(triggered()), this, SLOT(slotNetworkExportSM())); networkExportPajek = new QAction(QIcon(":/images/file_download_48px.svg"), tr("&Pajek"), this); networkExportPajek->setStatusTip(tr("Export social network to a Pajek-formatted file")); networkExportPajek->setWhatsThis(tr("Export Pajek \n\n" "Exports the social network to a Pajek-formatted file")); connect(networkExportPajek, SIGNAL(triggered()), this, SLOT(slotNetworkExportPajek())); networkExportListAct = new QAction(QIcon(":/images/file_download_48px.svg"), tr("&List"), this); networkExportListAct->setStatusTip(tr("Export to List-formatted file. ")); networkExportListAct->setWhatsThis(tr("Export List\n\n" "Exports the network to a List-formatted file")); connect(networkExportListAct, SIGNAL(triggered()), this, SLOT(slotNetworkExportList())); networkExportDLAct = new QAction(QIcon(":/images/file_download_48px.svg"), tr("&DL..."), this); networkExportDLAct->setStatusTip(tr("Export network to UCINET-formatted file")); networkExportDLAct->setWhatsThis(tr("Export UCINET\n\n" "Exports the active network to a DL-formatted")); connect(networkExportDLAct, SIGNAL(triggered()), this, SLOT(slotNetworkExportDL())); networkExportGWAct = new QAction(QIcon(":/images/file_download_48px.svg"), tr("&GW..."), this); networkExportGWAct->setStatusTip(tr("Export to GW-formatted file")); networkExportGWAct->setWhatsThis(tr("Export\n\n" "Exports the active network to a GW formatted file")); connect(networkExportGWAct, SIGNAL(triggered()), this, SLOT(slotNetworkExportGW())); networkExportDotAct = new QAction(QIcon(":/images/file_download_48px.svg"), tr("&GraphViz DOT..."), this); networkExportDotAct->setStatusTip(tr("Export the active relation to a GraphViz DOT (.dot) file")); networkExportDotAct->setWhatsThis(tr("Export GraphViz DOT\n\n" "Exports the currently active relation to a GraphViz DOT file. " "Node labels, colors, shapes, and custom attributes are preserved. " "Only the active relation is exported.")); connect(networkExportDotAct, &QAction::triggered, this, &MainWindow::slotNetworkExportDot); networkExportNodesCSVAct = new QAction(QIcon(":/images/file_download_48px.svg"), tr("Nodes as &CSV..."), this); networkExportNodesCSVAct->setStatusTip(tr("Export all node data to a CSV file")); connect(networkExportNodesCSVAct, &QAction::triggered, this, &MainWindow::slotNetworkExportNodesCSV); networkExportEdgesCSVAct = new QAction(QIcon(":/images/file_download_48px.svg"), tr("Edges as C&SV..."), this); networkExportEdgesCSVAct->setStatusTip(tr("Export all edge data to a CSV file")); connect(networkExportEdgesCSVAct, &QAction::triggered, this, &MainWindow::slotNetworkExportEdgesCSV); networkExportNodesJSONAct = new QAction(QIcon(":/images/file_download_48px.svg"), tr("Nodes as &JSON..."), this); networkExportNodesJSONAct->setStatusTip(tr("Export all node data to a JSON file")); connect(networkExportNodesJSONAct, &QAction::triggered, this, &MainWindow::slotNetworkExportNodesJSON); networkExportEdgesJSONAct = new QAction(QIcon(":/images/file_download_48px.svg"), tr("Edges as J&SON..."), this); networkExportEdgesJSONAct->setStatusTip(tr("Export all edge data to a JSON file")); connect(networkExportEdgesJSONAct, &QAction::triggered, this, &MainWindow::slotNetworkExportEdgesJSON); networkCloseAct = new QAction(QIcon(":/images/close_24px.svg"), tr("&Close"), this); networkCloseAct->setShortcut(QKeySequence::Close); networkCloseAct->setStatusTip(tr("Close the actual network")); networkCloseAct->setWhatsThis(tr("Close \n\nCloses the actual network")); connect(networkCloseAct, SIGNAL(triggered()), this, SLOT(slotNetworkClose())); networkPrintAct = new QAction(QIcon(":/images/print_48px.svg"), tr("&Print"), this); networkPrintAct->setShortcut(QKeySequence::Print); networkPrintAct->setStatusTip(tr("Send the currrent social network to the printer")); networkPrintAct->setWhatsThis(tr("Print \n\n" "Sends whatever is viewable on " "the canvas to your printer. \n" "To print the whole social network, " "you might want to zoom-out.")); connect(networkPrintAct, SIGNAL(triggered()), this, SLOT(slotNetworkPrint())); networkQuitAct = new QAction(QIcon(":/images/exit_24px.svg"), tr("E&xit"), this); networkQuitAct->setShortcut(QKeySequence::Quit); networkQuitAct->setStatusTip(tr("Quit SocNetV. Are you sure?")); networkQuitAct->setWhatsThis(tr("Exit\n\n" "Quits the application")); connect(networkQuitAct, SIGNAL(triggered()), this, SLOT(close())); openTextEditorAct = new QAction(QIcon(":/images/text_edit_48px.svg"), tr("Open &Text Editor"), this); openTextEditorAct->setShortcut(Qt::SHIFT | Qt::Key_F5); openTextEditorAct->setStatusTip(tr("Open a text editor " "to take notes, copy/paste network data, etc")); openTextEditorAct->setWhatsThis( tr("

Text Editor

" "

Opens a simple text editor where you can " "copy paste network data, of any supported format, " "and save to a file. Then you can import that file to SocNetV.

")); connect(openTextEditorAct, SIGNAL(triggered()), this, SLOT(slotNetworkTextEditor())); networkViewFileAct = new QAction(QIcon(":/images/code_48px.svg"), tr("&View Loaded File"), this); networkViewFileAct->setShortcut(Qt::Key_F5); networkViewFileAct->setStatusTip(tr("Display the loaded social network file.")); networkViewFileAct->setWhatsThis(tr("View Loaded File\n\n" "Displays the loaded social network file ")); connect(networkViewFileAct, SIGNAL(triggered()), this, SLOT(slotNetworkFileView())); networkViewSociomatrixAct = new QAction(QIcon(":/images/sociomatrix_48px.svg"), tr("View &Adjacency Matrix"), this); networkViewSociomatrixAct->setShortcut(Qt::Key_F6); networkViewSociomatrixAct->setStatusTip(tr("Display the adjacency matrix of the network.")); networkViewSociomatrixAct->setWhatsThis( tr("

View Adjacency Matrix

" "

Displays the adjacency matrix of the active network.

" "

The adjacency matrix of a social network is a matrix " "where each element a(i,j) is equal to the weight " "of the arc from actor (node) i to actor j. " "

If the actors are not connected, then a(i,j)=0.

")); connect(networkViewSociomatrixAct, SIGNAL(triggered()), this, SLOT(slotNetworkViewSociomatrix())); networkViewSociomatrixPlotAct = new QAction(QIcon(":/images/adjacencyplot.png"), tr("P&lot Adjacency Matrix (text)"), this); networkViewSociomatrixPlotAct->setShortcut(Qt::SHIFT | Qt::Key_F6); networkViewSociomatrixPlotAct->setStatusTip( tr("Plots the adjacency matrix in a text file using unicode characters.")); networkViewSociomatrixPlotAct->setWhatsThis( tr("

Plot Adjacency Matrix (text)

" "

Plots the adjacency matrix in a text file using " "unicode characters.

" "

In every element (i,j) of the \"image\", " "a black square means actors i and j are connected" "whereas a white square means they are disconnected.

")); connect(networkViewSociomatrixPlotAct, SIGNAL(triggered()), this, SLOT(slotNetworkViewSociomatrixPlotText())); networkDataSetSelectAct = new QAction(QIcon(":/images/science_48px.svg"), tr("Create From &Known Data Sets"), this); networkDataSetSelectAct->setShortcut(Qt::Key_F7); networkDataSetSelectAct->setStatusTip( tr("Load one of the \'famous\' social network data sets included in SocNetV.")); networkDataSetSelectAct->setWhatsThis( tr("

Famous Data Sets

" "

SocNetV includes a number of known " "(also called famous) data sets in Social Network Analysis, " "such as Krackhardt's high-tech managers, etc. " "Click this menu item or press F7 to load a data set.

")); connect(networkDataSetSelectAct, SIGNAL(triggered()), this, SLOT(slotNetworkDataSetSelect())); networkRandomScaleFreeAct = new QAction( QIcon(":/images/scalefree.png"), tr("Scale-free"), this); networkRandomScaleFreeAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_S)); networkRandomScaleFreeAct->setStatusTip( tr("Create a random network with a power-law degree distribution.")); networkRandomScaleFreeAct->setWhatsThis( tr("

Scale-free (power-law)

" "

A scale-free network is a network whose degree distribution " "follows a power law." " SocNetV generates random scale-free networks according to the " " BarabΓ‘si–Albert (BA) model using a preferential attachment mechanism.

")); connect(networkRandomScaleFreeAct, SIGNAL(triggered()), this, SLOT(slotNetworkRandomScaleFreeDialog())); networkRandomSmallWorldAct = new QAction(QIcon(":/images/sw.png"), tr("Small World"), this); networkRandomSmallWorldAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_M)); networkRandomSmallWorldAct->setStatusTip(tr("Create a small-world random network, according to the Watts & Strogatz model.")); networkRandomSmallWorldAct->setWhatsThis( tr("

Small World

" "

Creates a random small-world network, according to the " "Watts & Strogatz model.

" "

A small-world network has short average path lengths and " "high clustering coefficient.

")); connect(networkRandomSmallWorldAct, SIGNAL(triggered()), this, SLOT(slotNetworkRandomSmallWorldDialog())); networkRandomErdosRenyiAct = new QAction(QIcon(":/images/erdos.png"), tr("ErdΕ‘s–RΓ©nyi"), this); networkRandomErdosRenyiAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_E)); networkRandomErdosRenyiAct->setStatusTip( tr("Create a random network according to the ErdΕ‘s–RΓ©nyi model")); networkRandomErdosRenyiAct->setWhatsThis( tr("

ErdΕ‘s–RΓ©nyi

" "

Creates a random network either of G(n, p) model or G(n,M) model.

" "

The former model creates edges with Bernoulli trials (probability p).

" "

The latter creates a graph of exactly M edges.

")); connect(networkRandomErdosRenyiAct, SIGNAL(triggered()), this, SLOT(slotNetworkRandomErdosRenyiDialog())); networkRandomLatticeAct = new QAction(QIcon(":/images/lattice.png"), tr("Lattice"), this); networkRandomLatticeAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_T)); networkRandomLatticeAct->setStatusTip(tr("Create a lattice network.")); networkRandomLatticeAct->setWhatsThis( tr("

Lattice

" "

Creates a random lattice network

" "

A lattice is a network whose drawing forms a regular tiling. " "Lattices are also known as meshes or grids.

")); connect(networkRandomLatticeAct, SIGNAL(triggered()), this, SLOT(slotNetworkRandomLatticeDialog())); networkRandomRegularSameDegreeAct = new QAction(QIcon(":/images/net.png"), tr("d-Regular"), this); networkRandomRegularSameDegreeAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_R)); networkRandomRegularSameDegreeAct->setStatusTip( tr("Create a d-regular random network, " "where every actor has the same degree d.")); networkRandomRegularSameDegreeAct->setWhatsThis( tr("

d-Regular

" "

Creates a random network where each actor has the same " "number d of neighbours, aka the same degree d.

")); connect(networkRandomRegularSameDegreeAct, SIGNAL(triggered()), this, SLOT(slotNetworkRandomRegularDialog())); networkRandomLatticeRingAct = new QAction(QIcon(":/images/net1.png"), tr("Ring Lattice"), this); networkRandomLatticeRingAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_L)); networkRandomLatticeRingAct->setStatusTip(tr("Create a ring lattice random network.")); networkRandomLatticeRingAct->setWhatsThis( tr("

Ring Lattice

" "

Creates a ring lattice random network.

" "

A ring lattice is a graph with N vertices each connected to d neighbors, d / 2 on each side.

")); connect(networkRandomLatticeRingAct, SIGNAL(triggered()), this, SLOT(slotNetworkRandomRingLattice())); networkRandomGaussianAct = new QAction(tr("Gaussian"), this); networkRandomGaussianAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_G)); networkRandomGaussianAct->setStatusTip(tr("Create a Gaussian distributed random network.")); networkRandomGaussianAct->setWhatsThis(tr("Gaussian \n\nCreates a random network of Gaussian distribution")); connect(networkRandomGaussianAct, SIGNAL(triggered()), this, SLOT(slotNetworkRandomGaussian())); networkWebCrawlerAct = new QAction(QIcon(":/images/webcrawler_48px.svg"), tr("&Web Crawler"), this); networkWebCrawlerAct->setShortcut(Qt::SHIFT | Qt::Key_C); networkWebCrawlerAct->setEnabled(true); networkWebCrawlerAct->setStatusTip(tr("Use the web crawler to create a network from all links found in a given website")); networkWebCrawlerAct->setWhatsThis( tr("

Web Crawler

" "

Creates a network of linked webpages, starting " "from an initial webpage using the built-in Web Crawler.

" "

The web crawler visits the given URL (website or webpage) " "and parses its contents to find links to other pages (internal or external). " "If there are such links, it adds them to a queue of URLs. " "Then, all the URLs in the queue list are visited in a FIFO order " "and parsed to find more links which are also added to the url queue. " "The process repeats until it reaches user-defined " "limits:

" "

Maximum urls to visit (max nodes in the resulting network)

" "

Maximum links per page

" "

Except the initial url and the limits, you can also " "specify patterns of urls to include or exclude, " "types of links to follow (internal, external or both) as well as " "if you want delay between requests (strongly advised)

.")); connect(networkWebCrawlerAct, SIGNAL(triggered()), this, SLOT(slotNetworkWebCrawlerDialog())); /** Edit menu actions */ editMouseModeInteractiveAct = new QAction(QIcon(":/images/cursor-pointer.svg"), tr("Select/Move"), this); editMouseModeInteractiveAct->setCheckable(true); editMouseModeInteractiveAct->setChecked(true); editMouseModeInteractiveAct->setToolTip(tr("

Mouse mode: Interactive

" "

In this interactive mode, you can click on nodes/edges and move them around with your mouse.

" "

Also, you can select multiple items with a rubber band selection area. To move the canvas, use the keyboard arrows.

")); editMouseModeInteractiveAct->setStatusTip(tr("Enable the interactive mouse mode to be able to click and move items and select them with a rubber band.")); editMouseModeInteractiveAct->setWhatsThis(tr("

Mouse Mode: Interactive

" "

In this mode, you can interact with the items on the canvas using the mouse:

" "

a) double-click to create new nodes, " "

b) left-click or right-click on items (i.e. nodes, edges) to edit their properties

" "

c) move nodes by dragging them with your mouse.

" "

d) select multiple items with a rubber band.

" "

To move the canvas (up/down, left/right), use the keyboard arrows.")); editMouseModeScrollAct = new QAction(QIcon(":/images/cursor-hand-drag.svg"), tr("Scroll/Pan"), this); editMouseModeScrollAct->setCheckable(true); editMouseModeScrollAct->setChecked(false); editMouseModeScrollAct->setToolTip(tr("

Mouse mode: Scrolling

" "

In this non-interactive mode, you can easily scroll the canvas by dragging the mouse around. All mouse actions are disabled.

")); editMouseModeScrollAct->setStatusTip(tr("Enable this non-interactive mode to easily scroll the canvas by dragging the mouse around.")); editMouseModeScrollAct->setWhatsThis(tr("

Mouse mode: Scrolling

" "

In this mode, you cannot interact with the canvas using the mouse.

" "

The cursor changes into a pointing hand, and dragging the mouse around will only scroll the scrolbars.

" "

You will not be able to select any items or move them around with the mouse.

" "

Note: You will still be able to edit the network using the menu or the toolbar actions and icons.

")); editRelationNextAct = new QAction(QIcon(":/images/chevron_right_48px.svg"), tr("Next Relation"), this); editRelationNextAct->setShortcut(Qt::CTRL | Qt::Key_Right); editRelationNextAct->setToolTip(tr("Goto the next relation of the network (if any).")); editRelationNextAct->setStatusTip(tr("Goto the next relation of the network (if any).")); editRelationNextAct->setWhatsThis(tr("Next Relation\n\nLoads the next relation of the network (if any)")); editRelationNextAct->setEnabled(false); editRelationPreviousAct = new QAction(QIcon(":/images/chevron_left_48px.svg"), tr("Previous Relation"), this); editRelationPreviousAct->setShortcut(Qt::CTRL | Qt::Key_Left); editRelationPreviousAct->setToolTip( tr("Goto the previous relation of the network (if any).")); editRelationPreviousAct->setStatusTip( tr("Goto the previous relation of the network (if any).")); editRelationPreviousAct->setWhatsThis( tr("Previous Relation\n\n" "Loads the previous relation of the network (if any)")); editRelationPreviousAct->setEnabled(false); editRelationAddAct = new QAction(QIcon(":/images/add_48px.svg"), tr("Add New Relation"), this); editRelationAddAct->setShortcut(Qt::ALT | Qt::CTRL | Qt::Key_N); editRelationAddAct->setToolTip( tr("Add a new relation to the network. Nodes will be preserved, edges will be removed. ")); editRelationAddAct->setStatusTip( tr("Add a new relation to the network. Nodes will be preserved, edges will be removed. ")); editRelationAddAct->setWhatsThis( tr("Add New Relation\n\n" "Adds a new relation to the active network. " "Nodes will be preserved, edges will be removed. ")); editRelationRenameAct = new QAction(QIcon(":/images/relation_edit_48px.svg"), tr("Rename Relation"), this); editRelationRenameAct->setToolTip(tr("Rename current relation")); editRelationRenameAct->setStatusTip(tr("Rename the current relation of the network.")); editRelationRenameAct->setWhatsThis(tr("Rename Relation\n\n" "Renames the current relation of the network.")); zoomInAct = new QAction(QIcon(":/images/zoom_in_24px.svg"), tr("Zoom In"), this); zoomInAct->setShortcut(Qt::CTRL | Qt::Key_Plus); zoomInAct->setStatusTip(tr("Zoom In.\n\nZooms in the network")); zoomInAct->setWhatsThis(tr("Zoom in the network. Alternatives: use the canvas button, or press Ctrl++, or use mouse wheel while pressing Ctrl.")); zoomOutAct = new QAction(QIcon(":/images/zoom_in_24px.svg"), tr("Zoom Out"), this); zoomOutAct->setShortcut(Qt::CTRL | Qt::Key_Minus); zoomOutAct->setStatusTip(tr("Zoom Out.\n\nZooms out of the actual network")); zoomOutAct->setWhatsThis(tr("Zoom out the network. Alternatives: use the canvas button, or press Ctrl+-, or use mouse wheel while pressing Ctrl.")); editRotateLeftAct = new QAction(QIcon(":/images/rotate_left_48px.svg"), tr("Rotate counterclockwise"), this); editRotateLeftAct->setShortcut(Qt::SHIFT | Qt::CTRL | Qt::LeftArrow); editRotateLeftAct->setStatusTip(tr("Rotate counterclockwise. You can also use the button underneath the canvas.")); editRotateLeftAct->setWhatsThis(tr("Rotates the network counterclockwise. You can also use the far left button below the canvas.")); editRotateRightAct = new QAction(QIcon(":/images/rotate_right_48px.svg"), tr("Rotate clockwise"), this); editRotateRightAct->setShortcut(Qt::SHIFT | Qt::CTRL | Qt::RightArrow); editRotateRightAct->setStatusTip(tr("Rotate clockwise. You can also use the button underneath the canvas.")); editRotateRightAct->setWhatsThis(tr("Rotates the network clockwise. You can also use the far right button below the canvas.")); editResetSlidersAct = new QAction(QIcon(":/images/refresh_48px.svg"), tr("Reset Zoom and Rotation"), this); editResetSlidersAct->setShortcut(Qt::CTRL | Qt::Key_0); editResetSlidersAct->setStatusTip(tr("Reset zoom and rotation to zero.")); editResetSlidersAct->setWhatsThis(tr("Resets any zoom and rotation transformations to zero.")); editNodeSelectAllAct = new QAction(QIcon(":/images/select_all_48px.svg"), tr("Select All"), this); editNodeSelectAllAct->setShortcut(QKeySequence::SelectAll); editNodeSelectAllAct->setStatusTip(tr("Select all nodes")); editNodeSelectAllAct->setWhatsThis(tr("Select All\n\nSelects all nodes and edges in the network")); connect(editNodeSelectAllAct, SIGNAL(triggered()), this, SLOT(slotEditNodeSelectAll())); editNodeSelectNoneAct = new QAction(QIcon(":/images/select_none_48px.svg"), tr("Select None"), this); editNodeSelectNoneAct->setShortcut(tr("Ctrl+Alt+A")); editNodeSelectNoneAct->setStatusTip(tr("Deselect all nodes and edges")); editNodeSelectNoneAct->setWhatsThis(tr("Deselect all\n\n Clears the node selection")); connect(editNodeSelectNoneAct, SIGNAL(triggered()), this, SLOT(slotEditNodeSelectNone())); editNodeFindAct = new QAction(QIcon(":/images/search_48px.svg"), tr("Find Nodes "), this); editNodeFindAct->setShortcut(QKeySequence::Find); editNodeFindAct->setToolTip(tr("Find and select one or more nodes by their number or label.")); editNodeFindAct->setStatusTip(tr("Find and select one or more nodes by their number or label.")); editNodeFindAct->setWhatsThis(tr("Find Node\n\n" "Finds one or more nodes by their number or label and " "highlights them by doubling its size. ")); connect(editNodeFindAct, SIGNAL(triggered()), this, SLOT(slotEditNodeFindDialog())); editNodeAddAct = new QAction(QIcon(":/images/node_add_48px.svg"), tr("Add Node"), this); editNodeAddAct->setShortcut(tr("Ctrl+.")); editNodeAddAct->setStatusTip(tr("Add a new node to the network in a random position. Alternately, double-click on a specific position the canvas. ")); editNodeAddAct->setToolTip( tr("Add a new node to the network in a random position.\n\n" "Alternately, create a new node by double-clicking on a specific position the canvas. ")); editNodeAddAct->setWhatsThis( tr("Add new node\n\n" "Add a new node to the network in a random position. \n\n" "Alternately, you can create a new node by double-clicking on a specific position the canvas.")); connect(editNodeAddAct, SIGNAL(triggered()), this, SLOT(slotEditNodeAdd())); editNodeRemoveAct = new QAction(QIcon(":/images/node_remove_48px.svg"), tr("Remove Node"), this); editNodeRemoveAct->setShortcut(Qt::CTRL | Qt::ALT | Qt::Key_Period); // Single key shortcuts with backspace or del do no work in Mac http://goo.gl/7hz7Dx editNodeRemoveAct->setToolTip(tr("Remove selected node(s). \n\n" "If no nodes are selected, you will be prompted for a node number. ")); editNodeRemoveAct->setStatusTip(tr("Remove selected node(s). If no nodes are selected, you will be prompted for a node number. ")); editNodeRemoveAct->setWhatsThis( tr("Remove node\n\n" "Removes selected node(s) from the network. \n" "Alternately, you can remove a node by right-clicking on it. \n" "If no nodes are selected, you will be prompted for a node number. ")); connect(editNodeRemoveAct, SIGNAL(triggered()), this, SLOT(slotEditNodeRemove())); editNodePropertiesAct = new QAction(QIcon(":/images/node_properties_24px.svg"), tr("Selected Node Properties"), this); editNodePropertiesAct->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Period); editNodePropertiesAct->setToolTip(tr("Change the properties of the selected node(s) \n\n" "There must be some nodes on the canvas!")); editNodePropertiesAct->setStatusTip(tr("Change the basic properties of the selected node(s). There must be some nodes on the canvas!")); editNodePropertiesAct->setWhatsThis(tr("Selected Node Properties\n\n" "If there are one or more nodes selected, " "it opens a properties dialog to edit " "their label, size, color, shape etc. \n" "You must have some node selected.")); connect(editNodePropertiesAct, SIGNAL(triggered()), this, SLOT(slotEditNodePropertiesDialog())); editNodeEditSelectionInTableAct = new QAction(tr("Edit Selection in Data Table"), this); editNodeEditSelectionInTableAct->setStatusTip( tr("Open the Data Table and pre-select the rows matching the current canvas selection.")); connect(editNodeEditSelectionInTableAct, &QAction::triggered, this, &MainWindow::slotEditNodeEditSelectionInTable); editNodeSetPropertyForSelectionAct = new QAction(tr("Set property on selected nodes..."), this); editNodeSetPropertyForSelectionAct->setStatusTip( tr("Set a single property (built-in or custom attribute) on all selected nodes.")); connect(editNodeSetPropertyForSelectionAct, &QAction::triggered, this, &MainWindow::slotEditNodeSetPropertyForSelection); editEdgeSetPropertyForSelectionAct = new QAction(tr("Set property on selected edges..."), this); editEdgeSetPropertyForSelectionAct->setStatusTip( tr("Set a single property (built-in or custom attribute) on all selected edges.")); connect(editEdgeSetPropertyForSelectionAct, &QAction::triggered, this, &MainWindow::slotEditEdgeSetPropertyForSelection); editNodeSelectedToCliqueAct = new QAction(QIcon(":/images/cliquenew.png"), tr("Create a clique from selected nodes "), this); editNodeSelectedToCliqueAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X, Qt::CTRL | Qt::Key_C)); editNodeSelectedToCliqueAct->setStatusTip(tr("Connect all selected nodes with edges to create a clique -- " "There must be some nodes selected!")); editNodeSelectedToCliqueAct->setWhatsThis(tr("Clique from Selected Nodes\n\n" "Adds all possible edges between selected nodes, " "so that they become a complete subgraph (clique)\n" "You must have some nodes selected.")); connect(editNodeSelectedToCliqueAct, SIGNAL(triggered()), this, SLOT(slotEditNodeSelectedToClique())); editNodeSelectedToStarAct = new QAction(QIcon(":/images/subgraphstar_128px.svg"), tr("Create a star from selected nodes "), this); editNodeSelectedToStarAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X, Qt::CTRL | Qt::Key_S)); editNodeSelectedToStarAct->setStatusTip(tr("Connect selected nodes with edges/arcs to create a star -- " "There must be some nodes selected!")); editNodeSelectedToStarAct->setWhatsThis(tr("Star from Selected Nodes\n\n" "Adds edges between selected nodes, " "so that they become a star subgraph.\n" "You must have some nodes selected.")); connect(editNodeSelectedToStarAct, SIGNAL(triggered()), this, SLOT(slotEditNodeSelectedToStar())); editNodeSelectedToCycleAct = new QAction(QIcon(":/images/subgraphcycle_48px.svg"), tr("Create a cycle from selected nodes "), this); editNodeSelectedToCycleAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X, Qt::CTRL | Qt::Key_Y)); editNodeSelectedToCycleAct->setStatusTip(tr("Connect selected nodes with edges/arcs to create a star -- " "There must be some nodes selected!")); editNodeSelectedToCycleAct->setWhatsThis(tr("Cycle from Selected Nodes\n\n" "Connect selected nodes " "so that they become a cycle subgraph.\n" "A cycle graph or circular graph is a graph that consists of a single cycle, or in other words, the vertices are connected in a closed chain. The cycle graph with n vertices is called Cβ‚™\n" "You must have some nodes selected.")); connect(editNodeSelectedToCycleAct, SIGNAL(triggered()), this, SLOT(slotEditNodeSelectedToCycle())); editNodeSelectedToLineAct = new QAction(QIcon(":/images/subgraphline.png"), tr("Create a line from selected nodes "), this); editNodeSelectedToLineAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X, Qt::CTRL | Qt::Key_L)); editNodeSelectedToLineAct->setStatusTip(tr("Connect selected nodes with edges/arcs to create a line-- " "There must be some nodes selected!")); editNodeSelectedToLineAct->setWhatsThis(tr("Line from Selected Nodes\n\n" "Adds edges between selected nodes, " "so that they become a line subgraph.\n" "You must have some nodes selected.")); connect(editNodeSelectedToLineAct, SIGNAL(triggered()), this, SLOT(slotEditNodeSelectedToLine())); editNodeColorAll = new QAction(QIcon(":/images/colorize_48px.svg"), tr("Change All Nodes Color"), this); editNodeColorAll->setStatusTip(tr("Choose a new color for all nodes.")); editNodeColorAll->setWhatsThis(tr("Nodes Color\n\n" "Changes all nodes color at once.")); connect(editNodeColorAll, SIGNAL(triggered()), this, SLOT(slotEditNodeColorAll())); editNodeSizeAllAct = new QAction(QIcon(":/images/size_select_24px.svg"), tr("Change All Nodes Size"), this); editNodeSizeAllAct->setStatusTip(tr("Change the size of all nodes")); editNodeSizeAllAct->setWhatsThis(tr("Change All Nodes Size\n\n" "Click to select and apply a new size for all nodes at once.")); connect(editNodeSizeAllAct, SIGNAL(triggered()), this, SLOT(slotEditNodeSizeAll())); editNodeShapeAll = new QAction(QIcon(":/images/format_shapes_48px.svg"), tr("Change All Nodes Shape"), this); editNodeShapeAll->setStatusTip(tr("Change the shape of all nodes")); editNodeShapeAll->setWhatsThis(tr("Change All Nodes Shape\n\n" "Click to select and apply a new shape for all nodes at once.")); connect(editNodeShapeAll, SIGNAL(triggered()), this, SLOT(slotEditNodeShape())); editNodeNumbersSizeAct = new QAction(QIcon(":/images/nodenumbersize_48px.svg"), tr("Change All Node Numbers Size"), this); editNodeNumbersSizeAct->setStatusTip(tr("Change the font size of the numbers of all nodes")); editNodeNumbersSizeAct->setWhatsThis(tr("Change Node Numbers Size\n\n" "Click to select and apply a new font size for all node numbers.")); connect(editNodeNumbersSizeAct, SIGNAL(triggered()), this, SLOT(slotEditNodeNumberSize())); editNodeNumbersColorAct = new QAction(QIcon(":/images/format_color_text_48px.svg"), tr("Change All Node Numbers Color"), this); editNodeNumbersColorAct->setStatusTip(tr("Change the color of the numbers of all nodes.")); editNodeNumbersColorAct->setWhatsThis(tr("Node Numbers Color\n\n" "Click to select and apply a new color " "to all node numbers.")); connect(editNodeNumbersColorAct, SIGNAL(triggered()), this, SLOT(slotEditNodeNumbersColor())); editNodeLabelsSizeAct = new QAction(QIcon(":/images/format_textsize_48px.svg"), tr("Change All Node Labels Size"), this); editNodeLabelsSizeAct->setStatusTip(tr("Change the font size of the labels of all nodes")); editNodeLabelsSizeAct->setWhatsThis(tr("Node Labels Size\n\n" "Click to select and apply a new font-size to all node labels.")); connect(editNodeLabelsSizeAct, SIGNAL(triggered()), this, SLOT(slotEditNodeLabelSize())); editNodeLabelsColorAct = new QAction(QIcon(":/images/format_color_text_48px.svg"), tr("Change All Node Labels Color"), this); editNodeLabelsColorAct->setStatusTip(tr("Change the color of the labels of all nodes")); editNodeLabelsColorAct->setWhatsThis(tr("Labels Color\n\n" "Click to select and apply a new color to all node labels.")); connect(editNodeLabelsColorAct, SIGNAL(triggered()), this, SLOT(slotEditNodeLabelsColor())); editEdgeAddAct = new QAction(QIcon(":/images/edge_add_48px.svg"), tr("Add Edge (arc)"), this); editEdgeAddAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Slash)); editEdgeAddAct->setStatusTip(tr("Add a directed edge (arc) from a node to another. ")); editEdgeAddAct->setToolTip( tr("Add a new edge from a node to another.\n\n" "You can also create an edge between two nodes \n" "by double-clicking on them consecutively.")); editEdgeAddAct->setWhatsThis( tr("Add edge\n\n" "Adds a new edge from a node to another.\n\n" "Alternately, you can create a new edge between two nodes " "by double-clicking on them consecutively.")); connect(editEdgeAddAct, SIGNAL(triggered()), this, SLOT(slotEditEdgeAdd())); editEdgeRemoveAct = new QAction(QIcon(":/images/edge_remove_48px.svg"), tr("Remove Edge"), this); editEdgeRemoveAct->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Slash)); editEdgeRemoveAct->setToolTip(tr("Remove selected edges from the network. \n\n" "If no edge has been clicked or selected, you will be prompted \n" "to enter edge source and target nodes for the edge to remove.")); editEdgeRemoveAct->setStatusTip(tr("Remove selected Edge(s)")); editEdgeRemoveAct->setWhatsThis(tr("Remove Edge\n\n" "Removes edges from the network. \n" "If one or more edges has been clicked or selected, they are removed. " "Otherwise, you will be prompted to enter edge source and target " "nodes for the edge to remove.")); connect(editEdgeRemoveAct, SIGNAL(triggered()), this, SLOT(slotEditEdgeRemove())); editEdgePropertiesAct = new QAction(QIcon(":/images/edge_properties_48px.svg"), tr("Edge Properties"), this); editEdgePropertiesAct->setStatusTip(tr("Edit the properties and custom attributes of the clicked edge")); editEdgePropertiesAct->setWhatsThis(tr("Edge Properties\n\n" "Opens a dialog to edit the label, weight, color " "and custom attributes of the selected edge.")); connect(editEdgePropertiesAct, &QAction::triggered, this, &MainWindow::slotEditEdgePropertiesDialog); editEdgeLabelAct = new QAction(QIcon(":/images/format_textsize_48px.svg"), tr("Change Edge Label"), this); editEdgeLabelAct->setStatusTip(tr("Change the Label of an Edge")); editEdgeLabelAct->setWhatsThis(tr("Change Edge Label\n\n" "Changes the label of an Edge")); connect(editEdgeLabelAct, SIGNAL(triggered()), this, SLOT(slotEditEdgeLabel())); editEdgeColorAct = new QAction(QIcon(":/images/colorize_48px.svg"), tr("Change Edge Color"), this); editEdgeColorAct->setStatusTip(tr("Change the Color of an Edge")); editEdgeColorAct->setWhatsThis(tr("Change Edge Color\n\n" "Changes the Color of an Edge")); connect(editEdgeColorAct, SIGNAL(triggered()), this, SLOT(slotEditEdgeColor())); editEdgeWeightAct = new QAction(QIcon(":/images/line_weight_48px.svg"), tr("Change Edge Weight"), this); editEdgeWeightAct->setStatusTip(tr("Change the weight of an Edge")); editEdgeWeightAct->setWhatsThis(tr("Edge Weight\n\n" "Changes the Weight of an Edge")); connect(editEdgeWeightAct, SIGNAL(triggered()), this, SLOT(slotEditEdgeWeight())); editEdgeColorAllAct = new QAction(QIcon(":/images/colorize_48px.svg"), tr("Change All Edges Color"), this); editEdgeColorAllAct->setStatusTip(tr("Change the color of all Edges.")); editEdgeColorAllAct->setWhatsThis(tr("All Edges Color\n\n" "Changes the color of all Edges")); connect(editEdgeColorAllAct, SIGNAL(triggered()), this, SLOT(slotEditEdgeColorAll())); editEdgeSymmetrizeAllAct = new QAction(QIcon(":/images/symmetrize.png"), tr("Symmetrize All Edges"), this); editEdgeSymmetrizeAllAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E, Qt::CTRL | Qt::Key_S)); editEdgeSymmetrizeAllAct->setStatusTip(tr("Make all directed ties to be reciprocated (thus, a symmetric graph).")); editEdgeSymmetrizeAllAct->setWhatsThis( tr("

Symmetrize All Edges

" "

Forces all edges in this relation to be reciprocated: " "

If there is a directed edge from node A to node B \n" "then a new directed edge from node B to node A will be \n" " created, with the same weight.

" "

The result is a symmetric network.

")); connect(editEdgeSymmetrizeAllAct, SIGNAL(triggered()), this, SLOT(slotEditEdgeSymmetrizeAll())); editEdgeSymmetrizeStrongTiesAct = new QAction(QIcon(":/images/symmetrize_48px.svg"), tr("Symmetrize by Strong Ties"), this); editEdgeSymmetrizeStrongTiesAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E, Qt::CTRL | Qt::Key_T)); editEdgeSymmetrizeStrongTiesAct->setStatusTip(tr("Create a new symmetric relation by counting reciprocated ties only (strong ties).")); editEdgeSymmetrizeStrongTiesAct->setWhatsThis( tr("

Symmetrize Edges by Strong Ties:

" "

Creates a new symmetric relation by keeping strong ties only.

" "

A tie between actors A and B is considered strong if both A -> B and B -> A exist. " "Therefore, in the new relation, a reciprocated edge will be created between actors A and B " "only if both arcs A->B and B->A were present in the current or all relations.

" "

If the network is multi-relational, it will ask you whether " "ties in the current relation or all relations are to be considered.

")); connect(editEdgeSymmetrizeStrongTiesAct, SIGNAL(triggered()), this, SLOT(slotEditEdgeSymmetrizeStrongTies())); // TODO Separate action for Directed/Undirected graph drawing (without changing all existing edges). editEdgeUndirectedAllAct = new QAction(tr("Undirected Edges"), this); editEdgeUndirectedAllAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E, Qt::CTRL | Qt::Key_U)); editEdgeUndirectedAllAct->setStatusTip(tr("Enable to transform all arcs to undirected edges and hereafter work with undirected edges .")); editEdgeUndirectedAllAct->setWhatsThis( tr("Undirected Edges\n\n" "Transforms all directed arcs to undirected edges. \n" "The result is a undirected and symmetric network." "After that, every new edge you add, will be undirected too." "If you disable this, then all edges become directed again.")); editEdgeUndirectedAllAct->setCheckable(true); editEdgeUndirectedAllAct->setChecked(false); connect(editEdgeUndirectedAllAct, SIGNAL(triggered(bool)), this, SLOT(slotEditEdgeUndirectedAll(bool))); editEdgesCocitationAct = new QAction(QIcon(":/images/cocitation_48px.svg"), tr("Cocitation Network"), this); editEdgesCocitationAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E, Qt::CTRL | Qt::Key_C)); editEdgesCocitationAct->setStatusTip(tr("Create a new symmetric relation by " "connecting actors that are cocitated by others.")); editEdgesCocitationAct->setWhatsThis( tr("

Symmetrize Edges by examining Cocitation:

" "

Creates a new symmetric relation by connecting actors " "that are cocitated by others. " "In the new relation, an edge will be created between actor i and " "actor j only if C(i,j) > 0, where C the Cocitation Matrix.

" "

Thus the actor pairs cited by more common neighbors will appear " "with a stronger tie between them than pairs those cited by fewer " "common neighbors. " "The resulting relation is symmetric.

")); connect(editEdgesCocitationAct, SIGNAL(triggered()), this, SLOT(slotEditEdgeSymmetrizeCocitation())); editEdgeDichotomizeAct = new QAction(QIcon(":/images/filter_list_48px.svg"), tr("Dichotomize Valued Edges"), this); editEdgeDichotomizeAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E, Qt::CTRL | Qt::Key_D)); editEdgeDichotomizeAct->setStatusTip(tr("Create a new binary relation/graph in a valued network " "using edge dichotomization.")); editEdgeDichotomizeAct->setWhatsThis( tr("Dichotomize Edges\n\n" "Creates a new binary relation in a valued network using " "edge dichotomization according to a given threshold value. \n" "In the new dichotomized relation, an edge will exist between actor i and " "actor j only if e(i,j) > threshold, where threshold is a user-defined value." "Thus the dichotomization procedure is as follows: " "Choose a threshold value, set all ties with equal or higher values " "to equal one, and all lower to equal zero." "The result is a binary (dichotomized) graph. " "The process is also known as compression and slicing")); connect(editEdgeDichotomizeAct, SIGNAL(triggered()), this, SLOT(slotEditEdgeDichotomizationDialog())); transformNodes2EdgesAct = new QAction(tr("Transform Nodes to Edges"), this); transformNodes2EdgesAct->setStatusTip(tr("Transforms the network so that " "nodes become Edges and vice versa")); transformNodes2EdgesAct->setWhatsThis(tr("Transform Nodes EdgesAct\n\n" "Transforms network so that nodes become Edges and vice versa")); connect(transformNodes2EdgesAct, SIGNAL(triggered()), this, SLOT(slotEditTransformNodes2Edges())); filterNodesByCentralityAct = new QAction(QIcon(":/images/filter_centrality_48px.svg"), tr("Filter Nodes By Centrality"), this); filterNodesByCentralityAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X, Qt::CTRL | Qt::Key_E)); filterNodesByCentralityAct->setStatusTip(tr("Temporarily filter out nodes according to their centrality score.")); filterNodesByCentralityAct->setWhatsThis(tr("Filter Nodes By Centrality\n\n" "Filters out nodes according to their score in a user-selected centrality index.")); connect(filterNodesByCentralityAct, SIGNAL(triggered()), this, SLOT(slotFilterNodesDialogByCentrality())); filterNodesByAttributeAct = new QAction(QIcon(":/images/filter_attribute_48px.svg"), tr("Filter by Attribute..."), this); filterNodesByAttributeAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X, Qt::CTRL | Qt::Key_A)); filterNodesByAttributeAct->setStatusTip(tr("Show only nodes or edges matching a custom attribute condition (key, operator, value).")); filterNodesByAttributeAct->setWhatsThis(tr("Filter by Attribute\n\n" "Opens a dialog to filter nodes, edges, or both by a custom attribute. " "Non-matching elements are hidden (reversible via Restore All Nodes).")); connect(filterNodesByAttributeAct, SIGNAL(triggered()), this, SLOT(slotFilterNodesByAttribute())); filterByQueryBuilderAct = new QAction(QIcon(":/images/filter_list_48px.svg"), tr("Query Builder..."), this); filterByQueryBuilderAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X, Qt::CTRL | Qt::Key_B)); filterByQueryBuilderAct->setStatusTip(tr("Build a multi-condition AND filter for nodes or edges.")); filterByQueryBuilderAct->setWhatsThis(tr("Query Builder\n\n" "Opens the Query Builder dialog to compose a filter with " "multiple attribute conditions (AND logic). All conditions " "must match for a node or edge to remain visible.")); connect(filterByQueryBuilderAct, SIGNAL(triggered()), this, SLOT(slotFilterByQueryBuilder())); filterNodesBySelectionAct = new QAction(tr("Focus on Selection"), this); filterNodesBySelectionAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X, Qt::CTRL | Qt::Key_O)); filterNodesBySelectionAct->setStatusTip(tr("Show only the selected nodes and edges between them.")); filterNodesBySelectionAct->setWhatsThis(tr("Focus on Selection\n\n" "Hides all nodes except the currently selected ones. " "Only edges between selected nodes remain visible. " "Use 'Restore All Nodes' to undo.")); filterNodesBySelectionAct->setEnabled(false); // enabled when >= 1 node selected connect(filterNodesBySelectionAct, SIGNAL(triggered()), this, SLOT(slotFilterNodesBySelection())); filterNodesByEgoNetworkAct = new QAction(tr("Focus on Node (Ego Network)"), this); filterNodesByEgoNetworkAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X, Qt::CTRL | Qt::Key_F)); filterNodesByEgoNetworkAct->setStatusTip(tr("Show only the selected node and its direct neighbors.")); filterNodesByEgoNetworkAct->setWhatsThis(tr("Focus on Node (Ego Network)\n\n" "Hides all nodes except the selected node and its direct neighbors. " "Use 'Restore All Nodes' to undo.")); filterNodesByEgoNetworkAct->setEnabled(false); // enabled only when exactly 1 node selected connect(filterNodesByEgoNetworkAct, SIGNAL(triggered()), this, SLOT(slotFilterNodesByEgoNetwork())); filterNodesRestoreAllAct = new QAction(QIcon(":/images/filter_restore_nodes_48px.svg"), tr("Restore All Nodes"), this); filterNodesRestoreAllAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X, Qt::CTRL | Qt::Key_R)); filterNodesRestoreAllAct->setStatusTip(tr("Restore all nodes hidden by the last filter.")); filterNodesRestoreAllAct->setEnabled(false); // enabled only when history stack is non-empty connect(filterNodesRestoreAllAct, SIGNAL(triggered()), this, SLOT(slotFilterNodesRestoreAll())); editFilterNodesIsolatesAct = new QAction(tr("Disable Isolate Nodes"), this); editFilterNodesIsolatesAct->setEnabled(true); editFilterNodesIsolatesAct->setCheckable(true); editFilterNodesIsolatesAct->setChecked(false); editFilterNodesIsolatesAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X, Qt::CTRL | Qt::Key_I)); editFilterNodesIsolatesAct->setStatusTip(tr("Temporarily filter out nodes with no edges")); editFilterNodesIsolatesAct->setWhatsThis(tr("Filter Isolate Nodes\n\n" "Enables or disables displaying of isolate nodes. " "Isolate nodes are those with no edges...")); connect(editFilterNodesIsolatesAct, SIGNAL(toggled(bool)), this, SLOT(slotEditFilterNodesIsolates(bool))); editFilterEdgesByWeightAct = new QAction(QIcon(":/images/filter_edges_48px.svg"), tr("Filter Edges by Weight"), this); editFilterEdgesByWeightAct->setEnabled(true); editFilterEdgesByWeightAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E, Qt::CTRL | Qt::Key_F)); editFilterEdgesByWeightAct->setStatusTip(tr("Temporarily filter edges of some weight out of the network")); editFilterEdgesByWeightAct->setWhatsThis(tr("Filter Edges\n\n" "Filters edges according to their weight.")); connect(editFilterEdgesByWeightAct, SIGNAL(triggered()), this, SLOT(slotEditFilterEdgesByWeightDialog())); editFilterEdgesRestoreAllAct = new QAction(QIcon(":/images/filter_restore_edges_48px.svg"), tr("Restore All Edges"), this); editFilterEdgesRestoreAllAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E, Qt::CTRL | Qt::Key_R)); editFilterEdgesRestoreAllAct->setStatusTip(tr("Restore all edges hidden by the weight filter.")); editFilterEdgesRestoreAllAct->setWhatsThis(tr("Restore All Edges\n\n" "Re-enables all edges hidden by the weight filter. " "No data is modified.")); editFilterEdgesRestoreAllAct->setEnabled(false); connect(editFilterEdgesRestoreAllAct, SIGNAL(triggered()), this, SLOT(slotEditFilterEdgesReset())); editFilterEdgesUnilateralAct = new QAction(tr("Disable unilateral edges"), this); editFilterEdgesUnilateralAct->setEnabled(true); editFilterEdgesUnilateralAct->setCheckable(true); editFilterEdgesUnilateralAct->setChecked(false); editFilterEdgesUnilateralAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E, Qt::CTRL | Qt::Key_E)); editFilterEdgesUnilateralAct->setStatusTip(tr("Temporarily disable all unilateral (non-reciprocal) edges in this relation. Keeps only \"strong\" ties.")); editFilterEdgesUnilateralAct->setWhatsThis(tr("Unilateral edges\n\n" "In directed networks, a tie between two actors " "is unilateral when only one actor identifies the other " "as connected (i.e. friend, vote, etc). " "A unilateral tie is depicted as a single arc. " "These ties are considered weak, as opposed to " "reciprocal ties where both actors identify each other as connected. " "Strong ties are depicted as either a single undirected edge " "or as two reciprocated arcs between two nodes. " "By selecting this option, all unilateral edges in this relation will be disabled.")); connect(editFilterEdgesUnilateralAct, SIGNAL(triggered(bool)), this, SLOT(slotEditFilterEdgesUnilateral(bool))); editSubgraphExtractAct = new QAction(tr("Save visible nodes as subgraph..."), this); editSubgraphExtractAct->setEnabled(true); editSubgraphExtractAct->setStatusTip(tr("Copy the currently visible (non-filtered) nodes and their inter-edges into a new graph file.")); editSubgraphExtractAct->setWhatsThis(tr("Save visible nodes as subgraph\n\n" "Creates an independent copy of the nodes and edges that are currently " "visible on the canvas (i.e. not hidden by any filter). " "Vertices are renumbered from 1; all visual properties and custom " "attributes are preserved. You will be prompted for a name and a " "save location.")); connect(editSubgraphExtractAct, &QAction::triggered, this, &MainWindow::slotEditSubgraphExtract); editSubgraphExtractFromSelectionAct = new QAction(tr("Save selected nodes as subgraph..."), this); editSubgraphExtractFromSelectionAct->setEnabled(false); // enabled when >= 1 node selected editSubgraphExtractFromSelectionAct->setStatusTip(tr("Copy the currently selected nodes and their inter-edges into a new graph file.")); editSubgraphExtractFromSelectionAct->setWhatsThis(tr("Save selected nodes as subgraph\n\n" "Creates an independent copy of the selected nodes and the edges " "that run between them. Vertices are renumbered from 1; all visual " "properties and custom attributes are preserved. You will be " "prompted for a name and a save location.")); connect(editSubgraphExtractFromSelectionAct, &QAction::triggered, this, &MainWindow::slotEditSubgraphExtractFromSelection); /** Layout menu actions */ strongColorationAct = new QAction(tr("Strong Structural"), this); strongColorationAct->setStatusTip(tr("Nodes are assigned the same color if they have identical in and out neighborhoods")); strongColorationAct->setWhatsThis(tr("Click this to colorize nodes; Nodes are assigned the same color if they have identical in and out neighborhoods")); connect(strongColorationAct, SIGNAL(triggered()), this, SLOT(slotLayoutColorationStrongStructural())); regularColorationAct = new QAction(tr("Regular"), this); regularColorationAct->setStatusTip( tr("Nodes are assigned the same color if they have " "neighborhoods of the same set of colors")); regularColorationAct ->setWhatsThis( tr("Click this to colorize nodes; " "Nodes are assigned the same color if they have neighborhoods " "of the same set of colors")); connect(regularColorationAct, SIGNAL(triggered()), this, SLOT(slotLayoutColorationRegular())); // TODO layoutRandomAct = new QAction(tr("Random"), this); layoutRandomAct->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_0)); layoutRandomAct->setStatusTip(tr("Layout the network actors in random positions.")); layoutRandomAct->setWhatsThis(tr("Random Layout\n\n " "This layout algorithm repositions all " "network actors in random positions.")); connect(layoutRandomAct, SIGNAL(triggered()), this, SLOT(slotLayoutRandom())); layoutRandomRadialAct = new QAction(tr("Random Circles"), this); layoutRandomRadialAct->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_0)); layoutRandomRadialAct->setStatusTip(tr("Layout the network in random concentric circles")); layoutRandomRadialAct->setWhatsThis( tr("Random Circles Layout\n\n Repositions the nodes randomly on circles")); connect(layoutRandomRadialAct, SIGNAL(triggered()), this, SLOT(slotLayoutRadialRandom())); layoutEgoRadialAct = new QAction(QIcon(":/images/ego_radial_layout_48px.svg"), tr("Ego Radial layout"), this); layoutEgoRadialAct->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_E)); layoutEgoRadialAct->setStatusTip(tr("Place the selected vertex at center, its neighbors on an inner ring, all others on an outer ring")); layoutEgoRadialAct->setWhatsThis(tr("Ego Radial Layout\n\nPlaces the selected vertex at the canvas center, its 1-hop out-neighbors on an inner ring, and all remaining nodes on an outer ring.")); connect(layoutEgoRadialAct, SIGNAL(triggered()), this, SLOT(slotLayoutEgoRadial())); layoutRadialProminence_DC_Act = new QAction(tr("Degree Centrality"), this); layoutRadialProminence_DC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_1)); layoutRadialProminence_DC_Act ->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Degree Centrality.")); layoutRadialProminence_DC_Act->setWhatsThis( tr("Degree Centrality (DC) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their Degree Centrality score. " "Nodes with higher DC are closer to the centre.")); connect(layoutRadialProminence_DC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_CC_Act = new QAction(tr("Closeness Centrality"), this); layoutRadialProminence_CC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_2)); layoutRadialProminence_CC_Act ->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Closeness Centrality.")); layoutRadialProminence_CC_Act->setWhatsThis( tr("Closeness Centrality (CC) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their Closeness Centrality. " "Nodes having higher CC are closer to the centre.")); connect(layoutRadialProminence_CC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_IRCC_Act = new QAction( tr("Influence Range Closeness Centrality"), this); layoutRadialProminence_IRCC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_3)); layoutRadialProminence_IRCC_Act ->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Influence Range Closeness Centrality.")); layoutRadialProminence_IRCC_Act->setWhatsThis( tr("Influence Range Closeness Centrality (IRCC) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their IRCC score. " "Nodes having higher IRCC are closer to the centre.")); connect(layoutRadialProminence_IRCC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_BC_Act = new QAction(tr("Betweenness Centrality"), this); layoutRadialProminence_BC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_4)); layoutRadialProminence_BC_Act->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Betweenness Centrality.")); layoutRadialProminence_BC_Act->setWhatsThis( tr("Betweenness Centrality (BC) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their Betweenness Centrality. " "Nodes having higher BC are closer to the centre.")); connect(layoutRadialProminence_BC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_SC_Act = new QAction(tr("Stress Centrality"), this); layoutRadialProminence_SC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_5)); layoutRadialProminence_SC_Act->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Stress Centrality.")); layoutRadialProminence_SC_Act->setWhatsThis( tr("Stress Centrality (SC) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their Stress Centrality score. " "Nodes having higher SC are closer to the centre.")); connect(layoutRadialProminence_SC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_EC_Act = new QAction(tr("Eccentricity Centrality"), this); layoutRadialProminence_EC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_6)); layoutRadialProminence_EC_Act->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Eccentricity Centrality (aka Harary Graph Centrality).")); layoutRadialProminence_EC_Act->setWhatsThis( tr("Eccentricity Centrality (EC) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their Eccentricity Centrality " "(aka Harary Graph Centrality) score. " "Nodes having higher EC are closer to the centre.")); connect(layoutRadialProminence_EC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_PC_Act = new QAction(tr("Power Centrality"), this); layoutRadialProminence_PC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_7)); layoutRadialProminence_PC_Act->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Power Centrality.")); layoutRadialProminence_PC_Act->setWhatsThis( tr("Power Centrality (PC) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their Power Centrality score. " "Nodes having higher PC are closer to the centre.")); connect(layoutRadialProminence_PC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_IC_Act = new QAction(tr("Information Centrality"), this); layoutRadialProminence_IC_Act->setEnabled(true); layoutRadialProminence_IC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_8)); layoutRadialProminence_IC_Act->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Information Centrality.")); layoutRadialProminence_IC_Act->setWhatsThis( tr("Information Centrality (IC) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their Information Centrality score. " "Nodes of higher IC are closer to the centre.")); connect(layoutRadialProminence_IC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_EVC_Act = new QAction(tr("Eigenvector Centrality"), this); layoutRadialProminence_EVC_Act->setEnabled(true); layoutRadialProminence_EVC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_9)); layoutRadialProminence_EVC_Act->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Eigenvector Centrality.")); layoutRadialProminence_EVC_Act->setWhatsThis( tr("Eigenvector Centrality (EVC) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their Eigenvector Centrality score. " "Nodes of higher EVC are closer to the centre.")); connect(layoutRadialProminence_EVC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_KATZ_Act = new QAction(tr("Katz Centrality"), this); layoutRadialProminence_KATZ_Act->setEnabled(true); layoutRadialProminence_KATZ_Act->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Katz Centrality.")); layoutRadialProminence_KATZ_Act->setWhatsThis( tr("Katz Centrality (KC) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their Katz Centrality score. " "Nodes of higher KC are closer to the centre. " "Requires Katz Centrality to have been computed first " "(Analyze > Centrality > Katz Centrality).")); connect(layoutRadialProminence_KATZ_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_BPC_Act = new QAction(tr("Bonacich Power Centrality"), this); layoutRadialProminence_BPC_Act->setEnabled(true); layoutRadialProminence_BPC_Act->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Bonacich Power Centrality.")); layoutRadialProminence_BPC_Act->setWhatsThis( tr("Bonacich Power Centrality (BPC) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their Bonacich Power Centrality score. " "Nodes of higher BPC are closer to the centre. " "Requires Bonacich Power Centrality to have been computed first " "(Analyze > Centrality > Bonacich Power Centrality).")); connect(layoutRadialProminence_BPC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_DP_Act = new QAction(tr("Degree Prestige"), this); layoutRadialProminence_DP_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_I)); layoutRadialProminence_DP_Act->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Degree Prestige (inDegree).")); layoutRadialProminence_DP_Act->setWhatsThis( tr("Degree Prestige (DP) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their inDegree score. " "Nodes having higher DP are closer to the centre.")); connect(layoutRadialProminence_DP_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_PRP_Act = new QAction(tr("PageRank Prestige"), this); layoutRadialProminence_PRP_Act->setEnabled(true); layoutRadialProminence_PRP_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_K)); layoutRadialProminence_PRP_Act->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their PRP index.")); layoutRadialProminence_PRP_Act->setWhatsThis( tr("PageRank Prestige (PRP) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their PageRank score. " "Nodes having higher PRP are closer to the centre.")); connect(layoutRadialProminence_PRP_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutRadialProminence_PP_Act = new QAction(tr("Proximity Prestige"), this); layoutRadialProminence_PP_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_R, Qt::CTRL | Qt::Key_Y)); layoutRadialProminence_PP_Act->setStatusTip( tr("Place all nodes on concentric circles of radius inversely " "proportional to their Proximity Prestige.")); layoutRadialProminence_PP_Act->setWhatsThis( tr("Proximity Prestige (PP) Radial Layout\n\n" "Repositions all nodes on concentric circles of radius " "inversely proportional to their PP index. " "Nodes having higher PP score are closer to the centre.")); connect(layoutRadialProminence_PP_Act, SIGNAL(triggered()), this, SLOT(slotLayoutRadialByProminenceIndex())); layoutLevelProminence_DC_Act = new QAction(tr("Degree Centrality"), this); layoutLevelProminence_DC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_1)); layoutLevelProminence_DC_Act ->setStatusTip( tr("Place all nodes on horizontal levels of height " "proportional to their Degree Centrality.")); layoutLevelProminence_DC_Act->setWhatsThis( tr("Degree Centrality (DC) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height" "proportional to their DC score. " "Nodes having higher DC are closer to the top.\n\n")); connect(layoutLevelProminence_DC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_CC_Act = new QAction(tr("Closeness Centrality"), this); layoutLevelProminence_CC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_2)); layoutLevelProminence_CC_Act ->setStatusTip( tr("Place all nodes on horizontal levels of height " "proportional to their Closeness Centrality.")); layoutLevelProminence_CC_Act->setWhatsThis( tr("Closeness Centrality (CC) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height" "proportional to their Closeness Centrality score. " "Nodes of higher CC are closer to the top.\n\n" "This layout can be computed only for connected graphs. ")); connect(layoutLevelProminence_CC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_IRCC_Act = new QAction( tr("Influence Range Closeness Centrality"), this); layoutLevelProminence_IRCC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_3)); layoutLevelProminence_IRCC_Act ->setStatusTip( tr("Place all nodes on horizontal levels of height " "proportional to their Influence Range Closeness Centrality.")); layoutLevelProminence_IRCC_Act->setWhatsThis( tr("Influence Range Closeness Centrality (IRCC) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height" "proportional to their IRCC score. " "Nodes having higher IRCC are closer to the top.\n\n" "This layout can be computed for not connected graphs. ")); connect(layoutLevelProminence_IRCC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_BC_Act = new QAction(tr("Betweenness Centrality"), this); layoutLevelProminence_BC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_4)); layoutLevelProminence_BC_Act->setStatusTip( tr("Place all nodes on horizontal levels of height " "proportional to their Betweenness Centrality.")); layoutLevelProminence_BC_Act->setWhatsThis( tr("Betweenness Centrality (BC) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height" "proportional to their Betweenness Centrality score. " "Nodes having higher BC are closer to the top.")); connect(layoutLevelProminence_BC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_SC_Act = new QAction(tr("Stress Centrality"), this); layoutLevelProminence_SC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_5)); layoutLevelProminence_SC_Act->setStatusTip( tr("Place nodes on horizontal levels of height " "proportional to their Stress Centrality.")); layoutLevelProminence_SC_Act->setWhatsThis( tr("Stress Centrality (SC) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height" "proportional to their Stress Centrality score. " "Nodes having higher SC are closer to the top.")); connect(layoutLevelProminence_SC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_EC_Act = new QAction(tr("Eccentricity Centrality"), this); layoutLevelProminence_EC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_6)); layoutLevelProminence_EC_Act->setStatusTip( tr("Place nodes on horizontal levels of height " "proportional to their Eccentricity Centrality (aka Harary Graph Centrality).")); layoutLevelProminence_EC_Act->setWhatsThis( tr("Eccentricity Centrality (EC) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height" "proportional to their Eccentricity Centrality " "(aka Harary Graph Centrality) score. " "Nodes having higher EC are closer to the top.")); connect(layoutLevelProminence_EC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_PC_Act = new QAction(tr("Power Centrality"), this); layoutLevelProminence_PC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_7)); layoutLevelProminence_PC_Act->setStatusTip( tr("Place nodes on horizontal levels of height " "proportional to their Power Centrality.")); layoutLevelProminence_PC_Act->setWhatsThis( tr("Power Centrality (PC) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height" "proportional to their Power Centrality score. " "Nodes having higher PC are closer to the top.")); connect(layoutLevelProminence_PC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_IC_Act = new QAction(tr("Information Centrality"), this); layoutLevelProminence_IC_Act->setEnabled(true); layoutLevelProminence_IC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_8)); layoutLevelProminence_IC_Act->setStatusTip( tr("Place nodes on horizontal levels of height " "proportional to their Information Centrality.")); layoutLevelProminence_IC_Act->setWhatsThis( tr("Information Centrality (IC) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height" "proportional to their Information Centrality score. " "Nodes having higher IC are closer to the top.")); connect(layoutLevelProminence_IC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_EVC_Act = new QAction(tr("Eigenvector Centrality"), this); layoutLevelProminence_EVC_Act->setEnabled(true); layoutLevelProminence_EVC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_9)); layoutLevelProminence_EVC_Act->setStatusTip( tr( "Place nodes on horizontal levels of height " "proportional to their Eigenvector Centrality.")); layoutLevelProminence_EVC_Act->setWhatsThis( tr("Eigenvector Centrality (EVC) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height" "proportional to their Eigenvector Centrality score. " "Nodes having higher EVC are closer to the top.")); connect(layoutLevelProminence_EVC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_KATZ_Act = new QAction(tr("Katz Centrality"), this); layoutLevelProminence_KATZ_Act->setEnabled(true); layoutLevelProminence_KATZ_Act->setStatusTip( tr( "Place nodes on horizontal levels of height " "proportional to their Katz Centrality.")); layoutLevelProminence_KATZ_Act->setWhatsThis( tr("Katz Centrality (KC) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height " "proportional to their Katz Centrality score. " "Nodes having higher KC are closer to the top. " "Requires Katz Centrality to have been computed first " "(Analyze > Centrality > Katz Centrality).")); connect(layoutLevelProminence_KATZ_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_BPC_Act = new QAction(tr("Bonacich Power Centrality"), this); layoutLevelProminence_BPC_Act->setEnabled(true); layoutLevelProminence_BPC_Act->setStatusTip( tr( "Place nodes on horizontal levels of height " "proportional to their Bonacich Power Centrality.")); layoutLevelProminence_BPC_Act->setWhatsThis( tr("Bonacich Power Centrality (BPC) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height " "proportional to their Bonacich Power Centrality score. " "Nodes having higher BPC are closer to the top. " "Requires Bonacich Power Centrality to have been computed first " "(Analyze > Centrality > Bonacich Power Centrality).")); connect(layoutLevelProminence_BPC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_DP_Act = new QAction(tr("Degree Prestige"), this); layoutLevelProminence_DP_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_I)); layoutLevelProminence_DP_Act->setStatusTip( tr("Place nodes on horizontal levels of height " "proportional to their Degree Prestige.")); layoutLevelProminence_DP_Act->setWhatsThis( tr("Degree Prestige (DP) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height" "proportional to their Degree Prestige score. " "Nodes having higher DP are closer to the top.")); connect(layoutLevelProminence_DP_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_PRP_Act = new QAction(tr("PageRank Prestige"), this); layoutLevelProminence_PRP_Act->setEnabled(true); layoutLevelProminence_PRP_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_K)); layoutLevelProminence_PRP_Act->setStatusTip( tr("Place nodes on horizontal levels of height " "proportional to their PageRank Prestige.")); layoutLevelProminence_PRP_Act->setWhatsThis( tr("PageRank Prestige (PRP) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height" "proportional to their PageRank Prestige score. " "Nodes having higher PRP are closer to the top.")); connect(layoutLevelProminence_PRP_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutLevelProminence_PP_Act = new QAction(tr("Proximity Prestige"), this); layoutLevelProminence_PP_Act->setEnabled(true); layoutLevelProminence_PP_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_Y)); layoutLevelProminence_PP_Act->setStatusTip( tr("Place nodes on horizontal levels of height " "proportional to their Proximity Prestige.")); layoutLevelProminence_PP_Act->setWhatsThis( tr("Proximity Prestige (PP) Levels Layout\n\n" "Repositions all nodes on horizontal levels of height" "proportional to their Proximity Prestige score. " "Nodes having higher PP are closer to the top.")); connect(layoutLevelProminence_PP_Act, SIGNAL(triggered()), this, SLOT(slotLayoutLevelByProminenceIndex())); layoutNodeSizeProminence_DC_Act = new QAction(tr("Degree Centrality"), this); layoutNodeSizeProminence_DC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_S, Qt::CTRL | Qt::Key_1)); layoutNodeSizeProminence_DC_Act ->setStatusTip( tr("Resize all nodes to be " "proportional to their Degree Centrality.")); layoutNodeSizeProminence_DC_Act->setWhatsThis( tr( "Degree Centrality (DC) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their DC (inDegree) score. \n\n" "Nodes having higher DC will appear bigger.")); connect(layoutNodeSizeProminence_DC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_CC_Act = new QAction(tr("Closeness Centrality"), this); layoutNodeSizeProminence_CC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_S, Qt::CTRL | Qt::Key_2)); layoutNodeSizeProminence_CC_Act ->setStatusTip( tr("Resize all nodes to be " "proportional to their Closeness Centrality.")); layoutNodeSizeProminence_CC_Act->setWhatsThis( tr("Closeness Centrality (CC) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their CC score. " "Nodes of higher CC will appear bigger.\n\n" "This layout can be computed only for connected graphs. ")); connect(layoutNodeSizeProminence_CC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_IRCC_Act = new QAction( tr("Influence Range Closeness Centrality"), this); layoutNodeSizeProminence_IRCC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_S, Qt::CTRL | Qt::Key_3)); layoutNodeSizeProminence_IRCC_Act ->setStatusTip( tr("Resize all nodes to be proportional " "to their Influence Range Closeness Centrality.")); layoutNodeSizeProminence_IRCC_Act->setWhatsThis( tr("Influence Range Closeness Centrality (IRCC) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their IRCC score. " "Nodes having higher IRCC will appear bigger.\n\n" "This layout can be computed for not connected graphs. ")); connect(layoutNodeSizeProminence_IRCC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_BC_Act = new QAction(tr("Betweenness Centrality"), this); layoutNodeSizeProminence_BC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_S, Qt::CTRL | Qt::Key_4)); layoutNodeSizeProminence_BC_Act->setStatusTip( tr("Resize all nodes to be " "proportional to their Betweenness Centrality.")); layoutNodeSizeProminence_BC_Act->setWhatsThis( tr("Betweenness Centrality (BC) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their Betweenness Centrality score. " "Nodes having higher BC will appear bigger.")); connect(layoutNodeSizeProminence_BC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_SC_Act = new QAction(tr("Stress Centrality"), this); layoutNodeSizeProminence_SC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_S, Qt::CTRL | Qt::Key_5)); layoutNodeSizeProminence_SC_Act->setStatusTip( tr("Resize all nodes to be " "proportional to their Stress Centrality.")); layoutNodeSizeProminence_SC_Act->setWhatsThis( tr("Stress Centrality (SC) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their Stress Centrality score. " "Nodes having higher SC will appear bigger.")); connect(layoutNodeSizeProminence_SC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_EC_Act = new QAction(tr("Eccentricity Centrality"), this); layoutNodeSizeProminence_EC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_S, Qt::CTRL | Qt::Key_6)); layoutNodeSizeProminence_EC_Act->setStatusTip( tr("Resize all nodes to be " "proportional to their Eccentricity Centrality (aka Harary Graph Centrality).")); layoutNodeSizeProminence_EC_Act->setWhatsThis( tr("Eccentricity Centrality (EC) NodeSizes Layout\n\n" "Changes the size of all nodes to be " "proportional to their Eccentricity Centrality (aka Harary Graph Centrality) score. " "Nodes having higher EC will appear bigger.")); connect(layoutNodeSizeProminence_EC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_PC_Act = new QAction(tr("Power Centrality"), this); layoutNodeSizeProminence_PC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_S, Qt::CTRL | Qt::Key_7)); layoutNodeSizeProminence_PC_Act->setStatusTip( tr("Resize all nodes to be " "proportional to their Power Centrality.")); layoutNodeSizeProminence_PC_Act->setWhatsThis( tr("Power Centrality (PC) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their Power Centrality score. " "Nodes having higher PC will appear bigger.")); connect(layoutNodeSizeProminence_PC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_IC_Act = new QAction(tr("Information Centrality"), this); layoutNodeSizeProminence_IC_Act->setEnabled(true); layoutNodeSizeProminence_IC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_S, Qt::CTRL | Qt::Key_8)); layoutNodeSizeProminence_IC_Act->setStatusTip( tr("Resize all nodes to be " "proportional to their Information Centrality.")); layoutNodeSizeProminence_IC_Act->setWhatsThis( tr("Information Centrality (IC) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their Information Centrality score. " "Nodes having higher IC will appear bigger.")); connect(layoutNodeSizeProminence_IC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_EVC_Act = new QAction(tr("Eigenvector Centrality"), this); layoutNodeSizeProminence_EVC_Act->setEnabled(true); layoutNodeSizeProminence_EVC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_S, Qt::CTRL | Qt::Key_9)); layoutNodeSizeProminence_EVC_Act->setStatusTip( tr("Resize all nodes to be " "proportional to their Eigenvector Centrality.")); layoutNodeSizeProminence_EVC_Act->setWhatsThis( tr("Eigenvector Centrality (EVC) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their Eigenvector Centrality score. " "Nodes having higher EVC will appear bigger.")); connect(layoutNodeSizeProminence_EVC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_KATZ_Act = new QAction(tr("Katz Centrality"), this); layoutNodeSizeProminence_KATZ_Act->setEnabled(true); layoutNodeSizeProminence_KATZ_Act->setStatusTip( tr("Resize all nodes to be " "proportional to their Katz Centrality.")); layoutNodeSizeProminence_KATZ_Act->setWhatsThis( tr("Katz Centrality (KC) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their Katz Centrality score. " "Nodes having higher KC will appear bigger. " "Requires Katz Centrality to have been computed first " "(Analyze > Centrality > Katz Centrality).")); connect(layoutNodeSizeProminence_KATZ_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_BPC_Act = new QAction(tr("Bonacich Power Centrality"), this); layoutNodeSizeProminence_BPC_Act->setEnabled(true); layoutNodeSizeProminence_BPC_Act->setStatusTip( tr("Resize all nodes to be " "proportional to their Bonacich Power Centrality.")); layoutNodeSizeProminence_BPC_Act->setWhatsThis( tr("Bonacich Power Centrality (BPC) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their Bonacich Power Centrality score. " "Nodes having higher BPC will appear bigger. " "Requires Bonacich Power Centrality to have been computed first " "(Analyze > Centrality > Bonacich Power Centrality).")); connect(layoutNodeSizeProminence_BPC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_DP_Act = new QAction(tr("Degree Prestige"), this); layoutNodeSizeProminence_DP_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_S, Qt::CTRL | Qt::Key_I)); layoutNodeSizeProminence_DP_Act->setStatusTip( tr("Resize all nodes to be " "proportional to their Degree Prestige.")); layoutNodeSizeProminence_DP_Act->setWhatsThis( tr("Degree Prestige (DP) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their Degree Prestige score. " "Nodes having higher DP will appear bigger.")); connect(layoutNodeSizeProminence_DP_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_PRP_Act = new QAction(tr("PageRank Prestige"), this); layoutNodeSizeProminence_PRP_Act->setEnabled(true); layoutNodeSizeProminence_PRP_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_S, Qt::CTRL | Qt::Key_K)); layoutNodeSizeProminence_PRP_Act->setStatusTip( tr("Resize all nodes to be " "proportional to their PageRank Prestige.")); layoutNodeSizeProminence_PRP_Act->setWhatsThis( tr("PageRank Prestige (PRP) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their PageRank Prestige score. " "Nodes having higher PRP will appear bigger.")); connect(layoutNodeSizeProminence_PRP_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeSizeProminence_PP_Act = new QAction(tr("Proximity Prestige"), this); layoutNodeSizeProminence_PP_Act->setEnabled(true); layoutNodeSizeProminence_PP_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_S, Qt::CTRL | Qt::Key_Y)); layoutNodeSizeProminence_PP_Act->setStatusTip( tr("Resize all nodes to be " "proportional to their Proximity Prestige.")); layoutNodeSizeProminence_PP_Act->setWhatsThis( tr("Proximity Prestige (PP) Node Size Layout\n\n" "Changes the size of all nodes to be " "proportional to their Proximity Prestige score. " "Nodes having higher PP will appear bigger.")); connect(layoutNodeSizeProminence_PP_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeSizeByProminenceIndex())); layoutNodeColorProminence_DC_Act = new QAction(tr("Degree Centrality"), this); layoutNodeColorProminence_DC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_1)); layoutNodeColorProminence_DC_Act ->setStatusTip( tr("Change the color of all nodes to " "reflect their Degree Centrality.")); layoutNodeColorProminence_DC_Act->setWhatsThis( tr("Degree Centrality (DC) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their DC (inDegree) score. \n\n" "Nodes having higher DC will have warmer color (i.e. red).")); connect(layoutNodeColorProminence_DC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_CC_Act = new QAction(tr("Closeness Centrality"), this); layoutNodeColorProminence_CC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_2)); layoutNodeColorProminence_CC_Act ->setStatusTip( tr("Change the color of all nodes to " "reflect their Closeness Centrality.")); layoutNodeColorProminence_CC_Act->setWhatsThis( tr("Closeness Centrality (CC) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their CC score. " "Nodes of higher CC will have warmer color (i.e. red).\n\n" "This layout can be computed only for connected graphs. ")); connect(layoutNodeColorProminence_CC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_IRCC_Act = new QAction( tr("Influence Range Closeness Centrality"), this); layoutNodeColorProminence_IRCC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_3)); layoutNodeColorProminence_IRCC_Act ->setStatusTip( tr("Change the color of all nodes to proportional " "to their Influence Range Closeness Centrality.")); layoutNodeColorProminence_IRCC_Act->setWhatsThis( tr("Influence Range Closeness Centrality (IRCC) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their IRCC score. " "Nodes having higher IRCC will have warmer color (i.e. red).\n\n" "This layout can be computed for not connected graphs. ")); connect(layoutNodeColorProminence_IRCC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_BC_Act = new QAction(tr("Betweenness Centrality"), this); layoutNodeColorProminence_BC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_4)); layoutNodeColorProminence_BC_Act->setStatusTip( tr("Change the color of all nodes to " "reflect their Betweenness Centrality.")); layoutNodeColorProminence_BC_Act->setWhatsThis( tr("Betweenness Centrality (BC) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their Betweenness Centrality score. " "Nodes having higher BC will have warmer color (i.e. red).")); connect(layoutNodeColorProminence_BC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_SC_Act = new QAction(tr("Stress Centrality"), this); layoutNodeColorProminence_SC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_5)); layoutNodeColorProminence_SC_Act->setStatusTip( tr("Change the color of all nodes to " "reflect their Stress Centrality.")); layoutNodeColorProminence_SC_Act->setWhatsThis( tr("Stress Centrality (SC) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their Stress Centrality score. " "Nodes having higher SC will have warmer color (i.e. red).")); connect(layoutNodeColorProminence_SC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_EC_Act = new QAction(tr("Eccentricity Centrality"), this); layoutNodeColorProminence_EC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_6)); layoutNodeColorProminence_EC_Act->setStatusTip( tr("Change the color of all nodes to " "reflect their Eccentricity Centrality (aka Harary Graph Centrality).")); layoutNodeColorProminence_EC_Act->setWhatsThis( tr("Eccentricity Centrality (EC) NodeColors Layout\n\n" "Changes the color of all nodes to " "reflect their Eccentricity Centrality (aka Harary Graph Centrality) score. " "Nodes having higher EC will have warmer color (i.e. red).")); connect(layoutNodeColorProminence_EC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_PC_Act = new QAction(tr("Power Centrality"), this); layoutNodeColorProminence_PC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_7)); layoutNodeColorProminence_PC_Act->setStatusTip( tr("Change the color of all nodes to " "reflect their Power Centrality.")); layoutNodeColorProminence_PC_Act->setWhatsThis( tr("Power Centrality (PC) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their Power Centrality score. " "Nodes having higher PC will have warmer color (i.e. red).")); connect(layoutNodeColorProminence_PC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_IC_Act = new QAction(tr("Information Centrality"), this); layoutNodeColorProminence_IC_Act->setEnabled(true); layoutNodeColorProminence_IC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_8)); layoutNodeColorProminence_IC_Act->setStatusTip( tr("Change the color of all nodes to " "reflect their Information Centrality.")); layoutNodeColorProminence_IC_Act->setWhatsThis( tr("Information Centrality (IC) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their Information Centrality score. " "Nodes having higher IC will have warmer color (i.e. red).")); connect(layoutNodeColorProminence_IC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_EVC_Act = new QAction(tr("Eigenvector Centrality"), this); layoutNodeColorProminence_EVC_Act->setEnabled(true); layoutNodeColorProminence_EVC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_9)); layoutNodeColorProminence_EVC_Act->setStatusTip( tr("Change the color of all nodes to " "reflect their Eigenvector Centrality.")); layoutNodeColorProminence_EVC_Act->setWhatsThis( tr("Eigenvector Centrality (EVC) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their Eigenvector Centrality score. " "Nodes having higher EVC will have warmer color (i.e. red).")); connect(layoutNodeColorProminence_EVC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_KATZ_Act = new QAction(tr("Katz Centrality"), this); layoutNodeColorProminence_KATZ_Act->setEnabled(true); layoutNodeColorProminence_KATZ_Act->setStatusTip( tr("Change the color of all nodes to " "reflect their Katz Centrality.")); layoutNodeColorProminence_KATZ_Act->setWhatsThis( tr("Katz Centrality (KC) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their Katz Centrality score. " "Nodes having higher KC will have warmer color (i.e. red). " "Requires Katz Centrality to have been computed first " "(Analyze > Centrality > Katz Centrality).")); connect(layoutNodeColorProminence_KATZ_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_BPC_Act = new QAction(tr("Bonacich Power Centrality"), this); layoutNodeColorProminence_BPC_Act->setEnabled(true); layoutNodeColorProminence_BPC_Act->setStatusTip( tr("Change the color of all nodes to " "reflect their Bonacich Power Centrality.")); layoutNodeColorProminence_BPC_Act->setWhatsThis( tr("Bonacich Power Centrality (BPC) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their Bonacich Power Centrality score. " "Nodes having higher BPC will have warmer color (i.e. red). " "Requires Bonacich Power Centrality to have been computed first " "(Analyze > Centrality > Bonacich Power Centrality).")); connect(layoutNodeColorProminence_BPC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_DP_Act = new QAction(tr("Degree Prestige"), this); layoutNodeColorProminence_DP_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_I)); layoutNodeColorProminence_DP_Act->setStatusTip( tr("Change the color of all nodes to " "reflect their Degree Prestige.")); layoutNodeColorProminence_DP_Act->setWhatsThis( tr("Degree Prestige (DP) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their Degree Prestige score. " "Nodes having higher DP will have warmer color (i.e. red).")); connect(layoutNodeColorProminence_DP_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_PRP_Act = new QAction(tr("PageRank Prestige"), this); layoutNodeColorProminence_PRP_Act->setEnabled(true); layoutNodeColorProminence_PRP_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_K)); layoutNodeColorProminence_PRP_Act->setStatusTip( tr("Change the color of all nodes to " "reflect their PageRank Prestige.")); layoutNodeColorProminence_PRP_Act->setWhatsThis( tr("PageRank Prestige (PRP) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their PageRank Prestige score. " "Nodes having higher PRP will have warmer color (i.e. red).")); connect(layoutNodeColorProminence_PRP_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_PP_Act = new QAction(tr("Proximity Prestige"), this); layoutNodeColorProminence_PP_Act->setEnabled(true); layoutNodeColorProminence_PP_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_Y)); layoutNodeColorProminence_PP_Act->setStatusTip( tr("Change the color of all nodes to " "reflect their Proximity Prestige.")); layoutNodeColorProminence_PP_Act->setWhatsThis( tr("Proximity Prestige (PP) Node Color Layout\n\n" "Changes the color of all nodes to " "reflect their PageRank Prestige score. " "Nodes of higher PP will have warmer color (i.e. red).")); connect(layoutNodeColorProminence_PP_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorProminence_CLC_Act = new QAction(tr("Clustering Coefficient"), this); layoutNodeColorProminence_CLC_Act->setEnabled(true); layoutNodeColorProminence_CLC_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_G)); layoutNodeColorProminence_CLC_Act->setStatusTip( tr("Change the color of all nodes to " "reflect their local Clustering Coefficient.")); layoutNodeColorProminence_CLC_Act->setWhatsThis( tr("Clustering Coefficient Node Color Layout\n\n" "Changes the color of all nodes to reflect their local " "Clustering Coefficient (Watts-Strogatz). " "Nodes with higher clustering will have warmer color (i.e. red).")); connect(layoutNodeColorProminence_CLC_Act, SIGNAL(triggered()), this, SLOT(slotLayoutNodeColorByProminenceIndex())); layoutNodeColorByComponentAct = new QAction(tr("Node Color by Connected Component"), this); layoutNodeColorByComponentAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_C, Qt::CTRL | Qt::Key_0)); layoutNodeColorByComponentAct->setStatusTip( tr("Color nodes by their weakly connected component.")); layoutNodeColorByComponentAct->setWhatsThis( tr("Node Color by Connected Component\n\n" "Assigns a distinct color to each weakly connected component. " "All nodes in the same component share the same color, making " "isolated sub-networks immediately visible.")); connect(layoutNodeColorByComponentAct, &QAction::triggered, this, &MainWindow::slotLayoutNodeColorByComponent); layoutFDP_Eades_Act = new QAction(tr("Spring Embedder (Eades)"), this); layoutFDP_Eades_Act->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_E)); layoutFDP_Eades_Act->setStatusTip( tr("Layout Eades Spring-Gravitational model.")); layoutFDP_Eades_Act->setWhatsThis( tr("Spring Embedder Layout\n\n " "The Spring Embedder model (Eades, 1984), part of the " "Force Directed Placement (FDP) family, embeds a mechanical " "system in the graph by replacing nodes with rings and edges " "with springs. \n" "In our implementation, nodes are replaced by physical bodies " "(i.e. electrons) which exert repelling forces to each other, " "while edges are replaced by springs which exert attractive " "forces to the adjacent nodes. " "The nodes are placed in some initial layout and let go " "so that the spring forces move the system to a minimal energy state. " "The algorithm continues until the system retains an equilibrium state " "in which all forces cancel each other. ")); connect(layoutFDP_Eades_Act, SIGNAL(triggered(bool)), this, SLOT(slotLayoutSpringEmbedder())); layoutFDP_FR_Act = new QAction(tr("Fruchterman-Reingold"), this); layoutFDP_FR_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_D, Qt::CTRL | Qt::Key_F)); layoutFDP_FR_Act->setStatusTip( tr("Repelling forces between all nodes, and attracting forces between adjacent nodes.")); layoutFDP_FR_Act->setWhatsThis( tr("Fruchterman-Reingold Layout\n\n " "Embeds a layout all nodes according to a model in which repelling " "forces are used between every pair of nodes, while attracting " "forces are used only between adjacent nodes. " "The algorithm continues until the system retains its equilibrium " "state where all forces cancel each other.")); connect(layoutFDP_FR_Act, SIGNAL(triggered()), this, SLOT(slotLayoutFruchterman())); layoutFDP_KamadaKawai_Act = new QAction(tr("Kamada-Kawai"), this); layoutFDP_KamadaKawai_Act->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L, Qt::CTRL | Qt::Key_D, Qt::CTRL | Qt::Key_K)); layoutFDP_KamadaKawai_Act->setStatusTip( tr("Embeds the Kamada-Kawai FDP layout model, the best variant of the Spring Embedder family of models.")); layoutFDP_KamadaKawai_Act->setWhatsThis( tr( "

Kamada-Kawai

" "

The best variant of the Spring Embedder family of models. " "

In this the graph is considered to be a dynamic system where " "every edge is between two actors is a 'spring' of a desirable " "length, which corresponds to their graph theoretic distance.

" "

In this way, the optimal layout of the graph \n" "is the state with the minimum imbalance. The degree of " "imbalance is formulated as the total spring energy: " "the square summation of the differences between desirable " "distances and real ones for all pairs of vertices.

" )); connect(layoutFDP_KamadaKawai_Act, SIGNAL(triggered()), this, SLOT(slotLayoutKamadaKawai())); layoutGuidesAct = new QAction(QIcon(":/images/gridlines.png"), tr("Layout GuideLines"), this); layoutGuidesAct->setStatusTip(tr("Toggles layout guidelines on or off.")); layoutGuidesAct->setWhatsThis(tr("Layout Guidelines\n\n" "Layout Guidelines are circular or horizontal lines \n" "usually created when embedding prominence-based \n" "visualization models on the network.\n" "Disable this checkbox to hide guidelines")); layoutGuidesAct->setCheckable(true); layoutGuidesAct->setChecked(true); /** Analysis menu actions */ analyzeMatrixAdjInvertAct = new QAction( QIcon(":/images/invertmatrix.png"), tr("Invert Adjacency Matrix"), this); analyzeMatrixAdjInvertAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_M, Qt::CTRL | Qt::Key_I)); analyzeMatrixAdjInvertAct->setStatusTip(tr("Invert the adjacency matrix, if possible")); analyzeMatrixAdjInvertAct->setWhatsThis(tr("Invert Adjacency Matrix \n\n" "Inverts the adjacency matrix using linear algebra methods.")); connect(analyzeMatrixAdjInvertAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeMatrixAdjacencyInverse())); analyzeMatrixAdjTransposeAct = new QAction( QIcon(":/images/transposematrix.png"), tr("Transpose Adjacency Matrix"), this); analyzeMatrixAdjTransposeAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_M, Qt::CTRL | Qt::Key_T)); analyzeMatrixAdjTransposeAct->setStatusTip(tr("View the transpose of adjacency matrix")); analyzeMatrixAdjTransposeAct->setWhatsThis(tr("Transpose Adjacency Matrix \n\n" "Computes and displays the adjacency matrix tranpose.")); connect(analyzeMatrixAdjTransposeAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeMatrixAdjacencyTranspose())); analyzeMatrixAdjCocitationAct = new QAction( QIcon(":/images/cocitation.png"), tr("Cocitation Matrix"), this); analyzeMatrixAdjCocitationAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_M, Qt::CTRL | Qt::Key_C)); analyzeMatrixAdjCocitationAct->setStatusTip(tr("Compute the Cocitation matrix of this network.")); analyzeMatrixAdjCocitationAct->setWhatsThis(tr("Cocitation Matrix \n\n " "Computes and displays the cocitation matrix of the network. " "The Cocitation matrix, C=A*A^T, is a NxN matrix where " "each element (i,j) is the number of actors that have " "outbound ties/links to both actors i and j. ")); connect(analyzeMatrixAdjCocitationAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeMatrixAdjacencyCocitation())); analyzeMatrixDegreeAct = new QAction( QIcon(":/images/degreematrix.png"), tr("Degree Matrix"), this); analyzeMatrixDegreeAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_M, Qt::CTRL | Qt::Key_D)); analyzeMatrixDegreeAct->setStatusTip(tr("Compute the Degree matrix of the network")); analyzeMatrixDegreeAct->setWhatsThis(tr("Degree Matrix " "\n\n Compute the Degree matrix of the network.")); connect(analyzeMatrixDegreeAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeMatrixDegree())); analyzeMatrixLaplacianAct = new QAction( QIcon(":/images/laplacian.png"), tr("Laplacian Matrix"), this); analyzeMatrixLaplacianAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_M, Qt::CTRL | Qt::Key_L)); analyzeMatrixLaplacianAct->setStatusTip(tr("Compute the Laplacian matrix of the network")); analyzeMatrixLaplacianAct->setWhatsThis(tr("Laplacian Matrix \n\n" "Compute the Laplacian matrix of the network.")); connect(analyzeMatrixLaplacianAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeMatrixLaplacian())); analyzeGraphReciprocityAct = new QAction( QIcon(":/images/symmetry-edge.png"), tr("Reciprocity"), this); analyzeGraphReciprocityAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_R)); analyzeGraphReciprocityAct->setStatusTip(tr("Compute the arc and dyad reciprocity of the network.")); analyzeGraphReciprocityAct->setWhatsThis( tr("Arc and Dyad Reciprocity\n\n" "The arc reciprocity of a network/graph is the fraction of " "reciprocated ties over all present ties of the graph. \n" "The dyad reciprocity of a network/graph is the fraction of " "actor pairs that have reciprocated ties over all connected " "pairs of actors. \n" "In a directed network, the arc reciprocity measures the proportion " "of directed edges that are bidirectional. If the reciprocity is 1, \n" "then the adjacency matrix is structurally symmetric. \n" "Likewise, in a directed network, the dyad reciprocity measures " "the proportion of connected actor dyads that have bidirectional ties " "between them. \n" "In an undirected graph, all edges are reciprocal. Thus the " "reciprocity of the graph is always 1. \n" "Reciprocity can be computed on undirected, directed, and weighted graphs.")); connect(analyzeGraphReciprocityAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeReciprocity())); analyzeGraphSymmetryAct = new QAction( QIcon(":/images/symmetry_48px.svg"), tr("Symmetry Test"), this); analyzeGraphSymmetryAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_S)); analyzeGraphSymmetryAct->setStatusTip(tr("Check whether the network is symmetric or not")); analyzeGraphSymmetryAct->setWhatsThis( tr("Symmetry\n\n" "Checks whether the network is symmetric or not. \n" "A network is symmetric when all edges are reciprocal, or, " "in mathematical language, when the adjacency matrix is " "symmetric.")); connect(analyzeGraphSymmetryAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeSymmetryCheck())); analyzeGraphDistanceAct = new QAction( QIcon(":/images/distance.png"), tr("Geodesic Distance between 2 nodes"), this); analyzeGraphDistanceAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_G)); analyzeGraphDistanceAct->setStatusTip( tr("Compute the length of the shortest path (geodesic distance) between 2 nodes.")); analyzeGraphDistanceAct->setWhatsThis( tr("Distance\n\n" "Computes the geodesic distance between two nodes." "In graph theory, the geodesic distance of two " "nodes is the length (number of edges) of the shortest path " "between them.")); connect(analyzeGraphDistanceAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeDistance())); analyzeMatrixDistancesGeodesicAct = new QAction(QIcon(":/images/dm.png"), tr("Geodesic Distances Matrix"), this); analyzeMatrixDistancesGeodesicAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_M)); analyzeMatrixDistancesGeodesicAct->setStatusTip( tr("Compute the matrix of geodesic distances between all pair of nodes.")); analyzeMatrixDistancesGeodesicAct->setWhatsThis( tr("Distances Matrix\n\n" "Computes the matrix of distances between all " "pairs of actors/nodes in the social network." "A distances matrix is a n x n matrix, in which the " "(i,j) element is the distance from node i to node j" "The distance of two nodes is the length of the shortest path between them.")); connect(analyzeMatrixDistancesGeodesicAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeMatrixDistances())); analyzeMatrixGeodesicsAct = new QAction(QIcon(":/images/dm.png"), tr("Geodesics Matrix"), this); analyzeMatrixGeodesicsAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_P)); analyzeMatrixGeodesicsAct->setStatusTip(tr("Compute the number of shortest paths (geodesics) between each pair of nodes ")); analyzeMatrixGeodesicsAct->setWhatsThis( tr( "Geodesics Matrix\n\n" "Displays a n x n matrix, where the (i,j) element " "is the number of shortest paths (geodesics) between " "node i and node j. ")); connect(analyzeMatrixGeodesicsAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeMatrixGeodesics())); analyzeGraphDiameterAct = new QAction(QIcon(":/images/diameter_48px.svg"), tr("Graph Diameter"), this); analyzeGraphDiameterAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_D)); analyzeGraphDiameterAct->setStatusTip(tr("Compute the diameter of the network, " "the maximum geodesic distance between any actors.")); analyzeGraphDiameterAct->setWhatsThis(tr("Diameter\n\n " "The diameter of a network is the maximum geodesic distance " "(maximum shortest path length) between any two nodes of the network.")); connect(analyzeGraphDiameterAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeDiameter())); averGraphDistanceAct = new QAction(QIcon(":/images/avdistance.png"), tr("Average Distance"), this); averGraphDistanceAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_A)); averGraphDistanceAct->setStatusTip(tr("Compute the average graph distance for all possible pairs of nodes.")); averGraphDistanceAct->setWhatsThis( tr("Average Graph Distance\n\n " "This is the average length of shortest paths (geodesics) " "for all possible pairs of nodes. " "It is a measure of the efficiency or compactness of the network.")); connect(averGraphDistanceAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeDistanceAverage())); analyzeGeodesicDistributionAct = new QAction(tr("Geodesic Distance Distribution"), this); analyzeGeodesicDistributionAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_I)); analyzeGeodesicDistributionAct->setStatusTip( tr("Show the distribution of geodesic distances across all pairs of nodes.")); analyzeGeodesicDistributionAct->setWhatsThis( tr("Geodesic Distance Distribution\n\n" "Counts how many ordered pairs of nodes are separated by each " "geodesic distance d (d = 1, 2, …, diameter). " "Useful for understanding the shape of the network's distance structure β€” " "e.g. whether most pairs are at distance 2 (\"small world\") " "or spread across many distances.")); connect(analyzeGeodesicDistributionAct, &QAction::triggered, this, &MainWindow::slotAnalyzeGeodesicDistribution); analyzeGraphEccentricityAct = new QAction(QIcon(":/images/eccentricity.png"), tr("Eccentricity"), this); analyzeGraphEccentricityAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_E)); analyzeGraphEccentricityAct->setStatusTip(tr("Compute the Eccentricity of each actor and group Eccentricity")); analyzeGraphEccentricityAct->setWhatsThis(tr("Eccentricity\n\n" "The eccentricity of each node i in a network " "or graph is the largest geodesic distance " "between node i and any other node j. " "Therefore, it reflects how far, at most, " "is each node from every other node. \n" "The maximum eccentricity is the graph diameter " "while the minimum is the graph radius.\n" "This index can be calculated in both graphs " "and digraphs but is usually best suited " "for undirected graphs. \n" "It can also be calculated in weighted graphs " "although the weight of each edge (v,u) in E is " "always considered to be 1.")); connect(analyzeGraphEccentricityAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeEccentricity())); analyzeGraphConnectednessAct = new QAction(QIcon(":/images/distance.png"), tr("Connectedness"), this); analyzeGraphConnectednessAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_C)); analyzeGraphConnectednessAct->setStatusTip(tr("Check whether the network is a connected " "graph, a connected digraph or " "a disconnected graph/digraph...")); analyzeGraphConnectednessAct->setWhatsThis(tr("Connectedness\n\n In graph theory, a " "graph is connected if there is a " "path between every pair of nodes. \n" "A digraph is strongly connected " "if there the a path from i to j and " "from j to i for all pairs (i,j).\n" "A digraph is weakly connected if at least " "a pair of nodes are joined by a semipath.\n" "A digraph or a graph is disconnected if " "at least one node is isolate.")); connect(analyzeGraphConnectednessAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeConnectedness())); analyzeNodeConnectivityAct = new QAction(QIcon(":/images/distance.png"), tr("Node Connectivity"), this); analyzeNodeConnectivityAct->setStatusTip( tr("Compute the minimum number of nodes that must be removed to disconnect two nodes.")); analyzeNodeConnectivityAct->setWhatsThis( tr("Node Connectivity\n\n" "The local vertex connectivity of two nodes s and t is the minimum number " "of other nodes that must be removed to disconnect t from s.\n" "If s and t are directly connected by an edge, no such removal exists, " "since the edge itself always keeps them connected.\n" "For directed networks, asks whether to respect edge direction (strong) " "or ignore it (weak).")); connect(analyzeNodeConnectivityAct, &QAction::triggered, this, &MainWindow::slotAnalyzeNodeConnectivity); analyzeConnectivityAct = new QAction(QIcon(":/images/avdistance.png"), tr("Graph Connectivity"), this); analyzeConnectivityAct->setStatusTip( tr("Compute the network's overall vertex connectivity.")); analyzeConnectivityAct->setWhatsThis( tr("Graph Connectivity\n\n" "The vertex connectivity of the whole network is the minimum, over every pair " "of nodes, of their local vertex connectivity - i.e. the fewest nodes that " "would need to be removed to disconnect the network at its weakest point.\n" "For directed networks, asks whether to respect edge direction (strong) " "or ignore it (weak).")); connect(analyzeConnectivityAct, &QAction::triggered, this, &MainWindow::slotAnalyzeConnectivity); analyzeGraphWalksAct = new QAction(QIcon(":/images/walk.png"), tr("Walks of a given length"), this); analyzeGraphWalksAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_W)); analyzeGraphWalksAct->setStatusTip(tr("Compute the number of walks of a given length between any nodes.")); analyzeGraphWalksAct->setWhatsThis(tr("Walks of a given length\n\n" "A walk is a sequence of alternating vertices and edges " "such as v0e1, v1e2, " "v2e3, …, ekvk, " "where each edge, ei is defined as " "ei = {vi-1, vi}. " "This function counts the number of walks of a given " "length between each pair of nodes, by studying the powers of the sociomatrix.\n")); connect(analyzeGraphWalksAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeWalksLength())); analyzeGraphWalksTotalAct = new QAction(QIcon(":/images/walk.png"), tr("Total Walks"), this); analyzeGraphWalksTotalAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_T)); analyzeGraphWalksTotalAct->setStatusTip(tr("Calculate the total number of walks of every possible length between all nodes")); analyzeGraphWalksTotalAct->setWhatsThis(tr("Total Walks\n\n" "A walk is a sequence of alternating vertices " "and edges such as v0e1, " "v1e2, v2e3, …, " "ekvk, where each edge, ei " "is defined as ei = {vi-1, vi}. " "This function counts the number of walks of any length " "between each pair of nodes, by studying the powers of the sociomatrix. \n")); connect(analyzeGraphWalksTotalAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeWalksTotal())); analyzeMatrixReachabilityAct = new QAction(QIcon(":/images/walk.png"), tr("Reachability Matrix"), this); analyzeMatrixReachabilityAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_M, Qt::CTRL | Qt::Key_R)); analyzeMatrixReachabilityAct->setStatusTip(tr("Compute the Reachability Matrix of the network.")); analyzeMatrixReachabilityAct->setWhatsThis(tr("Reachability Matrix\n\n" "Calculates the reachability matrix XR of " "the graph where the {i,j} element is 1 if " "the vertices i and j are reachable. \n\n" "Actually, this just checks whether the corresponding element " "of Distances matrix is not zero.\n")); connect(analyzeMatrixReachabilityAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeReachabilityMatrix())); clusteringCoefAct = new QAction(QIcon(":/images/clucof.png"), tr("Local and Network Clustering Coefficient"), this); clusteringCoefAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_G, Qt::CTRL | Qt::Key_L)); clusteringCoefAct->setStatusTip(tr("Compute the Watts & Strogatz Clustering Coefficient for every actor and the network average.")); clusteringCoefAct->setWhatsThis(tr("Local and Network Clustering Coefficient\n\n" "The local Clustering Coefficient (Watts & Strogatz, 1998) " "of an actor quantifies how close " "the actor and her neighbors are to being a clique and " "can be used as an indication of network transitivity. \n")); connect(clusteringCoefAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeClusteringCoefficient())); analyzeCommunitiesCliquesAct = new QAction(QIcon(":/images/clique.png"), tr("Clique Census"), this); analyzeCommunitiesCliquesAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_U, Qt::CTRL | Qt::Key_C)); analyzeCommunitiesCliquesAct->setStatusTip(tr("Compute the clique census: find all maximal connected subgraphs.")); analyzeCommunitiesCliquesAct->setWhatsThis(tr("Clique Census\n\n" "Produces the census of network cliques (maximal connected subgraphs), " "along with disaggregation by actor and co-membership information. ")); connect(analyzeCommunitiesCliquesAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCommunitiesCliqueCensus())); analyzeCommunitiesTriadCensusAct = new QAction(QIcon(":/images/triad.png"), tr("Triad Census (M-A-N labeling)"), this); analyzeCommunitiesTriadCensusAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_U, Qt::CTRL | Qt::Key_T)); analyzeCommunitiesTriadCensusAct->setStatusTip(tr("Calculate the triad census for all actors.")); analyzeCommunitiesTriadCensusAct->setWhatsThis(tr("Triad Census\n\n" "A triad census counts all the different kinds of observed triads " "within a network and codes them according to their number of mutual, " "asymmetric and non-existent dyads using the M-A-N labeling scheme. \n")); connect(analyzeCommunitiesTriadCensusAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCommunitiesTriadCensus())); analyzeStrEquivalencePearsonAct = new QAction(QIcon(":/images/similarity.png"), tr("Pearson correlation coefficients"), this); analyzeStrEquivalencePearsonAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_T, Qt::CTRL | Qt::Key_P)); analyzeStrEquivalencePearsonAct->setStatusTip( tr("Compute Pearson Correlation Coefficients between pairs of actors. " "Most useful with valued/weighted ties (non-binary). ")); analyzeStrEquivalencePearsonAct->setWhatsThis( tr("Pearson correlation coefficients\n\n" "Computes a correlation matrix, where the elements are the " "Pearson correlation coefficients between pairs of actors " "in terms of their tie profiles or distances (in, out or both). \n\n" "The Pearson product-moment correlation coefficient (PPMCC or PCC or Pearson's r)" "is a measure of the linear dependence/association between two variables X and Y. \n\n" "This correlation measure of similarity is particularly useful " "when ties are valued/weighted denoting strength, cost or probability.\n\n" "Note that in very sparse networks (very low density), measures such as" "\"exact matches\", \"correlation\" and \"distance\" " "will show little variation among the actors, causing " "difficulty in classifying the actors in structural equivalence classes.")); connect(analyzeStrEquivalencePearsonAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeStrEquivalencePearsonDialog())); analyzeStrEquivalenceMatchesAct = new QAction(QIcon(":/images/similarity.png"), tr("Similarity by measure (Exact, Jaccard, Hamming, Cosine, Euclidean)"), this); analyzeStrEquivalenceMatchesAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_T, Qt::CTRL | Qt::Key_E)); analyzeStrEquivalenceMatchesAct->setStatusTip(tr("Compute a pair-wise actor similarity " "matrix based on a measure of their ties (or distances) \"matches\" .")); analyzeStrEquivalenceMatchesAct->setWhatsThis( tr("Actor Similarity by measure\n\n" "Computes a pair-wise actor similarity matrix, where each element (i,j) is " "the ratio of tie (or distance) matches of actors i and j to all other actors. \n\n" "SocNetV supports the following matching measures: " "Simple Matching (Exact Matches)" "Jaccard Index (Positive Matches or Co-citation)" "Hamming distance" "Cosine similarity" "Euclidean distance" "For instance, if you select Exact Matches, a matrix element (i,j) = 0.5, " "means that actors i and j have the same ties present or absent " "to other actors 50% of the time. \n\n" "These measures of similarity are particularly useful " "when ties are binary (not valued).\n\n" "Note that in very sparse networks (very low density), measures such as" "\"exact matches\", \"correlation\" and \"distance\" " "will show little variation among the actors, causing " "difficulty in classifying the actors in structural equivalence classes.")); connect(analyzeStrEquivalenceMatchesAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeStrEquivalenceSimilarityMeasureDialog())); analyzeStrEquivalenceTieProfileDissimilaritiesAct = new QAction(QIcon(":/images/dm.png"), tr("Tie Profile Dissimilarities/Distances"), this); analyzeStrEquivalenceTieProfileDissimilaritiesAct->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_T, Qt::CTRL | Qt::Key_T)); analyzeStrEquivalenceTieProfileDissimilaritiesAct->setStatusTip( tr("Compute tie profile dissimilarities/distances " "(Euclidean, Manhattan, Jaccard, Hamming) between all pair of nodes.")); analyzeStrEquivalenceTieProfileDissimilaritiesAct->setWhatsThis( tr("Tie Profile Dissimilarities/Distances\n\n" "Computes a matrix of tie profile distances/dissimilarities " "between all pairs of actors/nodes in the social network " "using an ordinary metric such as Euclidean distance, " "Manhattan distance, Jaccard distance or Hamming distance)." "The resulted distance matrix is a n x n matrix, in which the " "(i,j) element is the distance or dissimilarity between " "the tie profiles of node i and node j.")); connect(analyzeStrEquivalenceTieProfileDissimilaritiesAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeStrEquivalenceDissimilaritiesDialog())); analyzeStrEquivalenceClusteringHierarchicalAct = new QAction(QIcon(":/images/hierarchical.png"), tr("Hierarchical clustering"), this); analyzeStrEquivalenceClusteringHierarchicalAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_T, Qt::CTRL | Qt::Key_H)); analyzeStrEquivalenceClusteringHierarchicalAct->setStatusTip( tr("Perform agglomerative cluster analysis of the actors in the social network")); analyzeStrEquivalenceClusteringHierarchicalAct->setWhatsThis( tr("Hierarchical clustering\n\n" "Hierarchical clustering (or hierarchical cluster analysis, HCA) " "is a method of cluster analysis which builds a hierarchy " "of clusters, based on their elements dissimilarity. " "In SNA context these clusters usually consist of " "network actors. \n" "This method takes the social network distance matrix as input and uses " "the Agglomerative \"bottom up\" approach where each " "actor starts in its own cluster (Level 0). In each subsequent Level, " "as we move up the clustering hierarchy, a pair of clusters " "are merged into a larger cluster, until " "all actors end up in the same cluster. " "To decide which clusters should be combined at each level, a measure of " "dissimilarity between sets of observations is required. " "This measure consists of a metric for the distance between actors " "(i.e. manhattan distance) and a linkage criterion (i.e. single-linkage clustering). " "This linkage criterion (essentially a definition of distance between clusters), " "differentiates between the different HCA methods." "Note that the complexity of agglomerative clustering is O( n^2 log(n) ), " "therefore is too slow for large data sets.")); connect(analyzeStrEquivalenceClusteringHierarchicalAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeStrEquivalenceClusteringHierarchicalDialog())); cDegreeAct = new QAction(tr("Degree Centrality (DC)"), this); cDegreeAct->setShortcut(Qt::CTRL | Qt::Key_1); cDegreeAct ->setStatusTip(tr("Compute Degree Centrality indices for every actor and group Degree Centralization.")); cDegreeAct ->setWhatsThis( tr("Degree Centrality (DC)\n\n" "For each node v, the DC index is the number of edges " "attached to it (in undirected graphs) or the total number " "of arcs (outLinks) starting from it (in digraphs).\n" "This is often considered a measure of actor activity. \n\n" "This index can be calculated in both graphs and digraphs " "but is usually best suited for undirected graphs. " "It can also be calculated in weighted graphs. " "In weighted relations, DC is the sum of weights of all " "edges/outLinks attached to v.")); connect(cDegreeAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCentralityDegree())); cClosenessAct = new QAction(tr("Closeness Centrality (CC)"), this); cClosenessAct->setShortcut(Qt::CTRL | Qt::Key_2); cClosenessAct ->setStatusTip( tr( "Compute Closeness Centrality indices for every actor and group Closeness Centralization.")); cClosenessAct ->setWhatsThis( tr("Closeness Centrality (CC)\n\n" "For each node v, CC the inverse sum of " "the shortest distances between v and every other node. CC is " "interpreted as the ability to access information through the " "\"grapevine\" of network members. Nodes with high closeness " "centrality are those who can reach many other nodes in few steps. " "\n\nThis index can be calculated in both graphs and digraphs. " "It can also be calculated in weighted graphs although the weight of " "each edge (v,u) in E is always considered to be 1. ")); connect(cClosenessAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCentralityCloseness())); cInfluenceRangeClosenessAct = new QAction(tr("Influence Range Closeness Centrality (IRCC)"), this); cInfluenceRangeClosenessAct->setShortcut(Qt::CTRL | Qt::Key_3); cInfluenceRangeClosenessAct ->setStatusTip( tr("Compute Influence Range Closeness Centrality indices for every actor " "focusing on how proximate each one is" "to others in its influence range")); cInfluenceRangeClosenessAct ->setWhatsThis( tr("Influence Range Closeness Centrality (IRCC)\n\n" "For each node v, IRCC is the standardized inverse average distance " "between v and every reachable node.\n" "This improved CC index is optimized for graphs and directed graphs which " "are not strongly connected. Unlike the ordinary CC, which is the inverted " "sum of distances from node v to all others (thus undefined if a node is isolated " "or the digraph is not strongly connected), IRCC considers only " "distances from node v to nodes in its influence range J (nodes reachable from v). " "The IRCC formula used is the ratio of the fraction of nodes reachable by v " "(|J|/(n-1)) to the average distance of these nodes from v (sum(d(v,j))/|J|")); connect(cInfluenceRangeClosenessAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCentralityClosenessIR())); cBetweennessAct = new QAction(tr("Betweenness Centrality (BC)"), this); cBetweennessAct->setShortcut(Qt::CTRL | Qt::Key_4); cBetweennessAct->setWhatsThis(tr("Betweenness Centrality (BC)\n\n" "For each node v, BC is the ratio of all geodesics between pairs of nodes which run through v. " "It reflects how often an node lies on the geodesics between the other nodes of the network. " "It can be interpreted as a measure of control. " "A node which lies between many others is assumed to have a higher likelihood of being able " "to control information flow in the network. \n\n" "Note that betweenness centrality assumes that all geodesics " "have equal weight or are equally likely to be chosen for the flow of information " "between any two nodes. This is reasonable only on \"regular\" networks where all " "nodes have similar degrees. On networks with significant degree variance you might want " "to try informational centrality instead. \n\nThis index can be calculated in both graphs " "and digraphs but is usually best suited for undirected graphs. It can also be calculated" " in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1.")); cBetweennessAct->setStatusTip(tr("Compute Betweenness Centrality indices and group Betweenness Centralization.")); connect(cBetweennessAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCentralityBetweenness())); cStressAct = new QAction(tr("Stress Centrality (SC)"), this); cStressAct->setShortcut(Qt::CTRL | Qt::Key_5); cStressAct->setStatusTip(tr("Compute Stress Centrality indices for every actor and group Stress Centralization.")); cStressAct->setWhatsThis(tr("Stress Centrality (SC)\n\n" "For each node v, SC is the total number of geodesics between all other nodes which run through v. " "A node with high SC is considered 'stressed', since it is traversed by a high number of geodesics. " "When one node falls on all other geodesics between all the remaining (N-1) nodes, " "then we have a star graph with maximum Stress Centrality. \n\n" "This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. " "It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1.")); connect(cStressAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCentralityStress())); cEccentAct = new QAction(tr("Eccentricity Centrality (EC)"), this); cEccentAct->setShortcut(Qt::CTRL | Qt::Key_6); cEccentAct->setStatusTip(tr("Compute Eccentricity Centrality (aka Harary Graph Centrality) scores for each node.")); cEccentAct->setWhatsThis( tr("Eccentricity Centrality (EC)\n\n " "This index is also known as Harary Graph Centrality. " "For each node i, " "the EC is the inverse of the maximum geodesic distance " "of that v to all other nodes in the network. \n" "Nodes with high EC have short distances to all other nodes " "This index can be calculated in both graphs and digraphs " "but is usually best suited for undirected graphs. " "It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1.")); connect(cEccentAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCentralityEccentricity())); cPowerAct = new QAction(tr("Gil and Schmidt Power Centrality (PC)"), this); cPowerAct->setShortcut(Qt::CTRL | Qt::Key_7); cPowerAct->setStatusTip(tr("Compute Power Centrality indices (aka Gil-Schmidt Power Centrality) for every actor and group Power Centralization")); cPowerAct->setWhatsThis(tr("Power Centrality (PC)\n\n " "For each node v, this index sums its degree (with weight 1), with the size of the 2nd-order neighbourhood (with weight 2), and in general, with the size of the kth order neighbourhood (with weight k). Thus, for each node in the network the most important other nodes are its immediate neighbours and then in decreasing importance the nodes of the 2nd-order neighbourhood, 3rd-order neighbourhood etc. For each node, the sum obtained is normalised by the total numbers of nodes in the same component minus 1. Power centrality has been devised by Gil-Schmidt. \n\nThis index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1 (therefore not considered).")); connect(cPowerAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCentralityPower())); cInformationAct = new QAction(tr("Information Centrality (IC)"), this); cInformationAct->setShortcut(Qt::CTRL | Qt::Key_8); cInformationAct->setEnabled(true); cInformationAct->setStatusTip(tr("Compute Information Centrality indices and group Information Centralization")); cInformationAct->setWhatsThis( tr("Information Centrality (IC)\n\n" "Information centrality counts all paths between " "nodes weighted by strength of tie and distance. " "This centrality measure developed by Stephenson and Zelen (1989) " "focuses on how information might flow through many different paths. \n\n" "This index should be calculated only for graphs. \n\n" "Note: To compute this index, SocNetV drops all isolated nodes.")); connect(cInformationAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCentralityInformation())); cEigenvectorAct = new QAction(tr("Eigenvector Centrality (EVC)"), this); cEigenvectorAct->setShortcut(Qt::CTRL | Qt::Key_9); cEigenvectorAct->setEnabled(true); cEigenvectorAct->setStatusTip(tr("Compute Eigenvector Centrality indices and group Eigenvector Centralization")); cEigenvectorAct->setWhatsThis( tr("Eigenvector Centrality (EVC)\n\n" "Computes the Eigenvector centrality of each node in a social network " "which is defined as the ith element of the leading eigenvector " "of the adjacency matrix. The leading eigenvector is the " "eigenvector corresponding to the largest positive eigenvalue." "The Eigenvector Centrality, proposed by Bonacich (1989), is " "an extension of the simpler Degree Centrality because it gives " "each actor a score proportional to the scores of its neighbors. " "Thus, a node may be important, in terms of its EC, because it " "has lots of ties or it has fewer ties to important other nodes.")); connect(cEigenvectorAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCentralityEigenvector())); cKatzAct = new QAction(tr("Katz Centrality (KC)"), this); cKatzAct->setEnabled(true); cKatzAct->setStatusTip(tr("Compute Katz Centrality indices")); cKatzAct->setWhatsThis( tr("Katz Centrality (KC)\n\n" "Computes the Katz centrality of each node: credit for indirect " "connections, not just direct ones, discounted the further away " "they are by an attenuation factor alpha that you choose. " "Proposed by Katz (1953), it generalizes Degree Centrality by " "counting walks of every length, not just length 1.")); connect(cKatzAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCentralityKatz())); cBonacichAct = new QAction(tr("Bonacich Power Centrality (BPC)"), this); cBonacichAct->setEnabled(true); cBonacichAct->setStatusTip(tr("Compute Bonacich Power Centrality indices")); cBonacichAct->setWhatsThis( tr("Bonacich Power Centrality (BPC)\n\n" "Like Katz Centrality, computes credit for indirect connections " "discounted the further away they are - but with a second " "parameter beta that you choose, which can be negative. With " "negative beta, being tied to well-connected others can hurt " "rather than help a node's score, and scores can come out " "negative - unlike every other measure in this app. Not to be " "confused with the existing Power Centrality (PC).")); connect(cBonacichAct, SIGNAL(triggered()), this, SLOT(slotAnalyzeCentralityBonacich())); cInDegreeAct = new QAction(tr("Degree Prestige (DP)"), this); cInDegreeAct->setStatusTip(tr("Compute Degree Prestige (InDegree) indices ")); cInDegreeAct->setShortcut(Qt::CTRL | Qt::Key_I); cInDegreeAct->setWhatsThis(tr("InDegree (Degree Prestige)\n\n" "For each node k, this the number of arcs ending at k. " "Nodes with higher in-degree are considered more prominent among others. " "In directed graphs, this index measures the prestige of each node/actor. " "Thus it is called Degree Prestige. " "Nodes who are prestigious tend to receive many nominations or choices (in-links). " "The largest the index is, the more prestigious is the node. \n\n" "This index can be calculated only for digraphs. " "In weighted relations, DP is the sum of weights of all arcs/inLinks ending at node v.")); connect(cInDegreeAct, SIGNAL(triggered()), this, SLOT(slotAnalyzePrestigeDegree())); cPageRankAct = new QAction(tr("PageRank Prestige (PRP)"), this); cPageRankAct->setShortcut(Qt::CTRL | Qt::Key_K); cPageRankAct->setEnabled(true); cPageRankAct->setStatusTip(tr("Compute PageRank Prestige indices for every actor")); cPageRankAct->setWhatsThis(tr("PageRank Prestige\n\n" "An importance ranking for each node based on the link structure of the network. " "PageRank, developed by Page and Brin (1997), focuses on how nodes are " "connected to each other, treating each edge from a node as a citation/backlink/vote to another. " "In essence, for each node PageRank counts all backlinks to it, " "but it does so by not counting all edges equally while it " "normalizes each edge from a node by the total number of edges from it. " "PageRank is calculated iteratively and it corresponds to the principal " "eigenvector of the normalized link matrix. \n\n" "This index can be calculated in both graphs and digraphs but is " "usually best suited for directed graphs since it is a prestige measure. " "It can also be calculated in weighted graphs. " "In weighted relations, each backlink to a node v from another node u is " "considered to have weight=1 but it is normalized by the sum of " "outLinks weights (outDegree) of u. Therefore, nodes with high outLink " "weights give smaller percentage of their PR to node v.")); connect(cPageRankAct, SIGNAL(triggered()), this, SLOT(slotAnalyzePrestigePageRank())); cProximityPrestigeAct = new QAction(tr("Proximity Prestige (PP)"), this); cProximityPrestigeAct->setShortcut(Qt::CTRL | Qt::Key_Y); cProximityPrestigeAct->setEnabled(true); cProximityPrestigeAct->setStatusTip(tr("Calculate and display Proximity Prestige (digraphs only)")); cProximityPrestigeAct ->setWhatsThis( tr("Proximity Prestige (PP) \n\n" "This index measures how proximate a node v is to the nodes " "in its influence domain I (the influence domain I of a node " "is the number of other nodes that can reach it).\n\n" "In PP calculation, proximity is based on distances to rather " "than distances from node v. \n" "To put it simply, in PP what matters is how close are all " "the other nodes to node v. \n\n" "The algorithm takes the average distance to node v of all " "nodes in its influence domain, standardizes it by " "multiplying with (N-1)/I and takes its reciprocal. " "In essence, the formula SocNetV uses to calculate PP " "is the ratio of the fraction of nodes that can reach node v, " "to the average distance of that nodes to v: \n" "PP = (I/(N-1))/(sum{d(u,v)}/I) \n" "where the sum is over all nodes in I.")); connect(cProximityPrestigeAct, SIGNAL(triggered()), this, SLOT(slotAnalyzePrestigeProximity())); /** Options menu actions */ optionsNodeNumbersVisibilityAct = new QAction(tr("Display Node Numbers"), this); optionsNodeNumbersVisibilityAct->setStatusTip( tr("Toggle displaying of node numbers")); optionsNodeNumbersVisibilityAct->setWhatsThis( tr("Display Node Numbers\n\n" "Enables or disables displaying of node numbers.")); optionsNodeNumbersVisibilityAct->setCheckable(true); optionsNodeNumbersVisibilityAct->setChecked( (appSettings["initNodeNumbersVisibility"] == "true") ? true : false); connect(optionsNodeNumbersVisibilityAct, SIGNAL(triggered(bool)), this, SLOT(slotOptionsNodeNumbersVisibility(bool))); optionsNodeNumbersInsideAct = new QAction(tr("Display Numbers Inside Nodes"), this); optionsNodeNumbersInsideAct->setStatusTip( tr("Toggle displaying of numbers inside nodes")); optionsNodeNumbersInsideAct->setWhatsThis( tr("Display Numbers Inside Nodes\n\n" "Enables or disables displaying node numbers inside nodes.")); optionsNodeNumbersInsideAct->setCheckable(true); optionsNodeNumbersInsideAct->setChecked( (appSettings["initNodeNumbersInside"] == "true") ? true : false); connect(optionsNodeNumbersInsideAct, SIGNAL(triggered(bool)), this, SLOT(slotOptionsNodeNumbersInside(bool))); optionsNodeLabelsVisibilityAct = new QAction(tr("Display Node Labels"), this); optionsNodeLabelsVisibilityAct->setStatusTip( tr("Toggle displaying of node labels")); optionsNodeLabelsVisibilityAct->setWhatsThis( tr("Display Node Labels\n\n" "Enables or disables node labels.")); optionsNodeLabelsVisibilityAct->setCheckable(true); optionsNodeLabelsVisibilityAct->setChecked( (appSettings["initNodeLabelsVisibility"] == "true") ? true : false); connect(optionsNodeLabelsVisibilityAct, SIGNAL(toggled(bool)), this, SLOT(slotOptionsNodeLabelsVisibility(bool))); optionsEdgesVisibilityAct = new QAction(tr("Display Edges"), this); optionsEdgesVisibilityAct->setStatusTip(tr("Toggle displaying edges")); optionsEdgesVisibilityAct->setWhatsThis( tr("Display Edges\n\n" "Enables or disables displaying of edges.")); optionsEdgesVisibilityAct->setCheckable(true); optionsEdgesVisibilityAct->setChecked( (appSettings["initEdgesVisibility"] == "true") ? true : false); connect(optionsEdgesVisibilityAct, SIGNAL(triggered(bool)), this, SLOT(slotOptionsEdgesVisibility(bool))); optionsEdgeWeightNumbersAct = new QAction(tr("Display Edge Weights"), this); optionsEdgeWeightNumbersAct->setStatusTip( tr("Toggle displaying of numbers of edge weights")); optionsEdgeWeightNumbersAct->setWhatsThis( tr("Display Edge Weights\n\n" "Enables or disables displaying edge weight numbers.")); optionsEdgeWeightNumbersAct->setCheckable(true); connect(optionsEdgeWeightNumbersAct, SIGNAL(triggered(bool)), this, SLOT(slotOptionsEdgeWeightNumbersVisibility(bool))); optionsEdgeWeightConsiderAct = new QAction(tr("Consider Edge Weights in Calculations"), this); optionsEdgeWeightConsiderAct->setStatusTip( tr("Toggle considering edge weights during calculations " "(i.e. distances, centrality, etc) (this session only)")); optionsEdgeWeightConsiderAct->setWhatsThis( tr("Consider Edge Weights in Calculations\n\n" "Enables or disables considering edge weights during " "calculations (i.e. distances, centrality, etc).\n" "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); optionsEdgeWeightConsiderAct->setCheckable(true); optionsEdgeWeightConsiderAct->setChecked(false); connect(optionsEdgeWeightConsiderAct, SIGNAL(triggered(bool)), this, SLOT(slotOptionsEdgeWeightsDuringComputation(bool))); optionsEdgeLabelsAct = new QAction(tr("Display Edge Labels"), this); optionsEdgeLabelsAct->setStatusTip( tr("Toggle displaying of edge labels, if any")); optionsEdgeLabelsAct->setWhatsThis( tr("Display Edge Labels\n\n" "Enables or disables displaying edge labels.")); optionsEdgeLabelsAct->setCheckable(true); optionsEdgeLabelsAct->setChecked( (appSettings["initEdgeLabelsVisibility"] == "true") ? true : false); connect(optionsEdgeLabelsAct, SIGNAL(triggered(bool)), this, SLOT(slotOptionsEdgeLabelsVisibility(bool))); optionsEdgeArrowsAct = new QAction(tr("Display Edge Arrows"), this); optionsEdgeArrowsAct->setStatusTip( tr("Toggle displaying directional arrows on edges")); optionsEdgeArrowsAct->setWhatsThis( tr("Display Edge Arrows\n\n" "Enables or disables displaying of arrows on edges.\n\n" "Useful if all links are reciprocal (undirected graph).")); optionsEdgeArrowsAct->setCheckable(true); optionsEdgeArrowsAct->setChecked( (appSettings["initEdgeArrows"] == "true") ? true : false); connect(optionsEdgeArrowsAct, SIGNAL(triggered(bool)), this, SLOT(slotOptionsEdgeArrowsVisibility(bool))); optionsEdgeThicknessPerWeightAct = new QAction(tr("Edge Thickness reflects Weight"), this); optionsEdgeThicknessPerWeightAct->setStatusTip(tr("Draw edges as thick as their weights (if specified)")); optionsEdgeThicknessPerWeightAct->setWhatsThis( tr("Edge thickness reflects weight\n\n" "Click to toggle having all edges as thick as their weight (if specified)")); optionsEdgeThicknessPerWeightAct->setCheckable(true); optionsEdgeThicknessPerWeightAct->setChecked( (appSettings["initEdgeThicknessPerWeight"] == "true") ? true : false); connect(optionsEdgeThicknessPerWeightAct, SIGNAL(triggered(bool)), this, SLOT(slotOptionsEdgeThicknessPerWeight(bool))); optionsEdgeThicknessPerWeightAct->setEnabled(false); drawEdgesBezier = new QAction(tr("Bezier Curves"), this); drawEdgesBezier->setStatusTip(tr("Draw Edges as Bezier curves")); drawEdgesBezier->setWhatsThis( tr("Edges Bezier\n\n" "Enables or disables drawing edges as Bezier curves.")); drawEdgesBezier->setCheckable(true); drawEdgesBezier->setChecked( (appSettings["initEdgeShape"] == "bezier") ? true : false); connect(drawEdgesBezier, SIGNAL(triggered(bool)), this, SLOT(slotOptionsEdgesBezier(bool))); changeBackColorAct = new QAction(QIcon(":/images/format_color_fill_48px.svg"), tr("Change Background Color"), this); changeBackColorAct->setStatusTip(tr("Change the canvasbackground color")); changeBackColorAct->setWhatsThis(tr("Background Color\n\n" "Changes the background color of the canvas")); connect(changeBackColorAct, SIGNAL(triggered()), this, SLOT(slotOptionsBackgroundColor())); backgroundImageAct = new QAction(QIcon(":/images/wallpaper_48px.svg"), tr("Background Image"), this); backgroundImageAct->setStatusTip( tr("Select and display a custom image in the canvas background")); backgroundImageAct->setWhatsThis( tr("Background Image\n\n" "Enable to select an image file from your computer, " "which will be displayed in the background instead of plain color.")); backgroundImageAct->setCheckable(true); backgroundImageAct->setChecked(false); connect(backgroundImageAct, SIGNAL(triggered(bool)), this, SLOT(slotOptionsBackgroundImageSelect(bool))); fullScreenModeAct = new QAction(QIcon(":/images/fullscreen_48px.svg"), tr("Full screen (this session)"), this); fullScreenModeAct->setShortcut(QKeySequence::FullScreen); fullScreenModeAct->setStatusTip( tr("Toggle full screen mode (for this session only)")); fullScreenModeAct->setWhatsThis( tr("Full Screen Mode\n\n" "Enable to show application window in full screen mode. " "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); fullScreenModeAct->setCheckable(true); fullScreenModeAct->setChecked(false); connect(fullScreenModeAct, SIGNAL(triggered(bool)), this, SLOT(slotOptionsWindowFullScreen(bool))); openSettingsAct = new QAction(QIcon(":/images/settings_48px.svg"), tr("Settings"), this); openSettingsAct->setShortcut(Qt::CTRL | Qt::Key_Comma); openSettingsAct->setEnabled(true); openSettingsAct->setToolTip( tr("Open the Settings dialog where you can save your preferences " "for all future sessions")); openSettingsAct->setStatusTip( tr("Open the Settings dialog to save your preferences " "for all future sessions")); openSettingsAct->setWhatsThis( tr("Settings\n\n" "Opens the Settings dialog where you can edit and save settings " "permanently for all subsequent sessions.")); connect(openSettingsAct, SIGNAL(triggered()), this, SLOT(slotOpenSettingsDialog())); viewDataTableAct = new QAction(QIcon(":/images/data_table_48px.svg"), tr("Data Table"), this); viewDataTableAct->setCheckable(true); viewDataTableAct->setChecked(false); viewDataTableAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_D)); viewDataTableAct->setStatusTip(tr("Show/hide the node and edge data table panel")); viewDataTableAct->setToolTip(tr("Toggle the node/edge data table panel (Ctrl+D)")); connect(viewDataTableAct, &QAction::toggled, this, &MainWindow::slotViewDataTable); /** Help menu actions */ helpApp = new QAction(QIcon(":/images/help_48px.svg"), tr("Manual"), this); helpApp->setShortcut(Qt::Key_F1); helpApp->setStatusTip(tr("Read the manual...")); helpApp->setWhatsThis(tr("Manual\n\nDisplays the documentation of SocNetV")); connect(helpApp, SIGNAL(triggered()), this, SLOT(slotHelp())); tipsApp = new QAction(QIcon(":/images/tip_24px.svg"), tr("Tip of the Day"), this); tipsApp->setStatusTip(tr("Read useful tips")); tipsApp->setWhatsThis(tr("Quick Tips\n\nDisplays some useful and quick tips")); connect(tipsApp, SIGNAL(triggered()), this, SLOT(slotHelpTips())); helpCheckUpdatesApp = new QAction( QIcon(":/images/system_update_alt_48px.svg"), tr("Check for Updates"), this); helpCheckUpdatesApp->setStatusTip(tr("Open a browser to SocNetV website " "to check for a new version...")); helpCheckUpdatesApp->setWhatsThis(tr("Check Updates\n\n" "Open a browser to SocNetV website so " "that you can check yourself for updates")); connect(helpCheckUpdatesApp, SIGNAL(triggered()), this, SLOT(slotHelpCheckUpdateDialog())); helpSystemInfoAct = new QAction(QIcon(":/images/about_24px.svg"), tr("System Information"), this); helpSystemInfoAct->setEnabled(true); helpSystemInfoAct->setStatusTip(tr("Show information about your system")); helpSystemInfoAct->setWhatsThis( tr("

System Information

" "

Shows useful information about your system, " "which you can include in your bug reports.

")); connect(helpSystemInfoAct, SIGNAL(triggered()), this, SLOT(slotHelpSystemInfo())); helpAboutApp = new QAction(QIcon(":/images/about_24px.svg"), tr("About SocNetV"), this); helpAboutApp->setStatusTip(tr("About SocNetV")); helpAboutApp->setWhatsThis(tr("About\n\nBasic information about SocNetV")); connect(helpAboutApp, SIGNAL(triggered()), this, SLOT(slotHelpAbout())); helpAboutQt = new QAction(QIcon(":/images/qt.png"), tr("About Qt"), this); helpAboutQt->setStatusTip(tr("About Qt")); helpAboutQt->setWhatsThis(tr("About\n\nAbout Qt")); connect(helpAboutQt, SIGNAL(triggered()), this, SLOT(slotAboutQt())); qCDebug(lcMainWindow) << "Finished actions initialization."; } SocNetV-3.7/src/mainwindow/init/mainwindow_init_app.cpp000066400000000000000000000731261524402210300233570ustar00rootroot00000000000000/** * @file mainwindow_init_app.cpp * @brief Implements remaining MainWindow startup initialization: canvas/graph/signal-slot wiring, app defaults, combo boxes, and text codec setup. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/filterbarwidget.h" #include "widgets/graphtablewidget.h" #include #include #include /** * @brief Initializes our graphics widget, the canvas where we draw networks * * The widget is a QGraphicsView, with a scene, and is the 'main' widget of the application. */ void MainWindow::initView() { qCDebug(lcMainWindow) << "Creating graphics widget..."; // Create our scene scene = new QGraphicsScene(); // Create a view widget and pass the scene and the our object as parent graphicsWidget = new GraphicsWidget(scene, this); graphicsWidget->setObjectName("graphicsWidget"); bool toggle = false; toggle = (appSettings["opengl"] == "true") ? true : false; graphicsWidget->setOptionsOpenGL(toggle); toggle = (appSettings["antialiasing"] == "true") ? true : false; graphicsWidget->setOptionsAntialiasing(toggle); // Disables QGraphicsView's antialiasing auto-adjustment of exposed areas. toggle = (appSettings["canvasAntialiasingAutoAdjustment"] == "true") ? false : true; graphicsWidget->setOptionsNoAntialiasingAutoAdjust(toggle); toggle = (appSettings["canvasSmoothPixmapTransform"] == "true") ? true : false; graphicsWidget->setRenderHint(QPainter::SmoothPixmapTransform, toggle); // if items do restore their state, it's not needed for graphicsWidget to do the same... toggle = (appSettings["canvasPainterStateSave"] == "true") ? false : true; graphicsWidget->setOptimizationFlag(QGraphicsView::DontSavePainterState, toggle); if (appSettings["canvasUpdateMode"] == "Full") { graphicsWidget->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); } else if (appSettings["canvasUpdateMode"] == "Minimal") { graphicsWidget->setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); } else if (appSettings["canvasUpdateMode"] == "Smart") { graphicsWidget->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate); } else if (appSettings["canvasUpdateMode"] == "Bounding Rectangle") { graphicsWidget->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); } else if (appSettings["canvasUpdateMode"] == "None") { graphicsWidget->setViewportUpdateMode(QGraphicsView::NoViewportUpdate); } else { // graphicsWidget->setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); } // QGraphicsView can cache pre-rendered content in a QPixmap, which is then drawn onto the viewport. if (appSettings["canvasCacheBackground"] == "true") { graphicsWidget->setCacheMode(QGraphicsView::CacheBackground); } else { graphicsWidget->setCacheMode(QGraphicsView::CacheNone); } graphicsWidget->setTransformationAnchor(QGraphicsView::AnchorUnderMouse); // graphicsWidget->setTransformationAnchor(QGraphicsView::AnchorViewCenter); // graphicsWidget->setTransformationAnchor(QGraphicsView::NoAnchor); graphicsWidget->setResizeAnchor(QGraphicsView::AnchorViewCenter); // sets dragging the mouse over the scene while the left mouse button is pressed. graphicsWidget->setDragMode(QGraphicsView::RubberBandDrag); graphicsWidget->setFocusPolicy(Qt::StrongFocus); graphicsWidget->setFocus(); graphicsWidget->setWhatsThis(tr("

The canvas of SocNetV

" "

Inside this area you create and edit networks, " "load networks from files and visualize them " "according to the selected metrics.

" "

To create a new node, double-click anywhere.

" "

To add an edge between two nodes, double-click" " on the first node (source) then double-click on the second (target) .

" "

To move around the canvas, use the keyboard arrows.

" "

To change network appearance, right click on empty space.

" "

To edit the properties of a node, right-click on it.

" "

To edit the properties of an edge, right-click on it.

")); qCDebug(lcMainWindow) << "Finished initialization of graphics widget. Dimensions:" << graphicsWidget->width() << "x" << graphicsWidget->height(); } /** * @brief Initializes the Graph */ void MainWindow::initGraph() { qCDebug(lcMainWindow) << "creating activeGraph object..."; bool ok1; nodesEstimatedSize = (appSettings["initNodesEstimatedSize"]).toInt(&ok1, 10); if (!ok1) { nodesEstimatedSize = 0; } bool ok2; edgesPerNodeEstimatedSize = (appSettings["initEdgesPerNodeEstimatedSize"]).toInt(&ok2, 10); if (!ok2) { edgesPerNodeEstimatedSize = 0; } activeGraph = new Graph(nodesEstimatedSize, edgesPerNodeEstimatedSize); qCDebug(lcMainWindow) << "activeGraph created on thread:" << activeGraph->getThread() << "moving it to new thread "; activeGraph->moveToThreadFacade(&graphThread); qCDebug(lcMainWindow) << "activeGraph moved to thread:" << activeGraph->getThread() << "starting new activeGraph thread..."; graphThread.start(); qCDebug(lcMainWindow) << "activeGraph thread now:" << activeGraph->getThread() << "Finished initialization of graph."; } /** * @brief Connects signals & slots between various parts of the app * * Signal/slots between: * - the GraphicsWidget and the Graph * - the GraphicsWidget and the MainWindow * This must be called after all widgets have been created. * */ void MainWindow::initSignalSlots() { qCDebug(lcMainWindow) << "setting up signals/slots between widgets (graphicsWidget, activeGraph and MW)..."; // Signals between graphicsWidget and MainWindow connect(graphicsWidget, &GraphicsWidget::setCursor, this, &MainWindow::setCursor); connect(graphicsWidget, &GraphicsWidget::userMiddleClicked, this, &MainWindow::slotEditEdgeCreate); connect(graphicsWidget, SIGNAL(openNodeMenu()), this, SLOT(slotEditNodeOpenContextMenu())); connect(graphicsWidget, &GraphicsWidget::openContextMenu, this, &MainWindow::slotEditOpenContextMenu); connect(graphicsWidget, SIGNAL(userNodeMoved(const int &, const int &, const int &)), this, SLOT(slotEditNodePosition(const int &, const int &, const int &))); connect(graphicsWidget, SIGNAL(zoomChanged(const int &)), zoomSlider, SLOT(setValue(const int &))); connect(zoomSlider, SIGNAL(sliderMoved(const int &)), graphicsWidget, SLOT(changeMatrixScale(const int &))); connect(zoomInBtn, SIGNAL(clicked()), graphicsWidget, SLOT(zoomIn())); connect(zoomOutBtn, SIGNAL(clicked()), graphicsWidget, SLOT(zoomOut())); // rotateSlider::valueChanged (unlike zoomSlider::sliderMoved above) fires on // programmatic setValue() too, so updating it here would otherwise bounce straight // back into changeMatrixRotation() with the same angle. QSignalBlocker keeps this a // display-only update. connect(graphicsWidget, &GraphicsWidget::rotationChanged, this, [this](const int angle) { const QSignalBlocker blocker(rotateSlider); rotateSlider->setValue(angle); }); connect(rotateSlider, SIGNAL(valueChanged(const int &)), graphicsWidget, SLOT(changeMatrixRotation(const int &))); connect(rotateLeftBtn, SIGNAL(clicked()), graphicsWidget, SLOT(rotateLeft())); connect(rotateRightBtn, SIGNAL(clicked()), graphicsWidget, SLOT(rotateRight())); connect(resetSlidersBtn, SIGNAL(clicked()), graphicsWidget, SLOT(reset())); // // SIGNALS BETWEEN ACTIVEGRAPH AND MAINWINDOW // connect(activeGraph, &Graph::signalSelectionChanged, this, &MainWindow::slotEditSelectionChanged); connect(activeGraph, &Graph::signalNodeClickedInfo, this, &MainWindow::slotEditNodeInfoStatusBar); connect(activeGraph, &Graph::signalEdgeClicked, this, &MainWindow::slotEditEdgeClicked); connect(activeGraph, &Graph::signalGraphModified, this, &MainWindow::slotNetworkChanged); connect(activeGraph, &Graph::signalGraphLoaded, this, &MainWindow::slotNetworkFileLoaded); connect(activeGraph, &Graph::signalGraphLoaded, graphicsWidget, &GraphicsWidget::zoomToFit); connect(m_tableWidget, &GraphTableWidget::nodeSelected, this, [this](int number) { statusMessage(tr("Node %1 selected from data table").arg(number)); }); connect(m_tableWidget, &GraphTableWidget::edgeSelected, this, [this](int source, int target) { statusMessage(tr("Edge %1β†’%2 selected from data table").arg(source).arg(target)); }); connect(m_tableWidget, &GraphTableWidget::exportStatusMessage, this, &MainWindow::statusMessage); connect(m_tableWidget, &GraphTableWidget::importStatusMessage, this, &MainWindow::statusMessage); connect(activeGraph, &Graph::signalGraphSavedStatus, this, &MainWindow::slotNetworkSavedStatus); connect(activeGraph, SIGNAL(statusMessage(QString)), this, SLOT(statusMessage(QString))); connect(activeGraph, SIGNAL(signalDatasetDescription(QString)), this, SLOT(slotHelpMessageToUserInfo(QString))); connect(editRelationNextAct, &QAction::triggered, activeGraph, &Graph::relationNext); connect(editRelationPreviousAct, &QAction::triggered, activeGraph, &Graph::relationPrev); connect(editRelationChangeCombo, SIGNAL(activated(int)), activeGraph, SLOT(relationSet(int))); connect(editRelationChangeCombo, SIGNAL(currentTextChanged(const QString &)), activeGraph, SLOT(relationCurrentRename(const QString &))); // connect( editRelationChangeCombo, &QComboBox::currentTextChanged, // activeGraph, QOverload::of(&Graph::relationCurrentRename)); connect(this, &MainWindow::signalRelationAddAndChange, activeGraph, &Graph::relationAdd); connect(activeGraph, &Graph::signalRelationChangedToMW, this, &MainWindow::slotEditRelationChange); connect(activeGraph, &Graph::signalGraphDirectedChanged, this, &MainWindow::slotEditGraphDirectedChanged); connect(activeGraph, &Graph::signalRelationsClear, this, &MainWindow::slotEditRelationsClear); connect(activeGraph, &Graph::signalRelationAddToMW, this, &MainWindow::slotEditRelationAdd); connect(activeGraph, &Graph::signalRelationRenamedToMW, editRelationChangeCombo, &QComboBox::setCurrentText); connect(activeGraph, &Graph::signalPromininenceDistributionChartUpdate, this, &MainWindow::slotAnalyzeProminenceDistributionChartUpdate); connect(activeGraph, &Graph::signalNetworkManagerRequest, this, &MainWindow::slotNetworkManagerRequest); // // Signals between activeGraph and graphicsWidget // connect(activeGraph, &Graph::addGuideCircle, graphicsWidget, &GraphicsWidget::addGuideCircle); connect(activeGraph, &Graph::addGuideHLine, graphicsWidget, &GraphicsWidget::addGuideHLine); connect(activeGraph, &Graph::setNodePos, graphicsWidget, &GraphicsWidget::moveNode); connect(activeGraph, &Graph::signalLayoutFinished, graphicsWidget, &GraphicsWidget::zoomToFit); connect(activeGraph, &Graph::signalNodesFound, graphicsWidget, &GraphicsWidget::setSelectedNodes); connect(activeGraph, &Graph::signalDrawNode, graphicsWidget, &GraphicsWidget::drawNode); connect(activeGraph, &Graph::signalRemoveNode, graphicsWidget, &GraphicsWidget::removeNode); connect(activeGraph, &Graph::setVertexVisibility, graphicsWidget, &GraphicsWidget::setNodeVisibility); connect(activeGraph, &Graph::setNodeSize, graphicsWidget, &GraphicsWidget::setNodeSize); connect(activeGraph, &Graph::setNodeColor, graphicsWidget, &GraphicsWidget::setNodeColor); connect(activeGraph, &Graph::setNodeShape, graphicsWidget, &GraphicsWidget::setNodeShape); connect(activeGraph, &Graph::setNodeNumberColor, graphicsWidget, &GraphicsWidget::setNodeNumberColor); connect(activeGraph, &Graph::setNodeNumberSize, graphicsWidget, &GraphicsWidget::setNodeNumberSize); connect(activeGraph, &Graph::setNodeNumberDistance, graphicsWidget, &GraphicsWidget::setNodeNumberDistance); connect(activeGraph, &Graph::setNodeLabel, graphicsWidget, &GraphicsWidget::setNodeLabel); connect(activeGraph, &Graph::setNodeLabelColor, graphicsWidget, &GraphicsWidget::setNodeLabelColor); connect(activeGraph, &Graph::setNodeLabelSize, graphicsWidget, &GraphicsWidget::setNodeLabelSize); connect(activeGraph, &Graph::setNodeLabelDistance, graphicsWidget, &GraphicsWidget::setNodeLabelDistance); connect(activeGraph, &Graph::signalRemoveEdge, graphicsWidget, &GraphicsWidget::removeEdge); connect(activeGraph, &Graph::signalDrawEdge, graphicsWidget, &GraphicsWidget::drawEdge); connect(activeGraph, &Graph::setEdgeWeight, graphicsWidget, &GraphicsWidget::setEdgeWeight); connect(activeGraph, &Graph::signalEdgeType, graphicsWidget, &GraphicsWidget::setEdgeDirectionType); connect(activeGraph, &Graph::setEdgeColor, graphicsWidget, &GraphicsWidget::setEdgeColor); connect(activeGraph, &Graph::setEdgeLabel, graphicsWidget, &GraphicsWidget::setEdgeLabel); connect(activeGraph, &Graph::signalSetEdgeVisibility, graphicsWidget, &GraphicsWidget::setEdgeVisibility); connect(activeGraph, &Graph::signalSetEdgesVisibilityBatch, graphicsWidget, &GraphicsWidget::setEdgesVisibilityBatch); connect(activeGraph, &Graph::signalRelationChangedToGW, graphicsWidget, &GraphicsWidget::relationSet); connect(graphicsWidget, &GraphicsWidget::userClickOnEmptySpace, activeGraph, &Graph::graphClickedEmptySpace); connect(graphicsWidget, &GraphicsWidget::resized, activeGraph, &Graph::canvasSizeSet); connect(graphicsWidget, &GraphicsWidget::userDoubleClickNewNode, activeGraph, &Graph::vertexCreateAtPos); connect(graphicsWidget, &GraphicsWidget::userSelectedItems, activeGraph, &Graph::setSelectionChanged); connect(graphicsWidget, &GraphicsWidget::userSelectedItems, this, &MainWindow::slotCacheSelection); connect(graphicsWidget, &GraphicsWidget::userClickedNode, activeGraph, &Graph::vertexClickedSet); connect(graphicsWidget, &GraphicsWidget::userClickedEdge, activeGraph, &Graph::edgeClickedSet); // // Signals and slots inside MainWindow // #ifndef QT_NO_SSL connect(networkManager, &QNetworkAccessManager::sslErrors, this, &MainWindow::slotNetworkManagerSslErrors); #endif connect(editMouseModeInteractiveAct, &QAction::triggered, this, &MainWindow::slotEditDragModeSelection); connect(editMouseModeScrollAct, &QAction::triggered, this, &MainWindow::slotEditDragModeScroll); connect(editRelationAddAct, SIGNAL(triggered()), this, SLOT(slotEditRelationAddPrompt())); connect(editRelationRenameAct, SIGNAL(triggered()), this, SLOT(slotEditRelationRename())); connect(zoomInAct, SIGNAL(triggered()), graphicsWidget, SLOT(zoomIn())); connect(zoomOutAct, SIGNAL(triggered()), graphicsWidget, SLOT(zoomOut())); connect(editRotateLeftAct, SIGNAL(triggered()), graphicsWidget, SLOT(rotateLeft())); connect(editRotateRightAct, SIGNAL(triggered()), graphicsWidget, SLOT(rotateRight())); connect(editResetSlidersAct, SIGNAL(triggered()), graphicsWidget, SLOT(reset())); connect(layoutGuidesAct, SIGNAL(triggered(bool)), this, SLOT(slotLayoutGuides(bool))); connect(toolBoxNetworkAutoCreateSelect, static_cast(&QComboBox::currentIndexChanged), this, &MainWindow::toolBoxNetworkAutoCreateSelectChanged); connect(toolBoxEditNodeSubgraphSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(toolBoxEditNodeSubgraphSelectChanged(int))); connect(toolBoxEditEdgeModeSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(slotEditEdgeMode(int))); connect(toolBoxEditEdgeTransformSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(toolBoxEditEdgeTransformSelectChanged(int))); connect(toolBoxFilterSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(toolBoxFilterSelectChanged(int))); // Filter bar chip signals connect(m_filterBar, &FilterBarWidget::chipCloseRequested, this, [this](int barIndex, FilterCondition::Scope /*scope*/) { // barIndex == stackIndex: every filter (node or edge) pushes exactly one snapshot. runGraphOperationAsync( [this, barIndex]() { activeGraph->vertexFilterRemoveAt(barIndex); }, tr("Removing filter..."), [this, barIndex]() { m_filterChips.removeAt(barIndex); // Rebuild bar from remaining chips (preserves original application order). m_filterBar->clearAllChips(); for (const auto &chip : std::as_const(m_filterChips)) m_filterBar->addChip(chip.first, chip.second); // Update restore-action states. bool hasNodeFilters = false, hasEdgeFilters = false; for (const auto &chip : std::as_const(m_filterChips)) { if (chip.second != FilterCondition::Scope::Edges) hasNodeFilters = true; else hasEdgeFilters = true; } filterNodesRestoreAllAct->setEnabled(hasNodeFilters); editFilterEdgesRestoreAllAct->setEnabled(hasEdgeFilters); }); }); connect(m_filterBar, &FilterBarWidget::clearAllRequested, this, [this]() { runGraphOperationAsync( [this]() { while (!activeGraph->visibilityHistoryEmpty()) activeGraph->vertexFilterRestoreAll(); }, tr("Clearing all filters..."), [this]() { m_filterChips.clear(); filterNodesRestoreAllAct->setEnabled(false); editFilterEdgesRestoreAllAct->setEnabled(false); m_filterBar->clearAllChips(); }); }); connect(toolBoxAnalysisMatricesSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(toolBoxAnalysisMatricesSelectChanged(int))); connect(toolBoxAnalysisCohesionSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(toolBoxAnalysisCohesionSelectChanged(int))); connect(toolBoxAnalysisStrEquivalenceSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(toolBoxAnalysisStrEquivalenceSelectChanged(int))); connect(toolBoxAnalysisCommunitiesSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(toolBoxAnalysisCommunitiesSelectChanged(int))); connect(toolBoxAnalysisProminenceSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(toolBoxAnalysisProminenceSelectChanged(int))); connect(toolBoxLayoutByIndexSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(toolBoxLayoutByIndexApplyBtnPressed())); connect(toolBoxLayoutByIndexTypeSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(toolBoxLayoutByIndexApplyBtnPressed())); connect(toolBoxLayoutForceDirectedSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(toolBoxLayoutForceDirectedApplyBtnPressed())); } /** * @brief Initializes the default app parameters. * * Used on app start and when erasing a network to start a new one */ void MainWindow::initApp() { qCDebug(lcMainWindow) << "### Application initialization starts, on thread" << thread(); statusMessage(tr("Application initialization. Please wait...")); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // first select none graphicsWidget->selectNone(); // Init basic variables inverseWeights = false; askedAboutWeights = false; previous_fileName = fileName; fileName = ""; initTextCodecName = "UTF-8"; networkSaveAct->setIcon(QIcon(":/images/file_download_48px.svg")); networkSaveAct->setEnabled(false); /** Clear previous network data and reset user-selected settings */ qCDebug(lcMainWindow) << "### Clearing current graph. Please wait..."; activeGraph->clear(); activeGraph->vertexShapeSetDefault(appSettings["initNodeShape"], appSettings["initNodeIconPath"]); activeGraph->vertexSizeInit(appSettings["initNodeSize"].toInt(0, 10)); activeGraph->vertexColorInit(appSettings["initNodeColor"]); activeGraph->vertexNumberSizeInit(appSettings["initNodeNumberSize"].toInt(0, 10)); activeGraph->vertexNumberColorInit(appSettings["initNodeNumberColor"]); activeGraph->vertexNumberDistanceInit(appSettings["initNodeNumberDistance"].toInt(0, 10)); activeGraph->vertexLabelColorInit(appSettings["initNodeLabelColor"]); activeGraph->vertexLabelSizeInit(appSettings["initNodeLabelSize"].toInt(0, 10)); activeGraph->vertexLabelDistanceInit(appSettings["initNodeLabelDistance"].toInt(0, 10)); activeGraph->edgeColorInit(appSettings["initEdgeColor"]); activeGraph->edgeColorZeroInit(appSettings["initEdgeColorZero"]); // #30 activeGraph->showZeroWeightEdgesSet( appSettings["showZeroWeightEdges"] == "true"); // #30 activeGraph->edgeWeightNumbersVisibilitySet( (appSettings["initEdgeWeightNumbersVisibility"] == "true") ? true : false); activeGraph->setReportsRealNumberPrecision(appSettings["initReportsRealNumberPrecision"].toInt()); activeGraph->setReportsLabelLength(appSettings["initReportsLabelsLength"].toInt()); activeGraph->setReportsChartType(appSettings["initReportsChartType"].toInt()); activeGraph->setReportsOutputFormat(appSettings["initReportsOutputFormat"].toInt()); emit signalSetReportsDataDir(appSettings["dataDir"]); /** Clear graphicsWidget and reset settings and transformations **/ qCDebug(lcMainWindow) << "### Clearing graphicsWidget and resetting transformations. Please wait..."; graphicsWidget->clear(); rotateSlider->setValue(0); zoomSlider->setValue((int)maxZoomIndex / 2.0); // graphicsWidget->setInitZoomIndex((int) maxZoomIndex/2.0); graphicsWidget->setMaxZoomIndex(maxZoomIndex); graphicsWidget->setInitNodeSize(appSettings["initNodeSize"].toInt(0, 10)); graphicsWidget->setNodeNumberVisibility( (appSettings["initNodeNumbersVisibility"] == "true") ? true : false); graphicsWidget->setNodeLabelsVisibility( (appSettings["initNodeLabelsVisibility"] == "true") ? true : false); graphicsWidget->setNumbersInsideNodes( (appSettings["initNodeNumbersInside"] == "true") ? true : false); graphicsWidget->setEdgeHighlighting( (appSettings["canvasEdgeHighlighting"] == "true") ? true : false); graphicsWidget->setEdgeArrowSize(appSettings["initEdgeArrowSize"].toInt(nullptr, 10)); graphicsWidget->setEdgesBezier(appSettings["initEdgeShape"] == "bezier"); drawEdgesBezier->setChecked(appSettings["initEdgeShape"] == "bezier"); if (appSettings["initBackgroundImage"] != "" && QFileInfo::exists(appSettings["initBackgroundImage"])) { graphicsWidget->setBackgroundBrush(QImage(appSettings["initBackgroundImage"])); graphicsWidget->setCacheMode(QGraphicsView::CacheBackground); statusMessage(tr("BackgroundImage on.")); } else { graphicsWidget->setBackgroundBrush( QBrush(QColor(appSettings["initBackgroundColor"]))); } slotOptionsCanvasIndexMethod(appSettings["canvasIndexMethod"]); /** Clear Chart */ miniChart->resetToTrivial(); /** Clear LCDs **/ qCDebug(lcMainWindow) << "### Clearing Statistics panel LCDs. Please wait..."; rightPanelClickedNodeLCD->setText("-"); rightPanelClickedNodeInDegreeLabel->setVisible(false); rightPanelClickedNodeInDegreeLCD->setVisible(false); rightPanelClickedNodeOutDegreeLabel->setVisible(false); rightPanelClickedNodeOutDegreeLCD->setVisible(false); rightPanelClickedEdgeNameLCD->setText("-"); rightPanelClickedEdgeWeightLabel->setVisible(false); rightPanelClickedEdgeWeightLCD->setVisible(false); rightPanelClickedEdgeReciprocalWeightLabel->setVisible(false); rightPanelClickedEdgeReciprocalWeightLCD->setVisible(false); /** Clear toolbox and menu checkboxes **/ qCDebug(lcMainWindow) << "### Resetting toolbox. Please wait..."; toolBoxEditEdgeTransformSelect->setCurrentIndex(0); toolBoxEditEdgeModeSelect->setCurrentIndex(0); initComboBoxes(); toolBoxLayoutByIndexSelect->blockSignals(true); toolBoxLayoutByIndexSelect->setCurrentIndex(0); toolBoxLayoutByIndexSelect->blockSignals(false); toolBoxLayoutByIndexTypeSelect->blockSignals(true); toolBoxLayoutByIndexTypeSelect->setCurrentIndex(0); toolBoxLayoutByIndexTypeSelect->blockSignals(false); toolBoxLayoutForceDirectedSelect->blockSignals(true); toolBoxLayoutForceDirectedSelect->setCurrentIndex(0); toolBoxLayoutForceDirectedSelect->blockSignals(false); optionsEdgeWeightNumbersAct->setChecked( (appSettings["initEdgeWeightNumbersVisibility"] == "true") ? true : false); optionsEdgeWeightConsiderAct->setChecked(false); optionsEdgeArrowsAct->setChecked( (appSettings["initEdgeArrows"] == "true") ? true : false); optionsEdgeLabelsAct->setChecked( (appSettings["initEdgeLabelsVisibility"] == "true") ? true : false); editFilterNodesIsolatesAct->setChecked(false); // re-init orphan nodes menu item editFilterEdgesUnilateralAct->setChecked(false); // editRelationChangeCombo->clear(); qCDebug(lcMainWindow) << "### Clearing textEditors. Current count: " << m_textEditors.size() << "textEditors"; foreach (TextEditor *ed, m_textEditors) { ed->close(); delete ed; } m_textEditors.clear(); QApplication::restoreOverrideCursor(); // Do it again, to catch any older overriden cursor QApplication::restoreOverrideCursor(); setCursor(Qt::ArrowCursor); setWindowTitle("SocNetV"); filterNodesRestoreAllAct->setEnabled(false); editSubgraphExtractFromSelectionAct->setEnabled(false); m_filterChips.clear(); m_filterBar->clearAllChips(); // Always reset the data table so it shows an empty graph when next opened if (m_tableWidget) m_tableWidget->refresh(activeGraph); statusMessage(tr("Ready")); qCDebug(lcMainWindow) << "#### APP INITIALISATION FINISHED, ON THREAD" << thread(); } /** * @brief Initializes combo boxes in the MW */ void MainWindow::initComboBoxes() { toolBoxAnalysisCommunitiesSelect->setCurrentIndex(0); toolBoxAnalysisStrEquivalenceSelect->setCurrentIndex(0); toolBoxAnalysisCohesionSelect->setCurrentIndex(0); toolBoxAnalysisProminenceSelect->setCurrentIndex(0); toolBoxAnalysisMatricesSelect->setCurrentIndex(0); toolBoxNetworkAutoCreateSelect->setCurrentIndex(0); toolBoxEditNodeSubgraphSelect->setCurrentIndex(0); } /** * @brief Setup a list of all text codecs supported by OS */ void MainWindow::initNetworkAvailableTextCodecs() { qCDebug(lcMainWindow) << "Checking which text codecs are supported and storing them to a list"; QMap codecMap; QRegularExpression iso8859RegExp("ISO[- ]8859-([0-9]+).*"); QRegularExpressionMatch match; foreach (int mib, QTextCodec::availableMibs()) { QTextCodec *codec = QTextCodec::codecForMib(mib); // // FOR FUTURE REFERENCE (IF QTextCodec Class GETS REMOVED FROM QT6 QT5 CORE COMPAT MODULE) // Verify that Codec/Encoding is supported by QStringConverter, // Otherwise skip it. // std::optional test_support = QStringConverter::encodingForName(codec->name()); // if ( ! test_support.has_value()) { // continue; // } QString sortKey = codec->name().toUpper(); match = iso8859RegExp.match(sortKey); int rank; if (sortKey.startsWith("UTF-8")) { rank = 1; } else if (sortKey.startsWith("UTF-16")) { rank = 2; } else if (match.hasMatch()) { if (match.captured(1).size() == 1) rank = 3; else rank = 4; } else { rank = 5; } sortKey.prepend(QChar('0' + rank)); codecMap.insert(sortKey, codec); } codecs = codecMap.values(); } SocNetV-3.7/src/mainwindow/init/mainwindow_init_menu.cpp000066400000000000000000000575321524402210300235460ustar00rootroot00000000000000/** * @file mainwindow_init_menu.cpp * @brief Implements MainWindow menu bar and toolbar construction. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/filterbarwidget.h" #include "widgets/graphtablewidget.h" #include #include /** * @brief Populates the menu bar with our menu items. */ void MainWindow::initMenuBar() { qCDebug(lcMainWindow) << "Initializing menu bar..."; /** NETWORK MENU */ networkMenu = menuBar()->addMenu(tr("&Network")); networkMenu->addAction(networkNewAct); networkMenu->addAction(networkOpenAct); networkMenu->addSeparator(); recentFilesSubMenu = new QMenu(tr("Recent &files...")); recentFilesSubMenu->setIcon(QIcon(":/images/recent_48px.svg")); for (int i = 0; i < MaxRecentFiles; ++i) { recentFilesSubMenu->addAction(recentFileActs[i]); } slotNetworkFileRecentUpdateActions(); networkMenu->addMenu(recentFilesSubMenu); networkMenu->addSeparator(); importSubMenu = new QMenu(tr("&Import ...")); importSubMenu->setIcon(QIcon(":/images/file_upload_48px.svg")); importSubMenu->addAction(networkImportGMLAct); importSubMenu->addAction(networkImportPajekAct); importSubMenu->addAction(networkImportAdjAct); importSubMenu->addAction(networkImportTwoModeSM); importSubMenu->addAction(networkImportListAct); importSubMenu->addAction(networkImportUcinetAct); importSubMenu->addAction(networkImportGraphvizAct); networkMenu->addMenu(importSubMenu); networkMenu->addSeparator(); networkMenu->addAction(openTextEditorAct); networkMenu->addAction(networkViewFileAct); networkMenu->addSeparator(); networkMenu->addAction(networkViewSociomatrixAct); networkMenu->addAction(networkViewSociomatrixPlotAct); networkMenu->addSeparator(); networkMenu->addAction(networkDataSetSelectAct); networkMenu->addSeparator(); randomNetworkMenu = new QMenu(tr("Create &Random Network...")); randomNetworkMenu->setIcon(QIcon(":/images/random_48px.svg")); networkMenu->addMenu(randomNetworkMenu); randomNetworkMenu->addAction(networkRandomScaleFreeAct); randomNetworkMenu->addAction(networkRandomSmallWorldAct); randomNetworkMenu->addAction(networkRandomErdosRenyiAct); randomNetworkMenu->addAction(networkRandomLatticeAct); randomNetworkMenu->addAction(networkRandomRegularSameDegreeAct); randomNetworkMenu->addAction(networkRandomLatticeRingAct); // networkRandomGaussianAct->addTo(randomNetworkMenu); networkMenu->addSeparator(); networkMenu->addAction(networkWebCrawlerAct); networkMenu->addSeparator(); networkMenu->addAction(networkSaveAct); networkMenu->addAction(networkSaveAsAct); networkMenu->addSeparator(); networkMenu->addAction(networkExportImageAct); networkMenu->addAction(networkExportPDFAct); networkMenu->addSeparator(); exportSubMenu = networkMenu->addMenu(tr("Export to other...")); exportSubMenu->setIcon(QIcon(":/images/file_download_48px.svg")); exportSubMenu->addAction(networkExportSMAct); exportSubMenu->addAction(networkExportPajek); exportSubMenu->addAction(networkExportDotAct); exportSubMenu->addAction(networkExportDLAct); exportSubMenu->addAction(networkExportListAct); // exportSubMenu->addAction (networkExportGW); exportSubMenu->addSeparator(); exportSubMenu->addAction(networkExportNodesCSVAct); exportSubMenu->addAction(networkExportEdgesCSVAct); exportSubMenu->addAction(networkExportNodesJSONAct); exportSubMenu->addAction(networkExportEdgesJSONAct); networkMenu->addSeparator(); networkMenu->addAction(networkPrintAct); networkMenu->addSeparator(); networkMenu->addAction(networkCloseAct); networkMenu->addAction(networkQuitAct); // EDIT MENU editMenu = menuBar()->addMenu(tr("&Edit")); editMenu->addAction(editRelationPreviousAct); editMenu->addAction(editRelationNextAct); editMenu->addAction(editRelationAddAct); editMenu->addAction(editRelationRenameAct); editMenu->addSeparator(); editMenu->addAction(zoomInAct); editMenu->addAction(zoomOutAct); editMenu->addSeparator(); editMenu->addAction(editRotateLeftAct); editMenu->addAction(editRotateRightAct); editMenu->addSeparator(); editMenu->addAction(editResetSlidersAct); editMenu->addSeparator(); editNodeMenu = new QMenu(tr("Nodes...")); editNodeMenu->setIcon(QIcon(":/images/node_48px.svg")); editMenu->addMenu(editNodeMenu); editNodeMenu->addAction(editNodeSelectAllAct); editNodeMenu->addAction(editNodeSelectNoneAct); editNodeMenu->addSeparator(); editNodeMenu->addAction(editNodeFindAct); editNodeMenu->addAction(editNodeAddAct); editNodeMenu->addAction(editNodeRemoveAct); editNodeMenu->addSeparator(); editNodeMenu->addAction(editNodePropertiesAct); editNodeMenu->addSeparator(); editNodeMenu->addAction(editNodeSelectedToCliqueAct); editNodeMenu->addAction(editNodeSelectedToStarAct); editNodeMenu->addAction(editNodeSelectedToCycleAct); editNodeMenu->addAction(editNodeSelectedToLineAct); editNodeMenu->addSeparator(); editNodeMenu->addAction(editNodeColorAll); editNodeMenu->addAction(editNodeSizeAllAct); editNodeMenu->addAction(editNodeShapeAll); editNodeMenu->addSeparator(); editNodeMenu->addAction(editNodeNumbersSizeAct); editNodeMenu->addAction(editNodeNumbersColorAct); editNodeMenu->addSeparator(); editNodeMenu->addAction(editNodeLabelsSizeAct); editNodeMenu->addAction(editNodeLabelsColorAct); editNodeMenu->addSeparator(); editNodeMenu->addAction(editFilterNodesIsolatesAct); editEdgeMenu = new QMenu(tr("Edges...")); editEdgeMenu->setIcon(QIcon(":/images/edges_48px.svg")); editMenu->addMenu(editEdgeMenu); editEdgeMenu->addAction(editEdgeAddAct); editEdgeMenu->addAction(editEdgeRemoveAct); editEdgeMenu->addSeparator(); editEdgeMenu->addAction(editEdgeUndirectedAllAct); editEdgeMenu->addSeparator(); editEdgeMenu->addAction(editEdgeSymmetrizeAllAct); editEdgeMenu->addSeparator(); editEdgeMenu->addAction(editEdgeSymmetrizeStrongTiesAct); editEdgeMenu->addAction(editEdgesCocitationAct); editEdgeMenu->addSeparator(); editEdgeMenu->addAction(editEdgeDichotomizeAct); editEdgeMenu->addSeparator(); editEdgeMenu->addAction(editFilterEdgesUnilateralAct); editEdgeMenu->addSeparator(); editEdgeMenu->addAction(editEdgeLabelAct); editEdgeMenu->addAction(editEdgeColorAct); editEdgeMenu->addAction(editEdgeWeightAct); editEdgeMenu->addSeparator(); editEdgeMenu->addAction(editEdgeColorAllAct); // transformNodes2EdgesAct->addTo (editMenu); editMenu->addSeparator(); filterMenu = new QMenu(tr("Filter...")); filterMenu->setIcon(QIcon(":/images/filter_list_48px.svg")); editMenu->addMenu(filterMenu); // β€” Node filters β€” filterMenu->addAction(filterNodesBySelectionAct); filterMenu->addAction(filterNodesByEgoNetworkAct); filterMenu->addAction(filterNodesByCentralityAct); filterMenu->addSeparator(); // β€” Edge filters β€” filterMenu->addAction(editFilterEdgesByWeightAct); filterMenu->addSeparator(); // β€” Attribute filter (nodes + edges) β€” filterMenu->addAction(filterNodesByAttributeAct); filterMenu->addAction(filterByQueryBuilderAct); filterMenu->addSeparator(); // β€” Restore β€” filterMenu->addAction(filterNodesRestoreAllAct); filterMenu->addAction(editFilterEdgesRestoreAllAct); editMenu->addSeparator(); subgraphMenu = new QMenu(tr("Subgraphs...")); subgraphMenu->setIcon(QIcon(":/images/filter_list_48px.svg")); editMenu->addMenu(subgraphMenu); subgraphMenu->addAction(editSubgraphExtractAct); subgraphMenu->addSeparator(); subgraphMenu->addAction(editSubgraphExtractFromSelectionAct); editMenu->addSeparator(); editMenu->addAction(viewDataTableAct); // ANALYZE MENU analysisMenu = menuBar()->addMenu(tr("&Analyze")); matrixMenu = new QMenu(tr("Adjacency Matrix and Matrices...")); matrixMenu->setIcon(QIcon(":/images/sociomatrix_48px.svg")); analysisMenu->addMenu(matrixMenu); matrixMenu->addAction(networkViewSociomatrixAct); matrixMenu->addAction(networkViewSociomatrixPlotAct); matrixMenu->addSeparator(); matrixMenu->addAction(analyzeMatrixAdjInvertAct); matrixMenu->addSeparator(); matrixMenu->addAction(analyzeMatrixAdjTransposeAct); matrixMenu->addSeparator(); matrixMenu->addAction(analyzeMatrixAdjCocitationAct); matrixMenu->addSeparator(); matrixMenu->addAction(analyzeMatrixDegreeAct); matrixMenu->addAction(analyzeMatrixLaplacianAct); // analysisMenu->addAction (netDensity); analysisMenu->addSeparator(); cohesionMenu = new QMenu(tr("Cohesion...")); cohesionMenu->setIcon(QIcon(":/images/assessment_48px.svg")); analysisMenu->addMenu(cohesionMenu); cohesionMenu->addAction(analyzeGraphReciprocityAct); cohesionMenu->addAction(analyzeGraphSymmetryAct); cohesionMenu->addSection("Graph distances"); cohesionMenu->addAction(analyzeGraphDistanceAct); cohesionMenu->addAction(averGraphDistanceAct); cohesionMenu->addAction(analyzeGeodesicDistributionAct); cohesionMenu->addSeparator(); cohesionMenu->addAction(analyzeMatrixDistancesGeodesicAct); cohesionMenu->addAction(analyzeMatrixGeodesicsAct); cohesionMenu->addSeparator(); cohesionMenu->addAction(analyzeGraphEccentricityAct); cohesionMenu->addAction(analyzeGraphDiameterAct); cohesionMenu->addSeparator(); cohesionMenu->addAction(analyzeGraphConnectednessAct); cohesionMenu->addAction(analyzeNodeConnectivityAct); cohesionMenu->addAction(analyzeConnectivityAct); cohesionMenu->addSeparator(); cohesionMenu->addAction(analyzeGraphWalksAct); cohesionMenu->addAction(analyzeGraphWalksTotalAct); cohesionMenu->addSeparator(); cohesionMenu->addAction(analyzeMatrixReachabilityAct); cohesionMenu->addSeparator(); cohesionMenu->addAction(clusteringCoefAct); analysisMenu->addSeparator(); // CENTRALITIES centrlMenu = new QMenu(tr("Centrality and Prestige indices...")); centrlMenu->setIcon(QIcon(":/images/centrality_48px.svg")); analysisMenu->addMenu(centrlMenu); centrlMenu->addAction(cDegreeAct); centrlMenu->addAction(cClosenessAct); centrlMenu->addAction(cInfluenceRangeClosenessAct); centrlMenu->addAction(cBetweennessAct); centrlMenu->addAction(cStressAct); centrlMenu->addAction(cEccentAct); centrlMenu->addAction(cPowerAct); centrlMenu->addAction(cInformationAct); centrlMenu->addAction(cEigenvectorAct); centrlMenu->addAction(cKatzAct); centrlMenu->addAction(cBonacichAct); centrlMenu->addSeparator(); centrlMenu->addAction(cInDegreeAct); centrlMenu->addAction(cPageRankAct); centrlMenu->addAction(cProximityPrestigeAct); analysisMenu->addSeparator(); // COMMUNITIES & SUBGROUPS communitiesMenu = new QMenu(tr("Communities and Subgroups...")); communitiesMenu->setIcon(QIcon(":/images/communities_48px.svg")); analysisMenu->addMenu(communitiesMenu); communitiesMenu->addAction(analyzeCommunitiesCliquesAct); communitiesMenu->addSeparator(); communitiesMenu->addAction(analyzeCommunitiesTriadCensusAct); analysisMenu->addSeparator(); // STRUCTURAL EQUIVALENCE strEquivalenceMenu = new QMenu(tr("Structural Equivalence...")); strEquivalenceMenu->setIcon(QIcon(":/images/similarity.png")); analysisMenu->addMenu(strEquivalenceMenu); strEquivalenceMenu->addAction(analyzeStrEquivalencePearsonAct); strEquivalenceMenu->addAction(analyzeStrEquivalenceMatchesAct); strEquivalenceMenu->addSeparator(); strEquivalenceMenu->addAction(analyzeStrEquivalenceTieProfileDissimilaritiesAct); strEquivalenceMenu->addSeparator(); strEquivalenceMenu->addAction(analyzeStrEquivalenceClusteringHierarchicalAct); // LAYOUT MENU layoutMenu = menuBar()->addMenu(tr("&Layout")); // colorationMenu = new QPopupMenu(); // layoutMenu->insertItem (tr("Colorization"), colorationMenu); // strongColorationAct->addTo(colorationMenu); // regularColorationAct->addTo(colorationMenu); // layoutMenu->insertSeparator(); randomLayoutMenu = new QMenu(tr("Random...")); randomLayoutMenu->setIcon(QIcon(":/images/random_48px.svg")); layoutMenu->addMenu(randomLayoutMenu); randomLayoutMenu->addAction(layoutRandomAct); randomLayoutMenu->addAction(layoutRandomRadialAct); layoutMenu->addSeparator(); layoutRadialProminenceMenu = new QMenu(tr("Radial by prominence index...")); layoutRadialProminenceMenu->setIcon(QIcon(":/images/radial_layout_48px.svg")); layoutMenu->addMenu(layoutRadialProminenceMenu); layoutRadialProminenceMenu->addAction(layoutRadialProminence_DC_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_CC_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_IRCC_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_BC_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_SC_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_EC_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_PC_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_IC_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_EVC_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_DP_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_PRP_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_PP_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_KATZ_Act); layoutRadialProminenceMenu->addAction(layoutRadialProminence_BPC_Act); layoutMenu->addSeparator(); layoutLevelProminenceMenu = new QMenu(tr("On Levels by prominence index...")); layoutLevelProminenceMenu->setIcon(QIcon(":/images/layout_levels_24px.svg")); layoutMenu->addMenu(layoutLevelProminenceMenu); layoutLevelProminenceMenu->addAction(layoutLevelProminence_DC_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_CC_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_IRCC_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_BC_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_SC_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_EC_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_PC_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_IC_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_EVC_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_DP_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_PRP_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_PP_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_KATZ_Act); layoutLevelProminenceMenu->addAction(layoutLevelProminence_BPC_Act); layoutMenu->addSeparator(); layoutNodeSizeProminenceMenu = new QMenu(tr("Node Size by prominence index...")); layoutNodeSizeProminenceMenu->setIcon(QIcon(":/images/node_size_48px.svg")); layoutMenu->addMenu(layoutNodeSizeProminenceMenu); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_DC_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_CC_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_IRCC_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_BC_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_SC_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_EC_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_PC_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_IC_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_EVC_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_DP_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_PRP_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_PP_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_KATZ_Act); layoutNodeSizeProminenceMenu->addAction(layoutNodeSizeProminence_BPC_Act); layoutMenu->addSeparator(); layoutNodeColorProminenceMenu = new QMenu(tr("Node Color by prominence index...")); layoutNodeColorProminenceMenu->setIcon(QIcon(":/images/color_layout_48px.svg")); layoutMenu->addMenu(layoutNodeColorProminenceMenu); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_DC_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_CC_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_IRCC_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_BC_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_SC_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_EC_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_PC_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_IC_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_EVC_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_DP_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_PRP_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_PP_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_CLC_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_KATZ_Act); layoutNodeColorProminenceMenu->addAction(layoutNodeColorProminence_BPC_Act); layoutMenu->addSeparator(); layoutMenu->addAction(layoutNodeColorByComponentAct); layoutMenu->addSeparator(); layoutForceDirectedMenu = new QMenu(tr("Force-Directed Placement...")); layoutForceDirectedMenu->setIcon(QIcon(":/images/force.png")); layoutMenu->addMenu(layoutForceDirectedMenu); layoutForceDirectedMenu->addAction(layoutFDP_KamadaKawai_Act); layoutForceDirectedMenu->addAction(layoutFDP_FR_Act); layoutForceDirectedMenu->addAction(layoutFDP_Eades_Act); layoutMenu->addSeparator(); layoutMenu->addAction(layoutEgoRadialAct); layoutMenu->addSeparator(); layoutMenu->addAction(layoutGuidesAct); // OPTIONS MENU optionsMenu = menuBar()->addMenu(tr("&Options")); nodeOptionsMenu = new QMenu(tr("Nodes...")); nodeOptionsMenu->setIcon(QIcon(":/images/node_48px.svg")); optionsMenu->addMenu(nodeOptionsMenu); nodeOptionsMenu->addAction(optionsNodeNumbersVisibilityAct); nodeOptionsMenu->addAction(optionsNodeLabelsVisibilityAct); nodeOptionsMenu->addAction(optionsNodeNumbersInsideAct); edgeOptionsMenu = new QMenu(tr("Edges...")); edgeOptionsMenu->setIcon(QIcon(":/images/edges_48px.svg")); optionsMenu->addMenu(edgeOptionsMenu); edgeOptionsMenu->addAction(optionsEdgesVisibilityAct); edgeOptionsMenu->addSeparator(); edgeOptionsMenu->addAction(optionsEdgeWeightNumbersAct); edgeOptionsMenu->addAction(optionsEdgeWeightConsiderAct); edgeOptionsMenu->addAction(optionsEdgeThicknessPerWeightAct); edgeOptionsMenu->addSeparator(); edgeOptionsMenu->addAction(optionsEdgeLabelsAct); edgeOptionsMenu->addSeparator(); edgeOptionsMenu->addAction(optionsEdgeArrowsAct); edgeOptionsMenu->addSeparator(); edgeOptionsMenu->addAction(drawEdgesBezier); viewOptionsMenu = new QMenu(tr("&Canvas...")); viewOptionsMenu->setIcon(QIcon(":/images/view.png")); optionsMenu->addMenu(viewOptionsMenu); viewOptionsMenu->addAction(changeBackColorAct); viewOptionsMenu->addAction(backgroundImageAct); optionsMenu->addSeparator(); optionsMenu->addAction(fullScreenModeAct); optionsMenu->addAction(viewDataTableAct); optionsMenu->addSeparator(); optionsMenu->addAction(openSettingsAct); // HELP MENU helpMenu = menuBar()->addMenu(tr("&Help")); helpMenu->addAction(helpApp); helpMenu->addAction(tipsApp); helpMenu->addSeparator(); helpMenu->addAction(helpCheckUpdatesApp); helpMenu->addSeparator(); helpMenu->addAction(helpSystemInfoAct); helpMenu->addAction(helpAboutApp); helpMenu->addAction(helpAboutQt); qCDebug(lcMainWindow) << "Finished menu bar init."; } /** * @brief Initializes the toolbar */ void MainWindow::initToolBar() { qCDebug(lcMainWindow) << "Initializing toolbar..."; toolBar = addToolBar("operations"); toolBar->addAction(networkNewAct); toolBar->addAction(networkOpenAct); toolBar->addAction(networkSaveAct); toolBar->addAction(networkPrintAct); toolBar->addSeparator(); toolBar->addAction(editMouseModeInteractiveAct); toolBar->addAction(editMouseModeScrollAct); toolBar->addSeparator(); // Create relation select widget // QLabel *labelRelationSelect= new QLabel; // labelRelationSelect->setText(tr("Relations:")); // toolBar->addWidget (labelRelationSelect); toolBar->addAction(editRelationPreviousAct); editRelationChangeCombo = new QComboBox; editRelationChangeCombo->setEditable(true); editRelationChangeCombo->setInsertPolicy(QComboBox::InsertAtCurrent); editRelationChangeCombo->setMinimumWidth(180); editRelationChangeCombo->setCurrentIndex(0); editRelationChangeCombo->setToolTip( tr("

Current relation

" "

To rename the current relation, click here, enter new name and press Enter.

")); editRelationChangeCombo->setStatusTip( tr("Name of the current relation. " "To rename it, enter a new name and press Enter. To select another relation, click the Down arrow (on the right).")); editRelationChangeCombo->setWhatsThis( tr("

Relations combo

" "

This displays the currently selected relation of the network.

" "

To rename the current relation, click on the name, enter a new name and press Enter.

" "

To select another relation (if any), click the Down arrow (on the right).

")); toolBar->addWidget(editRelationChangeCombo); toolBar->addAction(editRelationNextAct); toolBar->addAction(editRelationAddAct); toolBar->addSeparator(); // QLabel *labelEditNodes= new QLabel; // labelEditNodes->setText(tr("Nodes:")); // toolBar->addWidget (labelEditNodes); toolBar->addAction(editNodeAddAct); toolBar->addAction(editNodeRemoveAct); toolBar->addAction(editNodeFindAct); toolBar->addAction(editNodePropertiesAct); toolBar->addAction(filterNodesByCentralityAct); toolBar->addAction(filterNodesByAttributeAct); toolBar->addAction(filterNodesRestoreAllAct); toolBar->addSeparator(); toolBar->addAction(editEdgeAddAct); toolBar->addAction(editEdgeRemoveAct); toolBar->addAction(editEdgePropertiesAct); toolBar->addAction(editFilterEdgesByWeightAct); toolBar->addAction(editFilterEdgesRestoreAllAct); toolBar->addSeparator(); // QLabel *labelApplicationIcons = new QLabel; // labelApplicationIcons->setText(tr("Settings:")); // toolBar->addWidget(labelApplicationIcons); toolBar->addAction(openSettingsAct); toolBar->addSeparator(); toolBar->addAction(QWhatsThis::createAction(this)); toolBar->setIconSize(QSize(16, 16)); qCDebug(lcMainWindow) << "Finished toolbar init."; } SocNetV-3.7/src/mainwindow/init/mainwindow_init_panels.cpp000066400000000000000000001657401524402210300240650ustar00rootroot00000000000000/** * @file mainwindow_init_panels.cpp * @brief Implements MainWindow dock panel construction and window layout. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/filterbarwidget.h" #include "widgets/graphtablewidget.h" #include #include /** * @brief Creates docked panels for instant access to main app functionalities * and displaying statistics */ void MainWindow::initPanels() { qCDebug(lcMainWindow) << "Initializing panels..."; // // create widgets for the Control Panel // QString helpMessage = ""; QLabel *toolBoxNetworkAutoCreateSelectLabel = new QLabel; toolBoxNetworkAutoCreateSelectLabel->setText(tr("Auto Create:")); toolBoxNetworkAutoCreateSelectLabel->setMinimumWidth(90); toolBoxNetworkAutoCreateSelectLabel->setStatusTip( tr("Create a network automatically (famous, random, or by using the web crawler).")); toolBoxNetworkAutoCreateSelect = new QComboBox; toolBoxNetworkAutoCreateSelect->setStatusTip( tr("Create a network automatically (famous, random, or by using the web crawler).")); helpMessage = tr("

Auto network creation

" "

Create a new network automatically.

" "

You may create a random network, recreate famous data-sets " "or use the built-in web crawler to create a network of webpages.

"); toolBoxNetworkAutoCreateSelect->setToolTip(helpMessage); toolBoxNetworkAutoCreateSelect->setWhatsThis(helpMessage); toolBoxNetworkAutoCreateSelect->setToolTip(helpMessage); toolBoxNetworkAutoCreateSelect->setWhatsThis(helpMessage); QStringList networkAutoCreateSelectCommands; networkAutoCreateSelectCommands << "Select" << "Famous data sets" << "Random scale-free" << "Random small-world" << "Random ErdΕ‘s–RΓ©nyi" << "Random lattice" << "Random d-regular" << "Random ring-lattice" << "With Web Crawler"; toolBoxNetworkAutoCreateSelect->addItems(networkAutoCreateSelectCommands); toolBoxNetworkAutoCreateSelect->setMinimumWidth(90); QLabel *toolBoxEditNodeSubgraphSelectLabel = new QLabel; toolBoxEditNodeSubgraphSelectLabel->setText(tr("Subgraph:")); toolBoxEditNodeSubgraphSelectLabel->setMinimumWidth(90); toolBoxEditNodeSubgraphSelectLabel->setStatusTip( tr("Create a basic subgraph with selected nodes.")); toolBoxEditNodeSubgraphSelect = new QComboBox; toolBoxEditNodeSubgraphSelect->setStatusTip( tr("Create a basic subgraph with selected nodes.")); helpMessage = tr("

Subgraph creation

" "

Create a basic subgraph from selected nodes.

" "

Select some nodes with your mouse and then click on one of these" "options to create a basic subgraph with them.

" "

You can create a star, clique, line, etc subgraph.

" "

There must be some nodes selected!

"); toolBoxEditNodeSubgraphSelect->setToolTip(helpMessage); toolBoxEditNodeSubgraphSelect->setWhatsThis(helpMessage); toolBoxEditNodeSubgraphSelectLabel->setToolTip(helpMessage); toolBoxEditNodeSubgraphSelectLabel->setWhatsThis(helpMessage); QStringList editNodeSubgraphCommands; editNodeSubgraphCommands << "Select" << "Clique" << "Star" << "Cycle" << "Line"; toolBoxEditNodeSubgraphSelect->addItems(editNodeSubgraphCommands); toolBoxEditNodeSubgraphSelect->setMinimumWidth(90); QLabel *toolBoxEdgeModeSelectLabel = new QLabel; toolBoxEdgeModeSelectLabel->setText(tr("Edge Mode:")); toolBoxEdgeModeSelectLabel->setMinimumWidth(90); toolBoxEditEdgeModeSelect = new QComboBox; toolBoxEditEdgeModeSelect->setStatusTip( tr("Select the edge mode: directed or undirected.")); helpMessage = tr("

Edge mode

" "

In social networks and graphs, edges can be directed or undirected " "(and the corresponding network is called directed or undirected as well).

" "

This option lets you choose what the kind of edges you want in your network.

" "

By selecting an option here, all edges of the network will change automatically.

" "

For instance, if the network is directed and and you select \"undirected\" " "then all the directed edges will become undirected

"); toolBoxEditEdgeModeSelect->setToolTip(helpMessage); toolBoxEditEdgeModeSelect->setWhatsThis(helpMessage); QStringList edgeModeCommands; edgeModeCommands << "Directed" << "Undirected"; toolBoxEditEdgeModeSelect->addItems(edgeModeCommands); toolBoxEditEdgeModeSelect->setMinimumWidth(120); QLabel *toolBoxEditEdgeTransformSelectLabel = new QLabel; toolBoxEditEdgeTransformSelectLabel->setText(tr("Transform:")); toolBoxEditEdgeTransformSelectLabel->setMinimumWidth(90); toolBoxEditEdgeTransformSelect = new QComboBox; toolBoxEditEdgeTransformSelect->setStatusTip( tr("Select a method to transform the network, i.e. transform all directed edges to undirected.")); helpMessage = tr("

Transform Network Edges

" "

Select a method to transform network edges. Available methods:

" "

Symmetrize All Edges

" "

Forces all edges in this relation to be reciprocated: " "

If there is a directed edge from node A to node B " "then a new directed edge from node B to node A will be " " created, with the same weight.

" "

The result is a symmetric network.

" "

Symmetrize Edges by Strong Ties:

" "

Creates a new symmetric relation by keeping strong ties only.

" "

A tie between actors A and B is considered strong if both A -> B and B -> A exist. " "Therefore, in the new relation, a reciprocated edge will be created between actors A and B " "only if both arcs A->B and B->A were present in the current or all relations.

" "

If the network is multi-relational, it will ask you whether " "ties in the current relation or all relations are to be considered.

" "

Symmetrize Edges by examining Cocitation:

" "

Creates a new symmetric relation by connecting actors " "that are cocitated by others. " "In the new relation, an edge will be created between actor i and " "actor j only if C(i,j) > 0, where C the Cocitation Matrix.

" "

Thus the actor pairs cited by more common neighbors will appear " "with a stronger tie between them than pairs those cited by fewer " "common neighbors. " "The resulting relation is symmetric.

" "

Dichotomize Edges

" "

Creates a new binary relation in a valued network using " "edge dichotomization according to a given threshold value. " "In the new dichotomized relation, an edge will exist between actor i and " "actor j only if e(i,j) > threshold, where threshold is a user-defined value." "The process is also known as compression and slicing.

"); toolBoxEditEdgeTransformSelect->setToolTip(helpMessage); toolBoxEditEdgeTransformSelect->setWhatsThis(helpMessage); QStringList edgeTransformCommands; edgeTransformCommands << "Select" << "Symmetrize All Ties" << "Symmetrize Strong Ties" << "Cocitation Network" << "Edge Dichotomization"; toolBoxEditEdgeTransformSelect->addItems(edgeTransformCommands); toolBoxEditEdgeTransformSelect->setMinimumWidth(120); // create a grid layout for Edit buttons QGridLayout *editGrid = new QGridLayout; editGrid->addWidget(toolBoxNetworkAutoCreateSelectLabel, 0, 0); editGrid->addWidget(toolBoxNetworkAutoCreateSelect, 0, 1); editGrid->addWidget(toolBoxEditNodeSubgraphSelectLabel, 1, 0); editGrid->addWidget(toolBoxEditNodeSubgraphSelect, 1, 1); editGrid->addWidget(toolBoxEdgeModeSelectLabel, 2, 0); editGrid->addWidget(toolBoxEditEdgeModeSelect, 2, 1); editGrid->addWidget(toolBoxEditEdgeTransformSelectLabel, 3, 0); editGrid->addWidget(toolBoxEditEdgeTransformSelect, 3, 1); QLabel *toolBoxFilterSelectLabel = new QLabel; toolBoxFilterSelectLabel->setText(tr("Filter:")); toolBoxFilterSelectLabel->setMinimumWidth(90); toolBoxFilterSelect = new QComboBox; toolBoxFilterSelect->setStatusTip( tr("Select a filter to apply to nodes or edges. Filters are non-destructive and reversible.")); toolBoxFilterSelect->setToolTip( tr("

Filter Nodes / Edges

" "

Apply a non-destructive, reversible filter to the network. Available options:

" "

Filter by Centrality β€” hide nodes below a centrality score threshold.

" "

Filter by Attribute β€” hide nodes or edges whose attribute does not match a condition.

" "

Filter Edges by Weight β€” hide edges below a weight threshold.

" "

Restore All Nodes β€” undo the last node filter.

" "

Restore All Edges β€” undo the last edge filter.

")); QStringList filterCommands; filterCommands << "Select" << "Focus on Node (Ego Network)" << "Focus on Selection" << "Filter Nodes by Centrality" << "Filter Nodes/Edges by Attribute" << "Filter Edges by Weight" << "Restore All Nodes" << "Restore All Edges"; toolBoxFilterSelect->addItems(filterCommands); toolBoxFilterSelect->setMinimumWidth(120); editGrid->addWidget(toolBoxFilterSelectLabel, 4, 0); editGrid->addWidget(toolBoxFilterSelect, 4, 1); editGrid->setSpacing(5); editGrid->setContentsMargins(5, 5, 5, 5); // create a groupbox "Network" - Inside, display the grid layout of widgets QGroupBox *editGroupBox = new QGroupBox(tr("Network")); editGroupBox->setLayout(editGrid); editGroupBox->setMaximumWidth(255); editGroupBox->setMinimumHeight(100); // create widgets for the "Analysis" box QLabel *toolBoxAnalysisMatricesSelectLabel = new QLabel; toolBoxAnalysisMatricesSelectLabel->setText(tr("Matrix:")); toolBoxAnalysisMatricesSelectLabel->setMinimumWidth(90); toolBoxAnalysisMatricesSelect = new QComboBox; toolBoxAnalysisMatricesSelect->setStatusTip( tr("Select which matrix to compute and display, based on the " "adjacency matrix of the current network.")); helpMessage = tr("

Matrix Analysis

" "

Compute and display the adjacency matrix and other matrices " "based on the adjacency matrix of the current network. " "Available options:" "

Adjacency Matrix

" "

Adjacency Matrix Plot

" "

Inverse of Adjacency Matrix

" "

Transpose of Adjacency Matrix

" "

Cocitation Matrix

" "

Degree Matrix

" "

Laplacian Matrix

"); toolBoxAnalysisMatricesSelect->setToolTip(helpMessage); toolBoxAnalysisMatricesSelect->setWhatsThis(helpMessage); QStringList graphMatricesList; graphMatricesList << "Select" << "Adjacency" << "Adjacency Plot" << "Adjacency Inverse" << "Adjacency Transpose" << "Cocitation Matrix" << "Degree Matrix" << "Laplacian Matrix"; toolBoxAnalysisMatricesSelect->addItems(graphMatricesList); toolBoxAnalysisMatricesSelect->setMinimumWidth(120); QLabel *toolBoxAnalysisCohesionSelectLabel = new QLabel; toolBoxAnalysisCohesionSelectLabel->setText(tr("Cohesion:")); toolBoxAnalysisCohesionSelectLabel->setMinimumWidth(90); toolBoxAnalysisCohesionSelect = new QComboBox; toolBoxAnalysisCohesionSelect->setStatusTip( tr("Select a graph-theoretic measure, i.e. distances, walks, graph diameter, eccentricity.")); helpMessage = tr("

Analyze Cohesion

" "

Reciprocity:

" "

Measures the likelihood that pairs of nodes in a directed network are mutually linked.

" "

Symmetry:

" "

Checks if the directed network is symmetric or not.

" "

Distances:

" "

Computes the matrix of geodesic distances between all pairs of nodes.

" "

Average Distance:

" "

Computes the average distance between all nodes.

" "

Graph Diameter:

" "

The maximum distance between any two nodes in the network.

" "

Walks:

" "

A walk is a sequence of edges and vertices (nodes), where " "each edge's endpoints are the two vertices adjacent to it. " "In a walk, vertices and edges may repeat." "

Eccentricity:

" "

The Eccentricity of each node is how far, at most, is from every other actor in the network.

" "

Connectedness:

" "

Checks whether the network is connected, i.e. whether a path exists between every " "pair of nodes. For directed networks, asks whether to check weak connectivity " "(ignoring edge direction) or strong connectivity (respecting it).

" "

Node Connectivity:

" "

The minimum number of nodes that must be removed to disconnect two chosen actors. " "For directed networks, asks whether to respect edge direction (strong) or ignore it " "(weak).

" "

Graph Connectivity:

" "

The network's overall vertex connectivity: the fewest nodes that would need to be " "removed to disconnect it at its weakest point. Same weak/strong choice as Node " "Connectivity for directed networks.

" "

Reachability:

" "

Creates a matrix where an element (i,j) = 1 only if the actors i and j are reachable.

" "

Clustering Coefficient (CLC):

" "

The CLC score of each node is the proportion of actual links " "between its neighbors divided by the number of links that could " "possibly exist between them. " "Quantifies how close each actor and its neighbors are to form " "a complete subgraph (clique)

"); toolBoxAnalysisCohesionSelect->setToolTip(helpMessage); toolBoxAnalysisCohesionSelect->setWhatsThis(helpMessage); QStringList graphPropertiesList; graphPropertiesList << "Select" << "Reciprocity" << "Symmetry" << "Distance" << "Average Distance" << "Geodesic Distribution" << "Distances Matrix" << "Geodesics Matrix" << "Eccentricity" << "Diameter" << "Connectedness" << "Walks of given length" << "Total Walks" << "Reachability Matrix" << "Clustering Coefficient" << "Node Connectivity" << "Graph Connectivity"; toolBoxAnalysisCohesionSelect->addItems(graphPropertiesList); toolBoxAnalysisCohesionSelect->setMinimumWidth(120); QLabel *toolBoxAnalysisProminenceSelectLabel = new QLabel; toolBoxAnalysisProminenceSelectLabel->setText(tr("Prominence:")); toolBoxAnalysisProminenceSelectLabel->setMinimumWidth(90); toolBoxAnalysisProminenceSelect = new QComboBox; toolBoxAnalysisProminenceSelect->setStatusTip( tr("Select a prominence metric to compute for each actor " "and the whole network. ")); helpMessage = tr("

Prominence Analysis

" "

Compute Centrality and Prestige indices, to measure how " "prominent (important) " "each actor (node) is inside the network.

" "

Centrality measures quantify how central is each node by examining " "its ties and its geodesic distances (shortest path lengths) to other nodes. " "Most Centrality indices were designed for undirected graphs.

" "

Prestige indices focus on \"choices received\" to a node. " "These indices measure the nominations or ties to each node from all others (or inLinks). " "Prestige indices are suitable (and can be calculated only) on directed graphs.

" "

Available measures:

" "

Degree Centrality (DC)

" "

The sum of outbound edges or the sum of weights of outbound " "edges from each node i to all adjacent nodes. Note: This is " "the outDegree Centrality. To compute inDegree Centrality, " "use the Degree Prestige measure.

" "

Closeness Centrality (CC):

" "The inverted sum of geodesic distances from each node u " "to all other nodes. " "

IR Closeness Centrality (IRCC):

" "

The ratio of the fraction of nodes reachable by each node u " "to the average distance of these nodes from u.

" "

Betweenness Centrality (BC):

" "

The sum of delta(s,t,u) for all s,t ∈ V where " "delta(s,t,u) is the ratio of all geodesics between nodes " "s and t which run through node u.

" "

Stress Centrality (SC):

" "

The sum of sigma(s,t,u) for all s,t ∈ V where " "sigma(s,t,u) is the number of geodesics between nodes " "s and t which run through node u.

" "

Eccentricity Centrality (EC):

" "

Also known as Harary Graph Centrality. The inverse maximum geodesic distance from node u to " "all other nodes in the network." "

Power Centrality (PC):

" "

The sum of the sizes of all Nth-order neighbourhoods " "of node u with weight 1/n.

" "

Information Centrality (IC):

" "

Measures the information flow through all paths between actors weighted by " "strength of tie and distance.

" "

Eigenvector Centrality (EVC):

" "

The EVC score of each node i is the ith element of the " "leading eigenvector of the adjacency matrix, that is the " "eigenvector corresponding to the largest positive eigenvalue. " "

Degree Prestige (DP):

" "

Also known as InDegree Centrality, it is the sum of inbound edges to a node u " "from all adjacent nodes.

" "

PageRank Prestige (PRP):

" "

For each node u counts all inbound links (edges) to it, but " "it normalizes each inbound link from another node v by the outDegree of v.

" "

Proximity Prestige (PP):

" "

The ratio of the proportion of nodes who can reach each node u " "to the average distance these nodes are from it. Similar to Closeness Centrality " "but it counts only inbound distances to each actor, thus it is a measure of actor prestige.

"); toolBoxAnalysisProminenceSelect->setToolTip(helpMessage); toolBoxAnalysisProminenceSelect->setWhatsThis(helpMessage); // Used in toolBoxAnalysisProminenceSelect and DialogNodeFind prominenceIndexList << "Degree Centrality" << "Closeness Centrality" << "IR Closeness Centrality" << "Betweenness Centrality" << "Stress Centrality" << "Eccentricity Centrality" << "Power Centrality" << "Information Centrality" << "Eigenvector Centrality" << "Katz Centrality" << "Bonacich Power Centrality" << "Degree Prestige" << "PageRank Prestige" << "Proximity Prestige" << "Clustering Coefficient"; // This list's order must match IndexType's enum values exactly, starting at DC=1: // toolBoxLayoutByIndexSelect's currentIndex syncing (mainwindow_layout.cpp's // setCurrentIndex(indexType + 1) calls) and vertexFindByIndexScore()/ // vertexFilterByCentrality() all rely on prominenceIndexList[i] == IndexType(i+1). // Appending a new measure anywhere but matching its IndexType's own position would // silently misalign every entry after it - keep this list and the IndexType enum // (src/global.h) in lockstep whenever either one changes. // Clustering Coefficient is excluded here: toolBoxAnalysisProminenceSelectChanged() // has no case for it (the Cohesion dropdown already offers it, via the same // slotAnalyzeClusteringCoefficient() - a 3rd entry point would be pure duplication). // prominenceIndexList itself keeps it, for Node Find / layout-by-index / filter-by- // centrality, which all handle it correctly via their own dedicated code paths. QStringList prominenceCommands; prominenceCommands << "Select" << prominenceIndexList; prominenceCommands.removeAll("Clustering Coefficient"); toolBoxAnalysisProminenceSelect->addItems(prominenceCommands); toolBoxAnalysisProminenceSelect->setMinimumWidth(120); QLabel *toolBoxAnalysisCommunitiesSelectLabel = new QLabel; toolBoxAnalysisCommunitiesSelectLabel->setText(tr("Communities:")); toolBoxAnalysisCommunitiesSelectLabel->setMinimumWidth(90); toolBoxAnalysisCommunitiesSelect = new QComboBox; toolBoxAnalysisCommunitiesSelect->setStatusTip( tr("Select a community detection measure / cohesive subgroup algorithm, i.e. cliques, triad census etc.")); helpMessage = tr("

Community Analysis

" "

Community detection measures and cohesive subgroup algorithms, " "to identify meaningful subgraphs in the graph.

" "

Available measures

" "

Clique Census:

" "

Computes aggregate counts of all maximal cliques of actors by size, " " actor by clique analysis, clique co-memberships

" "

Triad Census:

" "

Computes the Holland, Leinhardt and Davis triad census, which " "counts all different classes of triads coded according to their" "number of Mutual, Asymmetric and Non-existest dyads (M-A-N scheme)

"); toolBoxAnalysisCommunitiesSelect->setToolTip(helpMessage); toolBoxAnalysisCommunitiesSelect->setWhatsThis(helpMessage); QStringList communitiesCommands; communitiesCommands << "Select" << "Cliques" << "Triad Census"; toolBoxAnalysisCommunitiesSelect->addItems(communitiesCommands); toolBoxAnalysisCommunitiesSelect->setMinimumWidth(120); QLabel *toolBoxAnalysisStrEquivalenceSelectLabel = new QLabel; toolBoxAnalysisStrEquivalenceSelectLabel->setText(tr("Equivalence:")); toolBoxAnalysisStrEquivalenceSelectLabel->setMinimumWidth(90); toolBoxAnalysisStrEquivalenceSelect = new QComboBox; toolBoxAnalysisStrEquivalenceSelect->setStatusTip( tr("Select a method to measure structural equivalence, " "i.e. Pearson Coefficients, tie profile similarities, " "hierarchical clustering, etc.")); helpMessage = tr("

Structural Equivalence Analysis

" "

Select one of the available structural equivalence " "measures and visualization algorithms.

" "

Available options

" "

Pearson Coefficients<.em>

" "

Tie profile similarities

" "

Dissimilarities

" "

Hierarchical Clustering Analysis

"); toolBoxAnalysisStrEquivalenceSelect->setToolTip(helpMessage); toolBoxAnalysisStrEquivalenceSelect->setWhatsThis(helpMessage); QStringList connectivityCommands; connectivityCommands << "Select" << "Pearson Coefficients" << "Similarities" << "Dissimilarities" << "Hierarchical Clustering"; toolBoxAnalysisStrEquivalenceSelect->addItems(connectivityCommands); toolBoxAnalysisStrEquivalenceSelect->setMinimumWidth(120); // create layout for analysis options QGridLayout *analysisGrid = new QGridLayout(); analysisGrid->addWidget(toolBoxAnalysisMatricesSelectLabel, 0, 0); analysisGrid->addWidget(toolBoxAnalysisMatricesSelect, 0, 1); analysisGrid->addWidget(toolBoxAnalysisCohesionSelectLabel, 1, 0); analysisGrid->addWidget(toolBoxAnalysisCohesionSelect, 1, 1); analysisGrid->addWidget(toolBoxAnalysisProminenceSelectLabel, 2, 0); analysisGrid->addWidget(toolBoxAnalysisProminenceSelect, 2, 1); analysisGrid->addWidget(toolBoxAnalysisCommunitiesSelectLabel, 3, 0); analysisGrid->addWidget(toolBoxAnalysisCommunitiesSelect, 3, 1); analysisGrid->addWidget(toolBoxAnalysisStrEquivalenceSelectLabel, 4, 0); analysisGrid->addWidget(toolBoxAnalysisStrEquivalenceSelect, 4, 1); analysisGrid->setSpacing(5); analysisGrid->setContentsMargins(5, 5, 5, 5); // create a box and set the above layout inside QGroupBox *analysisBox = new QGroupBox(tr("Analyze")); analysisBox->setMinimumHeight(120); analysisBox->setMaximumWidth(255); analysisBox->setLayout(analysisGrid); // create widgets for the "Visualization By Index" box QLabel *toolBoxLayoutByIndexSelectLabel = new QLabel; toolBoxLayoutByIndexSelectLabel->setText(tr("Index:")); toolBoxLayoutByIndexSelectLabel->setMinimumWidth(70); toolBoxLayoutByIndexSelect = new QComboBox; toolBoxLayoutByIndexSelect->setStatusTip(tr("Select a prominence-based layout model")); helpMessage = tr("

Visualize by prominence index

" "

Apply a prominence-based layout model to the network.

" "

For instance, you can apply a degree centrality layout.

" "

Note: For each prominence index, you must select a layout type (below).

" "

Available measures:

" "

Degree Centrality (DC)

" "

The sum of outbound edges or the sum of weights of outbound " "edges from each node i to all adjacent nodes. Note: This is " "the outDegree Centrality. To compute inDegree Centrality, " "use the Degree Prestige measure.

" "

Closeness Centrality (CC):

" "The inverted sum of geodesic distances from each node u " "to all other nodes. " "

IR Closeness Centrality (IRCC):

" "

The ratio of the fraction of nodes reachable by each node u " "to the average distance of these nodes from u.

" "

Betweenness Centrality (BC):

" "

The sum of delta(s,t,u) for all s,t ∈ V where " "delta(s,t,u) is the ratio of all geodesics between nodes " "s and t which run through node u.

" "

Stress Centrality (SC):

" "

The sum of sigma(s,t,u) for all s,t ∈ V where " "sigma(s,t,u) is the number of geodesics between nodes " "s and t which run through node u.

" "

Eccentricity Centrality (EC):

" "

Also known as Harary Graph Centrality. The inverse maximum geodesic distance from node u to " "all other nodes in the network." "

Power Centrality (PC):

" "

The sum of the sizes of all Nth-order neighbourhoods " "of node u with weight 1/n.

" "

Information Centrality (IC):

" "

Measures the information flow through all paths between actors weighted by " "strength of tie and distance.

" "

Eigenvector Centrality (EVC):

" "

The EVC score of each node i is the ith element of the " "leading eigenvector of the adjacency matrix, that is the " "eigenvector corresponding to the largest positive eigenvalue. " "

Degree Prestige (DP):

" "

Also known as InDegree Centrality, it is the sum of inbound edges to a node u " "from all adjacent nodes.

" "

PageRank Prestige (PRP):

" "

For each node u counts all inbound links (edges) to it, but " "it normalizes each inbound link from another node v by the outDegree of v.

" "

Proximity Prestige (PP):

" "

The ratio of the proportion of nodes who can reach each node u " "to the average distance these nodes are from it. Similar to Closeness Centrality " "but it counts only inbound distances to each actor, thus it is a measure of actor prestige.

"); toolBoxLayoutByIndexSelect->setToolTip(helpMessage); toolBoxLayoutByIndexSelect->setWhatsThis(helpMessage); QStringList layoutCommandsList; layoutCommandsList << "None" << "Random" << prominenceIndexList; toolBoxLayoutByIndexSelect->addItems(layoutCommandsList); toolBoxLayoutByIndexSelect->setMinimumWidth(100); QLabel *toolBoxLayoutByIndexTypeLabel = new QLabel; toolBoxLayoutByIndexTypeLabel->setText(tr("Type:")); toolBoxLayoutByIndexTypeLabel->setMinimumWidth(70); toolBoxLayoutByIndexTypeSelect = new QComboBox; toolBoxLayoutByIndexTypeSelect->setStatusTip( tr("Select layout type for the selected model")); helpMessage = tr("

Layout Type

" "

Select a layout type (radial, level, node size or node color) " "for the selected prominence-based model you want to apply to the " "network. Please note that node coloring works only for basic shapes " "(box, circle, etc) not for image icons.

"); toolBoxLayoutByIndexTypeSelect->setToolTip(helpMessage); toolBoxLayoutByIndexTypeSelect->setWhatsThis(helpMessage); QStringList layoutTypes; layoutTypes << "None" << "Radial" << "On Levels" << "Node Size" << "Node Color"; toolBoxLayoutByIndexTypeSelect->addItems(layoutTypes); toolBoxLayoutByIndexTypeSelect->setMinimumWidth(100); // create layout for visualisation by index options QGridLayout *layoutByIndexGrid = new QGridLayout(); layoutByIndexGrid->addWidget(toolBoxLayoutByIndexSelectLabel, 0, 0); layoutByIndexGrid->addWidget(toolBoxLayoutByIndexSelect, 0, 1); layoutByIndexGrid->addWidget(toolBoxLayoutByIndexTypeLabel, 1, 0); layoutByIndexGrid->addWidget(toolBoxLayoutByIndexTypeSelect, 1, 1); layoutByIndexGrid->setSpacing(5); layoutByIndexGrid->setContentsMargins(5, 5, 5, 5); // create a box and set the above layout inside QGroupBox *layoutByIndexBox = new QGroupBox(tr("By Prominence Index")); layoutByIndexBox->setMinimumHeight(80); helpMessage = tr("

Visualize by prominence index

" "

Apply a prominence-based layout model to the network.

" "

For instance, you can apply a Degree Centrality layout.

" "

For each prominence index, you must select a layout type:

" "

Radial, Levels, NodeSize or NodeColor.

" "

Please note that node coloring works only for basic shapes " "(box, circle, etc) not for image icons.

"); layoutByIndexBox->setToolTip(helpMessage); layoutByIndexBox->setMaximumWidth(255); layoutByIndexBox->setLayout(layoutByIndexGrid); // create widgets for the "Force-Directed Models" Box QLabel *toolBoxLayoutForceDirectedSelectLabel = new QLabel; toolBoxLayoutForceDirectedSelectLabel->setText(tr("Model:")); toolBoxLayoutForceDirectedSelectLabel->setMinimumWidth(70); toolBoxLayoutForceDirectedSelect = new QComboBox; QStringList modelsList; modelsList << tr("None") << tr("Kamada-Kawai") << tr("Fruchterman-Reingold") << tr("Eades Spring Embedder"); toolBoxLayoutForceDirectedSelect->addItems(modelsList); toolBoxLayoutForceDirectedSelect->setMinimumWidth(100); toolBoxLayoutForceDirectedSelect->setStatusTip( tr("Select a Force-Directed layout model. ")); helpMessage = tr("

Visualize by a Force-Directed Placement layout model.

" "

Available models:

" "

Kamada-Kawai

" "

The best variant of the Spring Embedder family of models. " "

In this the graph is considered to be a dynamic system where " "every edge is between two actors is a 'spring' of a desirable " "length, which corresponds to their graph theoretic distance.

" "

In this way, the optimal layout of the graph \n" "is the state with the minimum imbalance. The degree of " "imbalance is formulated as the total spring energy: " "the square summation of the differences between desirable " "distances and real ones for all pairs of vertices.

" "

Fruchterman-Reingold:

" "

In this model, the vertices behave as atomic particles " "or celestial bodies, exerting attractive and repulsive " "forces to each other. Again, only vertices that are " "neighbours attract each other but, unlike Eades Spring " "Embedder, all vertices repel each other.

" "

Eades Spring Embedder:

" "

A spring-gravitational model, where each node is " "regarded as physical object (ring) repelling all other non-adjacent " "nodes, while springs between connected nodes attract them.

" ); toolBoxLayoutForceDirectedSelect->setToolTip(helpMessage); toolBoxLayoutForceDirectedSelect->setWhatsThis(helpMessage); // create layout for dynamic visualisation QGridLayout *layoutForceDirectedGrid = new QGridLayout(); layoutForceDirectedGrid->addWidget(toolBoxLayoutForceDirectedSelectLabel, 0, 0); layoutForceDirectedGrid->addWidget(toolBoxLayoutForceDirectedSelect, 0, 1); layoutForceDirectedGrid->setSpacing(5); layoutForceDirectedGrid->setContentsMargins(5, 5, 5, 5); // create a box for dynamic layout options QGroupBox *layoutDynamicBox = new QGroupBox(tr("By Force-Directed Model")); layoutDynamicBox->setMinimumHeight(90); layoutDynamicBox->setMaximumWidth(255); layoutDynamicBox->setLayout(layoutForceDirectedGrid); layoutDynamicBox->setContentsMargins(5, 5, 5, 5); // Parent box with vertical layout for all layout/visualization boxes QVBoxLayout *visualizationBoxLayout = new QVBoxLayout; visualizationBoxLayout->addWidget(layoutByIndexBox); visualizationBoxLayout->addWidget(layoutDynamicBox); visualizationBoxLayout->setContentsMargins(5, 5, 5, 5); QGroupBox *visualizationBox = new QGroupBox(tr("Layout")); visualizationBox->setMaximumWidth(255); visualizationBox->setLayout(visualizationBoxLayout); visualizationBox->setContentsMargins(5, 5, 5, 5); // Parent box with vertical layout for all boxes of Controls QGridLayout *controlGrid = new QGridLayout; controlGrid->addWidget(editGroupBox, 0, 0); controlGrid->addWidget(analysisBox, 1, 0); controlGrid->addWidget(visualizationBox, 2, 0); controlGrid->setRowStretch(3, 1); // fix stretch controlGrid->setContentsMargins(5, 5, 5, 5); // create a box with title leftPanel = new QGroupBox(tr("Control Panel")); leftPanel->setMinimumWidth(240); leftPanel->setObjectName("leftPanel"); leftPanel->setLayout(controlGrid); // // Create widgets for Properties/Statistics group/tab // // Helper: uniform collapsible section toggle button auto makeSectionBtn = [this](const QString &title) -> QPushButton * { QPushButton *btn = new QPushButton(tr("β–Ύ ") + title, this); btn->setObjectName("sectionToggleBtn"); btn->setFocusPolicy(Qt::NoFocus); btn->setFlat(true); return btn; }; // ── Network section ─────────────────────────────────────────────────────── m_networkToggleBtn = makeSectionBtn(tr("NETWORK")); QLabel *rightPanelNetworkTypeLabel = new QLabel("Type:"); rightPanelNetworkTypeLabel->setStatusTip( tr("The type of the network: directed or undirected. " "Toggle the menu option Edit->Edges->Undirected Edges to change it")); rightPanelNetworkTypeLabel->setToolTip( tr("The loaded network, if any, is directed and \n" "any link you add between nodes will be a directed arc.\n" "If you want to work with undirected edges and/or \n" "transform the loaded network (if any) to undirected \n" "toggle the option Edit->Edges->Undirected \n" "or press CTRL+E+U")); rightPanelNetworkTypeLCD = new QLabel; rightPanelNetworkTypeLCD->setAlignment(Qt::AlignRight); rightPanelNetworkTypeLCD->setText(tr("Directed")); rightPanelNetworkTypeLCD->setStatusTip( tr("Directed data mode. " "Toggle the menu option Edit->Edges->Undirected Edges to change it")); rightPanelNetworkTypeLCD->setToolTip( tr("The loaded network, if any, is directed and \n" "any link you add between nodes will be a directed arc.\n" "If you want to work with undirected edges and/or \n" "transform the loaded network (if any) to undirected \n" "toggle the option Edit->Edges->Undirected.")); QLabel *rightPanelNodesLabel = new QLabel(tr("Nodes:")); rightPanelNodesLabel->setStatusTip(tr("Each actor in a social network is visualized as a node (aka vertex).")); rightPanelNodesLabel->setToolTip(tr("

Nodes

" "

Total number of actors (nodes/vertices) in this social network.

")); rightPanelNodesLCD = new QLabel; rightPanelNodesLCD->setAlignment(Qt::AlignRight); rightPanelNodesLCD->setStatusTip(tr("The total number of actors (aka nodes or vertices) in the social network.")); rightPanelEdgesLabel = new QLabel(tr("Arcs:")); rightPanelEdgesLabel->setStatusTip(tr("Each link between a pair of actors is visualized as an edge or arc.")); rightPanelEdgesLabel->setToolTip(tr("

Edges

" "Each link between actors is visualized as an undirected edge or a directed arc.")); rightPanelEdgesLCD = new QLabel; rightPanelEdgesLCD->setAlignment(Qt::AlignRight); rightPanelEdgesLCD->setStatusTip(tr("The total number of directed edges in the social network.")); QLabel *rightPanelDensityLabel = new QLabel(tr("Density:")); helpMessage = tr("

Density

" "

The density d is the ratio of existing edges to all possible edges ( n*(n-1) ).

"); rightPanelDensityLabel->setStatusTip(tr("The density d is the ratio of existing edges to all possible edges")); rightPanelDensityLabel->setToolTip(helpMessage); rightPanelDensityLCD = new QLabel; rightPanelDensityLCD->setAlignment(Qt::AlignRight); rightPanelDensityLCD->setStatusTip(tr("The network density, the ratio of existing edges to all possible edges.")); QGridLayout *networkGrid = new QGridLayout; networkGrid->setSpacing(3); networkGrid->setContentsMargins(0, 2, 0, 4); networkGrid->addWidget(rightPanelNetworkTypeLabel, 0, 0); networkGrid->addWidget(rightPanelNetworkTypeLCD, 0, 1); networkGrid->addWidget(rightPanelNodesLabel, 1, 0); networkGrid->addWidget(rightPanelNodesLCD, 1, 1); networkGrid->addWidget(rightPanelEdgesLabel, 2, 0); networkGrid->addWidget(rightPanelEdgesLCD, 2, 1); networkGrid->addWidget(rightPanelDensityLabel, 3, 0); networkGrid->addWidget(rightPanelDensityLCD, 3, 1); m_networkSection = new QWidget; m_networkSection->setLayout(networkGrid); // ── Selection section ───────────────────────────────────────────────────── m_selectionToggleBtn = makeSectionBtn(tr("SELECTION")); QLabel *rightPanelSelectedNodesLabel = new QLabel(tr("Nodes:")); rightPanelSelectedNodesLabel->setStatusTip(tr("Selected nodes.")); rightPanelSelectedNodesLCD = new QLabel("0"); rightPanelSelectedNodesLCD->setAlignment(Qt::AlignRight); rightPanelSelectedNodesLCD->setStatusTip(tr("The number of selected nodes (vertices).")); rightPanelSelectedEdgesLabel = new QLabel(tr("Arcs:")); rightPanelSelectedEdgesLabel->setStatusTip(tr("Selected edges.")); rightPanelSelectedEdgesLCD = new QLabel("0"); rightPanelSelectedEdgesLCD->setAlignment(Qt::AlignRight); rightPanelSelectedEdgesLCD->setStatusTip(tr("The number of selected edges.")); QGridLayout *selectionGrid = new QGridLayout; selectionGrid->setSpacing(3); selectionGrid->setContentsMargins(0, 2, 0, 4); selectionGrid->addWidget(rightPanelSelectedNodesLabel, 0, 0); selectionGrid->addWidget(rightPanelSelectedNodesLCD, 0, 1); selectionGrid->addWidget(rightPanelSelectedEdgesLabel, 1, 0); selectionGrid->addWidget(rightPanelSelectedEdgesLCD, 1, 1); m_selectionSection = new QWidget; m_selectionSection->setLayout(selectionGrid); // ── Clicked Node section ────────────────────────────────────────────────── m_clickedNodeToggleBtn = makeSectionBtn(tr("CLICKED NODE")); QLabel *rightPanelClickedNodeLabel = new QLabel(tr("Number:")); rightPanelClickedNodeLabel->setToolTip(tr("The node number of the last clicked node.")); rightPanelClickedNodeLabel->setStatusTip(tr("The node number of the last clicked node. Zero means no node clicked.")); rightPanelClickedNodeLCD = new QLabel; rightPanelClickedNodeLCD->setAlignment(Qt::AlignRight); rightPanelClickedNodeLCD->setToolTip(tr("Node number of the last clicked node. Zero when nothing is clicked.")); rightPanelClickedNodeInDegreeLabel = new QLabel(tr("In-Degree:")); rightPanelClickedNodeInDegreeLabel->setToolTip(tr("The inDegree of a node is the sum of all inbound edge weights.")); rightPanelClickedNodeInDegreeLCD = new QLabel; rightPanelClickedNodeInDegreeLCD->setAlignment(Qt::AlignRight); rightPanelClickedNodeOutDegreeLabel = new QLabel(tr("Out-Degree:")); rightPanelClickedNodeOutDegreeLabel->setToolTip(tr("The outDegree of a node is the sum of all outbound edge weights.")); rightPanelClickedNodeOutDegreeLCD = new QLabel; rightPanelClickedNodeOutDegreeLCD->setAlignment(Qt::AlignRight); // Detail rows hidden until a node is clicked rightPanelClickedNodeInDegreeLabel->setVisible(false); rightPanelClickedNodeInDegreeLCD->setVisible(false); rightPanelClickedNodeOutDegreeLabel->setVisible(false); rightPanelClickedNodeOutDegreeLCD->setVisible(false); QGridLayout *clickedNodeGrid = new QGridLayout; clickedNodeGrid->setSpacing(3); clickedNodeGrid->setContentsMargins(0, 2, 0, 4); clickedNodeGrid->addWidget(rightPanelClickedNodeLabel, 0, 0); clickedNodeGrid->addWidget(rightPanelClickedNodeLCD, 0, 1); clickedNodeGrid->addWidget(rightPanelClickedNodeInDegreeLabel, 1, 0); clickedNodeGrid->addWidget(rightPanelClickedNodeInDegreeLCD, 1, 1); clickedNodeGrid->addWidget(rightPanelClickedNodeOutDegreeLabel, 2, 0); clickedNodeGrid->addWidget(rightPanelClickedNodeOutDegreeLCD, 2, 1); m_clickedNodeSection = new QWidget; m_clickedNodeSection->setLayout(clickedNodeGrid); // ── Clicked Edge section ────────────────────────────────────────────────── m_clickedEdgeToggleBtn = makeSectionBtn(tr("CLICKED EDGE")); rightPanelClickedEdgeNameLabel = new QLabel(tr("Name:")); rightPanelClickedEdgeNameLabel->setToolTip(tr("The name of the last clicked edge.")); rightPanelClickedEdgeNameLCD = new QLabel; rightPanelClickedEdgeNameLCD->setAlignment(Qt::AlignRight); rightPanelClickedEdgeWeightLabel = new QLabel(tr("Weight:")); rightPanelClickedEdgeWeightLabel->setStatusTip(tr("The weight of the clicked edge.")); rightPanelClickedEdgeWeightLCD = new QLabel; rightPanelClickedEdgeWeightLCD->setAlignment(Qt::AlignRight); rightPanelClickedEdgeReciprocalWeightLabel = new QLabel; rightPanelClickedEdgeReciprocalWeightLabel->setToolTip(tr("The weight of the reciprocal edge.")); rightPanelClickedEdgeReciprocalWeightLCD = new QLabel; rightPanelClickedEdgeReciprocalWeightLCD->setAlignment(Qt::AlignRight); // Detail rows hidden until an edge is clicked rightPanelClickedEdgeWeightLabel->setVisible(false); rightPanelClickedEdgeWeightLCD->setVisible(false); rightPanelClickedEdgeReciprocalWeightLabel->setVisible(false); rightPanelClickedEdgeReciprocalWeightLCD->setVisible(false); QGridLayout *clickedEdgeGrid = new QGridLayout; clickedEdgeGrid->setSpacing(3); clickedEdgeGrid->setContentsMargins(0, 2, 0, 4); clickedEdgeGrid->addWidget(rightPanelClickedEdgeNameLabel, 0, 0); clickedEdgeGrid->addWidget(rightPanelClickedEdgeNameLCD, 0, 1); clickedEdgeGrid->addWidget(rightPanelClickedEdgeWeightLabel, 1, 0); clickedEdgeGrid->addWidget(rightPanelClickedEdgeWeightLCD, 1, 1); clickedEdgeGrid->addWidget(rightPanelClickedEdgeReciprocalWeightLabel, 2, 0); clickedEdgeGrid->addWidget(rightPanelClickedEdgeReciprocalWeightLCD, 2, 1); m_clickedEdgeSection = new QWidget; m_clickedEdgeSection->setLayout(clickedEdgeGrid); // ── Distribution (chart) section ────────────────────────────────────────── m_chartToggleBtn = makeSectionBtn(tr("DISTRIBUTION")); miniChart = new Chart(this); int chartHeight = 100; miniChart->setThemeSmallWidget(chartHeight, chartHeight); // ── Outer grid ──────────────────────────────────────────────────────────── QGridLayout *propertiesGrid = new QGridLayout; propertiesGrid->setSpacing(2); propertiesGrid->setContentsMargins(4, 4, 4, 4); propertiesGrid->addWidget(m_networkToggleBtn, 0, 0); propertiesGrid->addWidget(m_networkSection, 1, 0); propertiesGrid->addWidget(m_selectionToggleBtn, 2, 0); propertiesGrid->addWidget(m_selectionSection, 3, 0); propertiesGrid->addWidget(m_clickedNodeToggleBtn, 4, 0); propertiesGrid->addWidget(m_clickedNodeSection, 5, 0); propertiesGrid->addWidget(m_clickedEdgeToggleBtn, 6, 0); propertiesGrid->addWidget(m_clickedEdgeSection, 7, 0); propertiesGrid->addWidget(m_chartToggleBtn, 8, 0); propertiesGrid->addWidget(miniChart, 9, 0); propertiesGrid->setRowMinimumHeight(9, (int)floor(1.5 * chartHeight)); propertiesGrid->setRowStretch(10, 1); // Toggle connections auto connectToggle = [](QPushButton *btn, QWidget *section, const QString &title) { QObject::connect(btn, &QPushButton::clicked, btn, [btn, section, title]() { const bool show = !section->isVisible(); section->setVisible(show); btn->setText((show ? QString("β–Ύ ") : QString("β–΄ ")) + title); }); }; connectToggle(m_networkToggleBtn, m_networkSection, tr("NETWORK")); connectToggle(m_selectionToggleBtn, m_selectionSection, tr("SELECTION")); connectToggle(m_clickedNodeToggleBtn, m_clickedNodeSection, tr("CLICKED NODE")); connectToggle(m_clickedEdgeToggleBtn, m_clickedEdgeSection, tr("CLICKED EDGE")); connectToggle(m_chartToggleBtn, miniChart, tr("DISTRIBUTION")); // Create a panel with title rightPanel = new QGroupBox(tr("Statistics Panel")); rightPanel->setMinimumWidth(170); rightPanel->setMaximumWidth(190); rightPanel->setObjectName("rightPanel"); rightPanel->setLayout(propertiesGrid); qCDebug(lcMainWindow) << "Finished panels init."; } /** * @brief Initializes the application window layout * * Creates helper widgets and sets the main layout of the MainWindow */ void MainWindow::initWindowLayout() { qCDebug(lcMainWindow) << "Initializing window layout..."; int size = style()->pixelMetric(QStyle::PM_ToolBarIconSize); QSize iconSize(size, size); iconSize.setHeight(16); iconSize.setWidth(16); // // Zoom slider // zoomInBtn = new QToolButton; zoomInBtn->setToolTip(tr("Zoom in the network.")); zoomInBtn->setStatusTip(tr("Zoom in the network. Or press Cltr and use mouse wheel.")); zoomInBtn->setWhatsThis(tr("Zoom In.\n\n" "Zooms in the network (Ctrl++)." "You can also press Cltr and use the mouse wheel.")); zoomInBtn->setAutoRepeat(true); zoomInBtn->setAutoRepeatInterval(33); zoomInBtn->setAutoRepeatDelay(0); zoomInBtn->setIcon(QPixmap(":/images/zoom_in_24px.svg")); zoomInBtn->setIconSize(iconSize); zoomOutBtn = new QToolButton; zoomOutBtn->setAutoRepeat(true); zoomOutBtn->setToolTip(tr("Zoom out.")); zoomOutBtn->setStatusTip(tr("Zoom out of the actual network. Or press Cltr and use mouse wheel.")); zoomOutBtn->setWhatsThis(tr("Zoom out.\n\n" "Zooms out of the actual network. (Ctrl+-)" "You can also press Cltr and use the mouse wheel.")); zoomOutBtn->setAutoRepeat(true); zoomOutBtn->setAutoRepeatInterval(33); zoomOutBtn->setAutoRepeatDelay(0); zoomOutBtn->setIcon(QPixmap(":/images/zoom_out_24px.svg")); zoomOutBtn->setIconSize(iconSize); zoomSlider = new QSlider; zoomSlider->setMinimum(0); zoomSlider->setMaximum(maxZoomIndex); zoomSlider->setValue((int)maxZoomIndex / 2.0); zoomSlider->setToolTip(tr("Zoom slider: Drag up to zoom in. \n" "Drag down to zoom out. ")); zoomSlider->setWhatsThis(tr("Zoom slider: Drag up to zoom in. \n" "Drag down to zoom out. ")); zoomSlider->setTickPosition(QSlider::TicksBothSides); // Zoom slider layout QVBoxLayout *zoomSliderLayout = new QVBoxLayout; zoomSliderLayout->addWidget(zoomInBtn); zoomSliderLayout->addWidget(zoomSlider); zoomSliderLayout->addWidget(zoomOutBtn); // // Rotate slider // rotateLeftBtn = new QToolButton; rotateLeftBtn->setAutoRepeat(true); rotateLeftBtn->setIcon(QPixmap(":/images/rotate_left_48px.svg")); rotateLeftBtn->setToolTip(tr("Rotates the canvas counterclockwise")); rotateLeftBtn->setStatusTip(tr("Rotate counterclockwise")); rotateLeftBtn->setWhatsThis(tr("Rotates the canvas counterclockwise.")); rotateLeftBtn->setIconSize(iconSize); rotateRightBtn = new QToolButton; rotateRightBtn->setAutoRepeat(true); rotateRightBtn->setIcon(QPixmap(":/images/rotate_right_48px.svg")); rotateRightBtn->setToolTip(tr("Rotates the canvas clockwise.")); rotateRightBtn->setStatusTip(tr("Rotate clockwise")); rotateRightBtn->setWhatsThis(tr("Rotates the canvas clockwise.")); rotateRightBtn->setIconSize(iconSize); rotateSlider = new QSlider; rotateSlider->setOrientation(Qt::Horizontal); rotateSlider->setMinimum(-180); rotateSlider->setMaximum(180); rotateSlider->setTickInterval(5); rotateSlider->setValue(0); rotateSlider->setToolTip(tr("Rotate slider: Drag to left to rotate clockwise. \n" "Drag to right to rotate counterclockwise. ")); rotateSlider->setWhatsThis(tr("Rotate slider: Drag to left to rotate clockwise. " "Drag to right to rotate counterclockwise. ")); rotateSlider->setTickPosition(QSlider::TicksBothSides); // Rotate slider layout QHBoxLayout *rotateSliderLayout = new QHBoxLayout; rotateSliderLayout->addWidget(rotateLeftBtn); rotateSliderLayout->addWidget(rotateSlider); rotateSliderLayout->addWidget(rotateRightBtn); resetSlidersBtn = new QToolButton; resetSlidersBtn->setText(tr("Reset")); resetSlidersBtn->setShortcut(Qt::CTRL | Qt::Key_0); resetSlidersBtn->setStatusTip(tr("Reset zoom and rotation to zero (or press Ctrl+0)")); resetSlidersBtn->setToolTip(tr("Reset zoom and rotation to zero (Ctrl+0)")); resetSlidersBtn->setWhatsThis(tr("Reset zoom and rotation to zero (Ctrl+0)")); resetSlidersBtn->setIcon(QPixmap(":/images/refresh_48px.svg")); resetSlidersBtn->setIconSize(iconSize); resetSlidersBtn->setEnabled(true); // Filter bar β€” sits between toolbar and canvas, hidden when no filter is active m_filterBar = new FilterBarWidget(this); // Wrap filter bar + canvas in a vertical container for grid cell [0,1] QVBoxLayout *canvasVBox = new QVBoxLayout; canvasVBox->setContentsMargins(0, 0, 0, 0); canvasVBox->setSpacing(0); canvasVBox->addWidget(m_filterBar); canvasVBox->addWidget(graphicsWidget, 1); // Wrap the left panel in a scroll area so it scrolls vertically when // the bottom dock (Data Table) reduces available height, preventing widget overlap. m_leftScrollArea = new QScrollArea; m_leftScrollArea->setWidget(leftPanel); m_leftScrollArea->setWidgetResizable(true); m_leftScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_leftScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_leftScrollArea->setFrameShape(QFrame::NoFrame); m_leftScrollArea->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); m_leftScrollArea->setMinimumWidth(leftPanel->minimumWidth() + 4); // Create a layout for the toolbox and the canvas. // This will be the layout of our MW central widget QGridLayout *layout = new QGridLayout; layout->addWidget(m_leftScrollArea, 0, 0, 2, 1); layout->addLayout(canvasVBox, 0, 1); layout->addLayout(zoomSliderLayout, 0, 2); layout->addWidget(rightPanel, 0, 3, 2, 1); layout->addLayout(rotateSliderLayout, 1, 1, 1, 1); layout->addWidget(resetSlidersBtn, 1, 2, 1, 1); // create a dummy widget, and set the above layout QWidget *widget = new QWidget; widget->setLayout(layout); // now set this as central widget of MW setCentralWidget(widget); // Data Table dock β€” docked at the bottom, hidden by default m_tableWidget = new GraphTableWidget(this); m_tableWidget->setNodeShapeLists(nodeShapeList, iconPathList); m_tableDock = new QDockWidget(tr("Data Table"), this); m_tableDock->setObjectName("tableDock"); m_tableDock->setWidget(m_tableWidget); m_tableDock->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea); // Minimum height prevents the dock from collapsing to zero when the splitter is dragged m_tableWidget->setMinimumHeight(80); addDockWidget(Qt::BottomDockWidgetArea, m_tableDock); m_tableDock->hide(); connect(m_tableDock, &QDockWidget::visibilityChanged, viewDataTableAct, &QAction::setChecked); // set panels visibility if (appSettings["showRightPanel"] == "false") { slotOptionsWindowRightPanelVisibility(false); } if (appSettings["showLeftPanel"] == "false") { slotOptionsWindowLeftPanelVisibility(false); } // // Load our default stylesheet, if set in the app settings. // if (appSettings["useCustomStyleSheet"] == "true") { slotStyleSheetByName(":/qss/default.qss"); } qCDebug(lcMainWindow) << "Finished window layout init."; } SocNetV-3.7/src/mainwindow/init/mainwindow_init_settings.cpp000066400000000000000000000337701524402210300244400ustar00rootroot00000000000000/** * @file mainwindow_init_settings.cpp * @brief Implements MainWindow settings load/save and last-used-path bookkeeping. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "chart.h" #include "widgets/filterbarwidget.h" #include "widgets/graphtablewidget.h" #include "forms/dialogpreviewfile.h" #include #include #include /** * @brief Reads user-defined settings (or uses defaults) and initializes some app settings */ QMap MainWindow::initSettings(const int &debugLevel, const bool &forceProgress) { qCDebug(lcMainWindow) << "Initializing settings - debugLevel" << debugLevel; // // Read used-defined settings or use defaults // // App settings are always saved to this folder. settingsDir = QDir::homePath() + QDir::separator() + "socnetv-data" + QDir::separator(); settingsFilePath = settingsDir + "settings.conf"; // dataDir is where our built-in datasets and reports are saved by default // initially dataDir and settingsDir are the same, but dataDir may be // changed by the user through Settings... QString dataDir = settingsDir; // hard-coded default settings to use only on first app load, // when there are no user defined values appSettings["initNodesEstimatedSize"] = "5000"; appSettings["initEdgesPerNodeEstimatedSize"] = "500"; appSettings["initNodeSize"] = "10"; appSettings["initNodeColor"] = "red"; appSettings["initNodeShape"] = "circle"; appSettings["initNodeIconPath"] = ""; appSettings["initNodeNumbersVisibility"] = "true"; appSettings["initNodeNumberSize"] = "0"; appSettings["initNodeNumberColor"] = "#333"; appSettings["initNodeNumbersInside"] = "true"; appSettings["initNodeNumberDistance"] = "2"; appSettings["initNodeLabelsVisibility"] = "false"; appSettings["initNodeLabelSize"] = "8"; appSettings["initNodeLabelColor"] = "#8d8d8d"; appSettings["initNodeLabelDistance"] = "6"; appSettings["initEdgesVisibility"] = "true"; appSettings["initEdgeShape"] = "line"; // bezier appSettings["initEdgeColor"] = "#666666"; appSettings["initEdgeColorNegative"] = "red"; appSettings["initEdgeColorZero"] = "blue"; appSettings["showZeroWeightEdges"] = "true"; // #30: show by default; user can disable in Settings appSettings["initEdgeArrows"] = "true"; appSettings["initEdgeArrowSize"] = "6"; appSettings["initEdgeOffsetFromNode"] = "7"; appSettings["initEdgeThicknessPerWeight"] = "true"; appSettings["initEdgeWeightNumbersVisibility"] = "false"; appSettings["initEdgeWeightNumberSize"] = "7"; appSettings["initEdgeWeightNumberColor"] = "#00aa00"; appSettings["initEdgeLabelsVisibility"] = "false"; appSettings["initBackgroundColor"] = "white"; //"gainsboro"; appSettings["initBackgroundImage"] = ""; appSettings["printDebug"] = "false"; appSettings["viewReportsInSystemBrowser"] = "true"; appSettings["showProgressBar"] = "true"; appSettings["showToolBar"] = "true"; appSettings["showStatusBar"] = "true"; appSettings["useCustomStyleSheet"] = "true"; appSettings["opengl"] = "true"; appSettings["antialiasing"] = "true"; appSettings["canvasAntialiasingAutoAdjustment"] = "true"; appSettings["canvasSmoothPixmapTransform"] = "true"; appSettings["canvasPainterStateSave"] = "false"; appSettings["canvasCacheBackground"] = "false"; appSettings["canvasUpdateMode"] = "Full"; appSettings["canvasIndexMethod"] = "NoIndex"; appSettings["canvasEdgeHighlighting"] = "true"; appSettings["canvasNodeHighlighting"] = "true"; appSettings["dataDir"] = dataDir; appSettings["lastUsedDirPath"] = dataDir; appSettings["showRightPanel"] = "true"; appSettings["showLeftPanel"] = "true"; appSettings["printLogo"] = "true"; appSettings["initStatusBarDuration"] = "5000"; appSettings["randomErdosEdgeProbability"] = "0.04"; appSettings["initReportsRealNumberPrecision"] = "6"; appSettings["initReportsLabelsLength"] = "16"; appSettings["initReportsChartType"] = "0"; appSettings["initReportsOutputFormat"] = "0"; appSettings["saveZeroWeightEdges"] = "false"; // Try to load settings from previously-saved file // First check if our settings folder exist QDir socnetvDir(settingsDir); if (!socnetvDir.exists()) { qCDebug(lcMainWindow) << "socnetv settings dir does not exist. Creating it..."; socnetvDir.mkdir(settingsDir); } // Then check if the settings file exists inside the folder if (!socnetvDir.exists(settingsFilePath)) { qCDebug(lcMainWindow) << "Settings file does not exist. Creating it with defaults at: " << settingsFilePath; saveSettings(); } else { qCDebug(lcMainWindow) << "Settings file exist. Reading it..."; QFile file(settingsFilePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qCDebug(lcMainWindow) << "Could not open (for reading) file:" << settingsFilePath; slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error loading settings file"), tr("Error loading settings"), tr("Error! \n" "I cannot read the settings file " "in \n %1 \n" "You can continue using SocNetV with default " "settings but any changes to them will not " " be saved for future sessions \n" "Please, check permissions in your home folder " " and contact the developer team.") .arg(settingsFilePath.toLocal8Bit())); return appSettings; } // Read the previously-stored settings from the file and update appSettings QTextStream in(&file); QStringList setting; while (!in.atEnd()) { QString line = in.readLine(); if (!line.isEmpty()) { setting = line.simplified().split('='); if (setting[0].simplified().startsWith("recentFile_")) { recentFiles += setting[1].simplified(); } else { appSettings.insert(setting[0].simplified(), setting[1].simplified()); } } } file.close(); // Migration 2: switch old BspTreeIndex default β†’ NoIndex (better for large dynamic scenes) if (appSettings.value("settingsMigration", "0").toInt() < 2) { if (appSettings["canvasIndexMethod"] == "BspTreeIndex") appSettings["canvasIndexMethod"] = "NoIndex"; appSettings["settingsMigration"] = "2"; } // Migration 3: force-enable OpenGL once for existing users with a stale "opengl = false" // setting. setOptionsOpenGL() already no-ops safely if the Qt build has no OpenGL support, // so this is safe even when OpenGL isn't actually available. Users can still turn it back // off afterward; this only overrides the setting once. if (appSettings.value("settingsMigration", "0").toInt() < 3) { appSettings["opengl"] = "true"; appSettings["settingsMigration"] = "3"; } } // Override progress bar setting if the user has requested it (through a command-line parameter) appSettings["showProgressBar"] = forceProgress ? "true" : appSettings["showProgressBar"]; // Override debug messages setting if the user has requested it (through a command-line parameter) if (debugLevel > 0) { appSettings["printDebug"] = "true"; } else { // debugLevel == 0 (-d 0) or -1 (no -d flag at all): WS14 L1 -- a no-flag launch must // come up quiet regardless of whatever "print debug messages" persisted to // settings.conf from a previous Settings-dialog session. Without this, a single past // toggle-on silently makes every future no-flag launch pay the full qDebug() cost // forever, which is exactly the release-build-hygiene bug this milestone exists to // close. The Settings dialog checkbox still works fine as a live, in-session toggle. appSettings["printDebug"] = "false"; slotOptionsDebugMessages(false); } if (appSettings["printDebug"] == "true") { slotOptionsDebugMessages(true); } else { slotOptionsDebugMessages(false); } // // Create fortune cookies and tips // createFortuneCookies(); slotHelpCreateTips(); // // Populate icons and shapes lists // // Note: When you add a new shape and icon, you must also: // 1. Add a new enum in NodeShape (global.h) // 2. Add a new branch in GraphicsNode::setShape() and paint() // 3. Add a new branch in DialogNodeEdit: getNodeShape() and getUserChoices() nodeShapeList << "box" << "circle" << "diamond" << "ellipse" << "triangle" << "star" << "person" << "person-b" << "bugs" << "heart" << "dice" << "custom"; iconPathList << ":/images/box.png" << ":/images/circle.png" << ":/images/diamond.png" << ":/images/ellipse.png" << ":/images/triangle.png" << ":/images/star.png" << ":/images/person.svg" << ":/images/person-bw.svg" << ":/images/bugs.png" << ":/images/heart.svg" << ":/images/random.png" << ":/images/export_photo_48px.svg"; // Max nodes used by createRandomNetwork dialogues maxRandomlyCreatedNodes = 5000; // // Initialize list of supported text codecs and prepare the preview file dialog // qCDebug(lcMainWindow) << "initializing text codecs list.."; initNetworkAvailableTextCodecs(); qCDebug(lcMainWindow) << "creating preview file dialog and passing the codecs list: " << codecs; m_dialogPreviewFile = new DialogPreviewFile(this); m_dialogPreviewFile->setCodecList(codecs); connect(m_dialogPreviewFile, &DialogPreviewFile::loadNetworkFileWithCodec, this, &MainWindow::slotNetworkFileLoad); // return the setting return appSettings; } /** * @brief Saves default (or user-defined) app settings */ void MainWindow::saveSettings() { qCDebug(lcMainWindow) << "Saving app settings to file: " << settingsFilePath; QFile file(settingsFilePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCDebug(lcMainWindow) << "Could not open (for writing) file:" << settingsFilePath; slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error writing settings file"), tr("Error writing settings"), tr("I cannot write the settings file " "in \n %1 \n" "You can continue using SocNetV with default " "settings but any changes to them will not " " be saved for future sessions \n" "Please, check permissions in your home folder " " and contact the developer team.") .arg(settingsFilePath.toLocal8Bit())); return; } QTextStream out(&file); qCDebug(lcMainWindow) << "Writing settings to settings file first "; QMap::const_iterator it = appSettings.constBegin(); while (it != appSettings.constEnd()) { out << it.key() << " = " << it.value() << "\n"; ++it; } // save recent files for (int i = 0; i < recentFiles.size(); ++i) { out << "recentFile_" + QString::number(i + 1) << " = " << recentFiles.at(i) << "\n"; } file.close(); } /** * @brief Returns the last path used by user to open/save something */ QString MainWindow::getLastPath() { if (appSettings["lastUsedDirPath"] == "socnetv-initial-none") { appSettings["lastUsedDirPath"] = appSettings["dataDir"]; } qCDebug(lcMainWindow) << "Last path used: " << appSettings["lastUsedDirPath"]; return appSettings["lastUsedDirPath"]; } /** * @brief Sets the last path used by user to open/save a network and adds the file * to recent files... * @param filePath */ void MainWindow::setLastPath(const QString &filePath) { qCDebug(lcMainWindow) << "Setting last path and adding to recent files:" << filePath; QString currentPath = QFileInfo(filePath).dir().absolutePath(); QDir::setCurrent(currentPath); appSettings["lastUsedDirPath"] = currentPath; if (!QFileInfo(filePath).completeSuffix().toLower().contains("bmp") && !QFileInfo(filePath).completeSuffix().toLower().contains("jpg") && !QFileInfo(filePath).completeSuffix().toLower().contains("png") && !QFileInfo(filePath).completeSuffix().toLower().contains("pdf")) { recentFiles.removeAll(filePath); recentFiles.prepend(filePath); while (recentFiles.size() > MaxRecentFiles) recentFiles.removeLast(); } slotNetworkFileRecentUpdateActions(); saveSettings(); } SocNetV-3.7/src/mainwindow/layout/000077500000000000000000000000001524402210300171555ustar00rootroot00000000000000SocNetV-3.7/src/mainwindow/layout/mainwindow_layout.cpp000066400000000000000000000765431524402210300234510ustar00rootroot00000000000000/** * @file mainwindow_layout.cpp * @brief Implements MainWindow Layout menu actions: colorization, random/radial/spring-embedder/Fruchterman-Reingold/Kamada-Kawai layouts, layouts by prominence index, and the layout toolbox apply buttons. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "forms/dialogsettings.h" #include /** * @brief Called when the user selects a Prominence index in the Layout selectbox * of the Control Panel. */ void MainWindow::toolBoxLayoutByIndexApplyBtnPressed() { qCDebug(lcMainWindow) << "User request to apply prominence-based layout..."; int selectedIndex = toolBoxLayoutByIndexSelect->currentIndex(); QString selectedIndexText = toolBoxLayoutByIndexSelect->currentText(); int selectedLayoutType = toolBoxLayoutByIndexTypeSelect->currentIndex(); qCDebug(lcMainWindow) << "elected index is " << selectedIndexText << " : " << selectedIndex << " selected layout type is " << selectedLayoutType; switch (selectedIndex) { case 0: // None break; case 1: // Random if (selectedLayoutType == 1) slotLayoutRadialRandom(); else if (selectedLayoutType == 2) slotLayoutRandom(); break; default: if (selectedLayoutType == 0) // None type β€” do nothing break; else if (selectedLayoutType == 1) // Radial slotLayoutRadialByProminenceIndex(selectedIndexText); else if (selectedLayoutType == 2) // On Levels slotLayoutLevelByProminenceIndex(selectedIndexText); else if (selectedLayoutType == 3) // Node Size slotLayoutNodeSizeByProminenceIndex(selectedIndexText); else if (selectedLayoutType == 4) // Node Color slotLayoutNodeColorByProminenceIndex(selectedIndexText); break; }; } /** * @brief Called when the user selects a model in the Layout by Force Directed * selectbox of left panel. */ void MainWindow::toolBoxLayoutForceDirectedApplyBtnPressed() { qCDebug(lcMainWindow) << "User selected to apply a FDP layout..."; int selectedModel = toolBoxLayoutForceDirectedSelect->currentIndex(); QString selectedModelText = toolBoxLayoutForceDirectedSelect->currentText(); qCDebug(lcMainWindow) << " selected index is " << selectedModelText << " : " << selectedModel; switch (selectedModel) { case 0: break; case 1: slotLayoutGuides(false); slotLayoutKamadaKawai(); break; case 2: slotLayoutGuides(false); slotLayoutFruchterman(); break; case 3: slotLayoutGuides(false); slotLayoutSpringEmbedder(); break; default: toolBoxLayoutForceDirectedSelect->blockSignals(true); toolBoxLayoutForceDirectedSelect->setCurrentIndex(0); toolBoxLayoutForceDirectedSelect->blockSignals(false); break; }; // FD model controls node positions β€” reset prominence Type if it was // Radial (1) or On Levels (2), but leave Node Size/Color (3/4) intact. if (selectedModel > 0) { int currentType = toolBoxLayoutByIndexTypeSelect->currentIndex(); if (currentType == 1 || currentType == 2) { toolBoxLayoutByIndexTypeSelect->blockSignals(true); toolBoxLayoutByIndexTypeSelect->setCurrentIndex(0); toolBoxLayoutByIndexTypeSelect->blockSignals(false); } } } /** TODO slotLayoutColorationStrongStructural */ void MainWindow::slotLayoutColorationStrongStructural() { } /** TODO slotLayoutColorationRegular */ void MainWindow::slotLayoutColorationRegular() { } /** * @brief Calls Graph::layoutRandom * to reposition all nodes on a random layout */ void MainWindow::slotLayoutRandom() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } graphicsWidget->clearGuides(); runGraphOperationAsync( [this]() { activeGraph->layoutRandom(); }, tr("Placing nodes in random positions. Please wait..."), tr("Nodes in random positions.")); } /** * @brief Calls Graph::layoutRadialRandom * to reposition all nodes on a radial layout randomly */ void MainWindow::slotLayoutRadialRandom() { qCDebug(lcMainWindow) << "MainWindow::slotLayoutRadialRandom()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } slotLayoutGuides(false); runGraphOperationAsync( [this]() { activeGraph->layoutRadialRandom(true); }, tr("Placing nodes in random concentric circles. Please wait..."), [this]() { slotLayoutGuides(true); statusMessage(tr("Nodes in random concentric circles.")); }); } /** * @brief Calls Graph::layoutEgoRadial. * Resolves the ego vertex from the current selection (exactly one selected) * or from the last-clicked vertex, whichever is available. */ void MainWindow::slotLayoutEgoRadial() { qCDebug(lcMainWindow) << "MainWindow::slotLayoutEgoRadial()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } int egoVertex = 0; if (activeGraph->getSelectedVerticesCount() == 1) egoVertex = activeGraph->getSelectedVertices().first(); else if (activeGraph->vertexClicked() != 0) egoVertex = activeGraph->vertexClicked(); else { statusMessage(tr("Please select or click one node first.")); return; } runGraphOperationAsync( [this, egoVertex]() { activeGraph->layoutEgoRadial(egoVertex); }, tr("Computing ego radial layout. Please wait..."), [this, egoVertex]() { statusMessage(tr("Ego radial layout centered on vertex %1.").arg(egoVertex)); }); } /** * @brief Embed the Eades spring-gravitational model to the network. * Called from menu or toolbox checkbox. */ void MainWindow::slotLayoutSpringEmbedder() { qCDebug(lcMainWindow) << "MW::slotLayoutSpringEmbedder"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } // Eades (1984) designed this algorithm for small graphs (N < 30). // For larger graphs the system frequently gets stuck in local minima // and the O(NΒ²Γ—I) complexity makes it slow. Warn the user. if (activeNodes() > 30) { QMessageBox::warning( this, tr("Eades Spring Embedder β€” Size Warning"), tr("The Eades Spring Embedder was designed for graphs with fewer than 30 nodes.\n\n" "This network has %1 nodes. The layout may get stuck in local minima " "and the result may not be aesthetically pleasing.\n\n" "Consider using the Fruchterman-Reingold or Kamada-Kawai models instead, " "which handle larger graphs better.") .arg(activeNodes())); } runGraphOperationAsync( [this]() { activeGraph->layoutForceDirectedSpringEmbedder(300); }, tr("Computing Spring-Gravitational (Eades) layout. Please wait..."), tr("Spring-Gravitational (Eades) model embedded.")); } /** * @brief Calls Graph::layoutForceDirectedFruchtermanReingold to embed * the Fruchterman-Reingold model of repelling-attracting forces to the network. * Called from menu or toolbox */ void MainWindow::slotLayoutFruchterman() { qCDebug(lcMainWindow, "MW: slotLayoutFruchterman ()"); if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } runGraphOperationAsync( [this]() { activeGraph->layoutForceDirectedFruchtermanReingold(100); }, tr("Computing Fruchterman & Reingold layout. Please wait..."), tr("Fruchterman & Reingold model embedded.")); } /** * @brief Layouts the network according to the Kamada-Kawai FDP model */ void MainWindow::slotLayoutKamadaKawai() { qCDebug(lcMainWindow) << "MW::slotLayoutKamadaKawai ()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } runGraphOperationAsync( [this]() { activeGraph->layoutForceDirectedKamadaKawai(400); }, tr("Computing Kamada & Kawai layout. Please wait..."), tr("Kamada & Kawai model embedded.")); } /** * @brief Runs when the user selects a radial layout menu option * * Checks sender text() to find out what QMenu item was pressed and so the requested index * */ void MainWindow::slotLayoutRadialByProminenceIndex() { qCDebug(lcMainWindow) << "Got request to apply a radial layout by prominence index. Checking what index is requested..."; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QAction *menuitem = (QAction *)sender(); QString menuItemText = menuitem->text(); slotLayoutRadialByProminenceIndex(menuItemText); } /** * @brief Applies a radial layout on the social network, where each node is placed on concentric circles according to their index score. * * More prominent nodes are closer to the centre of the screen. * * @param prominenceIndexName */ void MainWindow::slotLayoutRadialByProminenceIndex(QString prominenceIndexName = "") { qCDebug(lcMainWindow) << "Will apply a radial layout by prominence index: " << prominenceIndexName; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } slotLayoutGuides(true); int indexType = 0; indexType = activeGraph->getProminenceIndexByName(prominenceIndexName); qCDebug(lcMainWindow) << "indexType" << indexType; if (indexType == IndexType::KATZ && !activeGraph->isCentralityIndexComputed(IndexType::KATZ)) { QMessageBox::information( this, tr("Katz Centrality not computed yet"), tr("Katz Centrality needs an attenuation factor (alpha) that only " "Analyze > Centrality > Katz Centrality asks for - this Layout menu " "has no way to prompt for it.\n\n" "Please compute Katz Centrality from the Analyze menu first, " "then try this layout again.")); return; } if (indexType == IndexType::BPC && !activeGraph->isCentralityIndexComputed(IndexType::BPC)) { QMessageBox::information( this, tr("Bonacich Power Centrality not computed yet"), tr("Bonacich Power Centrality needs parameters (alpha, beta) that only " "Analyze > Centrality > Bonacich Power Centrality asks for - this Layout " "menu has no way to prompt for them.\n\n" "Please compute Bonacich Power Centrality from the Analyze menu first, " "then try this layout again.")); return; } toolBoxLayoutByIndexSelect->blockSignals(true); toolBoxLayoutByIndexSelect->setCurrentIndex(indexType + 1); toolBoxLayoutByIndexSelect->blockSignals(false); toolBoxLayoutByIndexTypeSelect->blockSignals(true); toolBoxLayoutByIndexTypeSelect->setCurrentIndex(1); // Radial toolBoxLayoutByIndexTypeSelect->blockSignals(false); toolBoxLayoutForceDirectedSelect->blockSignals(true); toolBoxLayoutForceDirectedSelect->setCurrentIndex(0); // None β€” FD position overridden toolBoxLayoutForceDirectedSelect->blockSignals(false); bool dropIsolates = false; if (indexType == IndexType::IC && activeNodes() > 200) { switch ( QMessageBox::critical( this, "Slow function warning", tr("Please note that this function is CPU-intensive on large " "networks (n>200), since it will calculate a (n x n) matrix A with:
" "Aii=1+weighted_degree_ni
" "Aij=1 if (i,j)=0
" "Aij=1-wij if (i,j)=wij
" "Next, it will compute the inverse matrix C of A. " "The computation of the inverse matrix is a CPU intensive function " "although it uses LU decomposition.
" "This can take a while on large networks, but the app stays responsive " "while it works in the background.
" "Are you sure you want to continue?"), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel)) { case QMessageBox::Ok: break; case QMessageBox::Cancel: // Cancel was clicked return; break; default: // should never be reached break; } } askAboutEdgeWeights(); graphicsWidget->clearGuides(); // Read UI/member state on the GUI thread now - the lambda below runs on graphThread // (see runGraphOperationAsync), where reading QAction/MainWindow state would be unsafe. const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolatesFinal = editFilterNodesIsolatesAct->isChecked() || dropIsolates; const bool inverseWeightsFinal = inverseWeights; runGraphOperationAsync( [this, indexType, considerWeights, inverseWeightsFinal, dropIsolatesFinal]() { activeGraph->layoutByProminenceIndex( indexType, 0, considerWeights, inverseWeightsFinal, dropIsolatesFinal); }, tr("Computing %1 radial layout. Please wait...").arg(prominenceIndexName), tr("Nodes in inner circles have higher %1 score. ").arg(prominenceIndexName)); } /** * @brief Runs when the user selects a radial layout menu option * * Checks sender text() to find out what QMenu item was pressed and so the requested index * */ void MainWindow::slotLayoutLevelByProminenceIndex() { qCDebug(lcMainWindow) << "Got request to apply a level layout by prominence index. Checking what index is requested..."; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QAction *menuitem = (QAction *)sender(); QString menuItemText = menuitem->text(); slotLayoutLevelByProminenceIndex(menuItemText); } /** * @brief Applies a level layout on the social network, where each node is placed on different top-down levels according to their index score. * * More prominent nodes are closer to the the top of the screen * * @param prominenceIndexName */ void MainWindow::slotLayoutLevelByProminenceIndex(QString prominenceIndexName = "") { qCDebug(lcMainWindow) << "Will apply a level layout by prominence index: " << prominenceIndexName; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } int indexType = 0; slotLayoutGuides(true); indexType = activeGraph->getProminenceIndexByName(prominenceIndexName); qCDebug(lcMainWindow) << "indexType" << indexType; if (indexType == IndexType::KATZ && !activeGraph->isCentralityIndexComputed(IndexType::KATZ)) { QMessageBox::information( this, tr("Katz Centrality not computed yet"), tr("Katz Centrality needs an attenuation factor (alpha) that only " "Analyze > Centrality > Katz Centrality asks for - this Layout menu " "has no way to prompt for it.\n\n" "Please compute Katz Centrality from the Analyze menu first, " "then try this layout again.")); return; } if (indexType == IndexType::BPC && !activeGraph->isCentralityIndexComputed(IndexType::BPC)) { QMessageBox::information( this, tr("Bonacich Power Centrality not computed yet"), tr("Bonacich Power Centrality needs parameters (alpha, beta) that only " "Analyze > Centrality > Bonacich Power Centrality asks for - this Layout " "menu has no way to prompt for them.\n\n" "Please compute Bonacich Power Centrality from the Analyze menu first, " "then try this layout again.")); return; } toolBoxLayoutByIndexSelect->blockSignals(true); toolBoxLayoutByIndexSelect->setCurrentIndex(indexType + 1); toolBoxLayoutByIndexSelect->blockSignals(false); toolBoxLayoutByIndexTypeSelect->blockSignals(true); toolBoxLayoutByIndexTypeSelect->setCurrentIndex(2); // On Levels toolBoxLayoutByIndexTypeSelect->blockSignals(false); toolBoxLayoutForceDirectedSelect->blockSignals(true); toolBoxLayoutForceDirectedSelect->setCurrentIndex(0); // None β€” FD position overridden toolBoxLayoutForceDirectedSelect->blockSignals(false); bool dropIsolates = false; if (indexType == IndexType::IC && activeNodes() > 200) { switch ( QMessageBox::critical( this, "Slow function warning", tr("Please note that this function is CPU-intensive on large " "networks (n>200), since it will calculate a (n x n) matrix A with:
" "Aii=1+weighted_degree_ni
" "Aij=1 if (i,j)=0
" "Aij=1-wij if (i,j)=wij
" "Next, it will compute the inverse matrix C of A. " "The computation of the inverse matrix is a CPU intensive function " "although it uses LU decomposition.
" "This can take a while on large networks, but the app stays responsive " "while it works in the background.
" "Are you sure you want to continue?"), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel)) { case QMessageBox::Ok: break; case QMessageBox::Cancel: // Cancel was clicked return; break; default: // should never be reached break; } } askAboutEdgeWeights(); graphicsWidget->clearGuides(); // Read UI/member state on the GUI thread now - the lambda below runs on graphThread // (see runGraphOperationAsync), where reading QAction/MainWindow state would be unsafe. const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolatesFinal = editFilterNodesIsolatesAct->isChecked() || dropIsolates; const bool inverseWeightsFinal = inverseWeights; runGraphOperationAsync( [this, indexType, considerWeights, inverseWeightsFinal, dropIsolatesFinal]() { activeGraph->layoutByProminenceIndex( indexType, 1, considerWeights, inverseWeightsFinal, dropIsolatesFinal); }, tr("Computing %1 level layout. Please wait...").arg(prominenceIndexName), tr("Nodes in upper levels have higher %1 score. ").arg(prominenceIndexName)); } /** * @brief Runs when the user selects a color layout menu option * * Checks sender text() to find out what QMenu item was pressed and so the requested index * */ void MainWindow::slotLayoutNodeSizeByProminenceIndex() { qCDebug(lcMainWindow) << "Got request to apply a color layout by prominence index. Checking what index is requested..."; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QAction *menuitem = (QAction *)sender(); QString menuItemText = menuitem->text(); slotLayoutNodeSizeByProminenceIndex(menuItemText); } /** * @brief Applies a node size layout on the social network, where the size of each of node is analogous to their index score. * * More prominent nodes are bigger. * * @param prominenceIndexName */ void MainWindow::slotLayoutNodeSizeByProminenceIndex(QString prominenceIndexName = "") { qCDebug(lcMainWindow) << "Will apply a node size layout by prominence index: " << prominenceIndexName; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } int indexType = 0; indexType = activeGraph->getProminenceIndexByName(prominenceIndexName); qCDebug(lcMainWindow) << "indexType" << indexType; if (indexType == IndexType::KATZ && !activeGraph->isCentralityIndexComputed(IndexType::KATZ)) { QMessageBox::information( this, tr("Katz Centrality not computed yet"), tr("Katz Centrality needs an attenuation factor (alpha) that only " "Analyze > Centrality > Katz Centrality asks for - this Layout menu " "has no way to prompt for it.\n\n" "Please compute Katz Centrality from the Analyze menu first, " "then try this layout again.")); return; } if (indexType == IndexType::BPC && !activeGraph->isCentralityIndexComputed(IndexType::BPC)) { QMessageBox::information( this, tr("Bonacich Power Centrality not computed yet"), tr("Bonacich Power Centrality needs parameters (alpha, beta) that only " "Analyze > Centrality > Bonacich Power Centrality asks for - this Layout " "menu has no way to prompt for them.\n\n" "Please compute Bonacich Power Centrality from the Analyze menu first, " "then try this layout again.")); return; } toolBoxLayoutByIndexSelect->blockSignals(true); toolBoxLayoutByIndexSelect->setCurrentIndex(indexType + 1); toolBoxLayoutByIndexSelect->blockSignals(false); toolBoxLayoutByIndexTypeSelect->blockSignals(true); toolBoxLayoutByIndexTypeSelect->setCurrentIndex(3); // Node Size toolBoxLayoutByIndexTypeSelect->blockSignals(false); // Node size coexists with FD positional layout β€” don't reset FD combobox bool dropIsolates = false; if (indexType == IndexType::IC && activeNodes() > 200) { switch ( QMessageBox::critical( this, "Slow function warning", tr("Please note that this function is CPU-intensive on large " "networks (n>200), since it will calculate a (n x n) matrix A with:
" "Aii=1+weighted_degree_ni
" "Aij=1 if (i,j)=0
" "Aij=1-wij if (i,j)=wij
" "Next, it will compute the inverse matrix C of A. " "The computation of the inverse matrix is a CPU intensive function " "although it uses LU decomposition.
" "This can take a while on large networks, but the app stays responsive " "while it works in the background.
" "Are you sure you want to continue?"), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel)) { case QMessageBox::Ok: break; case QMessageBox::Cancel: // Cancel was clicked return; break; default: // should never be reached break; } } askAboutEdgeWeights(); graphicsWidget->clearGuides(); // Read UI/member state on the GUI thread now - the lambda below runs on graphThread // (see runGraphOperationAsync), where reading QAction/MainWindow state would be unsafe. const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolatesFinal = editFilterNodesIsolatesAct->isChecked() || dropIsolates; const bool inverseWeightsFinal = inverseWeights; runGraphOperationAsync( [this, indexType, considerWeights, inverseWeightsFinal, dropIsolatesFinal]() { activeGraph->layoutByProminenceIndex( indexType, 2, considerWeights, inverseWeightsFinal, dropIsolatesFinal); }, tr("Computing %1 node size layout. Please wait...").arg(prominenceIndexName), tr("Bigger nodes have greater %1 score.").arg(prominenceIndexName)); } /** * @brief Runs when the user selects a color layout menu option * * Checks sender text() to find out what QMenu item was pressed and so the requested index * */ void MainWindow::slotLayoutNodeColorByProminenceIndex() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QAction *menuitem = (QAction *)sender(); QString menuItemText = menuitem->text(); slotLayoutNodeColorByProminenceIndex(menuItemText); } /** * @brief Applies a color layout on the social network. Changes the colors of all nodes according to their index score. * * More prominent nodes have more warm colors * * RED=rgb(255,0,0) most prominent * BLUE=rgb(0,0,255) least prominent * * @param prominenceIndexName */ void MainWindow::slotLayoutNodeColorByProminenceIndex(QString prominenceIndexName = "") { qCDebug(lcMainWindow) << "Will apply a node color layout by prominence index: " << prominenceIndexName; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } int indexType = 0; indexType = activeGraph->getProminenceIndexByName(prominenceIndexName); qCDebug(lcMainWindow) << "indexType" << indexType; if (indexType == IndexType::KATZ && !activeGraph->isCentralityIndexComputed(IndexType::KATZ)) { QMessageBox::information( this, tr("Katz Centrality not computed yet"), tr("Katz Centrality needs an attenuation factor (alpha) that only " "Analyze > Centrality > Katz Centrality asks for - this Layout menu " "has no way to prompt for it.\n\n" "Please compute Katz Centrality from the Analyze menu first, " "then try this layout again.")); return; } if (indexType == IndexType::BPC && !activeGraph->isCentralityIndexComputed(IndexType::BPC)) { QMessageBox::information( this, tr("Bonacich Power Centrality not computed yet"), tr("Bonacich Power Centrality needs parameters (alpha, beta) that only " "Analyze > Centrality > Bonacich Power Centrality asks for - this Layout " "menu has no way to prompt for them.\n\n" "Please compute Bonacich Power Centrality from the Analyze menu first, " "then try this layout again.")); return; } toolBoxLayoutByIndexSelect->blockSignals(true); toolBoxLayoutByIndexSelect->setCurrentIndex(indexType + 1); toolBoxLayoutByIndexSelect->blockSignals(false); toolBoxLayoutByIndexTypeSelect->blockSignals(true); toolBoxLayoutByIndexTypeSelect->setCurrentIndex(4); // Node Color toolBoxLayoutByIndexTypeSelect->blockSignals(false); // Node color coexists with FD positional layout β€” don't reset FD combobox bool dropIsolates = false; if (indexType == 8 && activeNodes() > 200) { switch ( QMessageBox::critical( this, "Slow function warning", tr("Please note that this function is CPU-intensive on large " "networks (n>200), since it will calculate a (n x n) matrix A with:
" "Aii=1+weighted_degree_ni
" "Aij=1 if (i,j)=0
" "Aij=1-wij if (i,j)=wij
" "Next, it will compute the inverse matrix C of A. " "The computation of the inverse matrix is a CPU intensive function " "although it uses LU decomposition.
" "This can take a while on large networks, but the app stays responsive " "while it works in the background.
" "Are you sure you want to continue?"), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel)) { case QMessageBox::Ok: break; case QMessageBox::Cancel: // Cancel was clicked return; break; default: // should never be reached break; } } askAboutEdgeWeights(); graphicsWidget->clearGuides(); // Read UI/member state on the GUI thread now - the lambda below runs on graphThread // (see runGraphOperationAsync), where reading QAction/MainWindow state would be unsafe. const bool considerWeights = optionsEdgeWeightConsiderAct->isChecked(); const bool dropIsolatesFinal = editFilterNodesIsolatesAct->isChecked() || dropIsolates; const bool inverseWeightsFinal = inverseWeights; runGraphOperationAsync( [this, indexType, considerWeights, inverseWeightsFinal, dropIsolatesFinal]() { activeGraph->layoutByProminenceIndex( indexType, 3, considerWeights, inverseWeightsFinal, dropIsolatesFinal); }, tr("Computing %1 node color layout. Please wait...").arg(prominenceIndexName), tr("Nodes with warmer color have greater %1 score.").arg(prominenceIndexName)); } /** * @brief Colors nodes by their weakly connected component. * * Runs a BFS to identify weakly connected components and assigns a distinct * color to each one. If the network is already fully connected (1 component), * reports that and leaves colors unchanged. */ void MainWindow::slotLayoutNodeColorByComponent() { qCDebug(lcMainWindow) << "MW::slotLayoutNodeColorByComponent()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int components = activeGraph->graphWeaklyConnectedComponents(); if (components <= 1) { slotHelpMessageToUser( USER_MSG_INFO, tr("Network is fully connected β€” only one component."), tr("Network is fully connected."), tr("All nodes belong to a single connected component. No coloring applied.")); return; } // Visually distinct palette β€” cycles if there are more components than entries static const QStringList palette = { "#e74c3c", "#3498db", "#2ecc71", "#f39c12", "#9b59b6", "#1abc9c", "#e67e22", "#34495e", "#e91e63", "#00bcd4", "#8bc34a", "#ff5722", "#673ab7", "#009688", "#ffc107"}; const QHash &compMap = activeGraph->vertexComponentId(); for (auto it = activeGraph->verticesBegin(); it != activeGraph->verticesEnd(); ++it) { if (!(*it)->isEnabled()) continue; const int compId = compMap.value((*it)->number(), 1); activeGraph->vertexColorSet((*it)->number(), palette.at((compId - 1) % palette.size())); } statusMessage(tr("Nodes colored by component: %1 components found.").arg(components)); } /** * @brief Shows or hides (clears) layout guides * * @param toggle */ void MainWindow::slotLayoutGuides(const bool &toggle) { qCDebug(lcMainWindow) << "MW:slotLayoutGuides()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } if (toggle) { layoutGuidesAct->setChecked(true); statusMessage(tr("Layout Guides are displayed")); } else { layoutGuidesAct->setChecked(false); graphicsWidget->clearGuides(); statusMessage(tr("Layout Guides removed")); } } SocNetV-3.7/src/mainwindow/lifecycle/000077500000000000000000000000001524402210300175775ustar00rootroot00000000000000SocNetV-3.7/src/mainwindow/lifecycle/mainwindow_lifecycle.cpp000066400000000000000000000154151524402210300245040ustar00rootroot00000000000000/** * @file mainwindow_lifecycle.cpp * @brief Implements MainWindow's window lifecycle handlers: close event, resize event, and * thread teardown. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "texteditor.h" #include #include /** * @brief Called when the application closes. Asks to write any unsaved network data. * @param ce */ void MainWindow::closeEvent(QCloseEvent *ce) { // // Show a status message // qCDebug(lcMainWindow) << "Received close event. Show a status message to user..."; statusMessage(tr("Closing SocNetV. Bye!")); // // Check if the graph has been saved // bool userCancelled = false; qCDebug(lcMainWindow) << "Checking if current graph is saved..."; if (m_interactiveScriptQuitting) { // 'quit' interactive-script command: no one is present to answer the save-changes // prompt below, so skip it entirely - same "bypass modal dialogs that would block a // scripted run" precedent already used by 'erdos'/'add-node'/etc. ce->accept(); qCDebug(lcMainWindow) << "Interactive script quit - skipping save-changes prompt."; } else if (activeGraph->isSaved()) { ce->accept(); qCDebug(lcMainWindow) << "Graph is already saved. Nothing to do."; } else { qCDebug(lcMainWindow) << "Graph NOT saved. Asking the user what to do."; switch (slotHelpMessageToUser( USER_MSG_QUESTION, tr("Save changes"), tr("Modified network has not been saved!"), tr("Do you want to save the changes to the network file?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Cancel)) { case QMessageBox::Yes: slotNetworkSave(); ce->accept(); break; case QMessageBox::No: ce->accept(); break; case QMessageBox::Cancel: ce->ignore(); userCancelled = true; break; case QMessageBox::NoButton: default: // just for sanity ce->ignore(); break; } } if (userCancelled) { qCDebug(lcMainWindow) << "User canceled (while saving graph). Returning without closing the app."; return; } // // Terminate running threads // qCDebug(lcMainWindow) << "I will terminate any running threads..."; terminateThreads("closeEvent()"); // // Delete other objects and pointers // qCDebug(lcMainWindow) << "Deleting other objects and pointers..."; qCDebug(lcMainWindow) << "Deleting printer"; delete printer; qCDebug(lcMainWindow) << "Deleting printerPDF"; delete printerPDF; // graphicsWidget is NOT explicitly deleted here (WS7 MW0 finding, confirmed via lldb/ASan): // Qt's own window-activation bookkeeping (QApplicationPrivate::setActiveWindow, run later // during this same close sequence, after closeEvent() returns) still holds a live reference // to it at this point and dereferences it - deleting it here left that reference dangling and // crashed. `this` (MainWindow) is never explicitly destructed either (see main.cpp - heap // allocated, no matching delete, process just exits), so graphicsWidget - a `this`-parented // QObject - was never going to be cleanly destructed via ~MainWindow() anyway; the OS reclaims // it at process exit the same way it already reclaims MainWindow itself. qCDebug(lcMainWindow) << "Deleting activeGraph"; delete activeGraph; // scene is likewise NOT explicitly deleted here (same WS7 MW0 finding): graphicsWidget (a // QGraphicsView, still alive per above) holds a live, non-owning reference to it // (QGraphicsView(sc, m_parent) does not reparent/take ownership - graphicsWidget.cpp), and // dereferences it while handling a viewport event during this same close sequence. scene has // no Qt parent (`new QGraphicsScene()`), so the app is nominally responsible for deleting it - // but since the process exits immediately once this close sequence finishes, leaving it for // the OS to reclaim at exit is no different in practice from deleting it here, and avoids the // dangling-reference crash. // delete miniChart; qCDebug(lcMainWindow) << "Clearing and deleting text editors..."; foreach (TextEditor *ed, m_textEditors) { ed->close(); delete ed; } m_textEditors.clear(); qCDebug(lcMainWindow) << " Checking if networkManager thread is running..."; if (networkManager->thread()->isRunning()) { qCDebug(lcMainWindow) << "networkManager thread running" << "Calling deleteLater();"; networkManager->deleteLater(); } // Not explicitly deleted: both are constructed with `this` as their QObject parent // (mainwindow.cpp), so normal Qt parent-child teardown already destroys them when // MainWindow itself is destroyed. An explicit delete() here crashed - the action is still // attached to a live QToolBar at this point in closeEvent, and ~QAction() reaching into // QToolBar::actionEvent()/removeAction() to detach itself segfaulted. qCDebug(lcMainWindow) << "Clearing codecs..."; codecs.clear(); qCDebug(lcMainWindow) << "Finished. Bye!"; } /** * @brief Terminates any remaining threads. * * @param reason */ void MainWindow::terminateThreads(const QString &reason) { qCDebug(lcMainWindow) << "Terminating threads (those started from MW). Reason:" << reason << " Checking if graphThread is running..."; if (graphThread.isRunning()) { qCDebug(lcMainWindow) << "graphThread running." << "Calling graphThread.quit();"; graphThread.quit(); qCDebug(lcMainWindow) << "deleting activeGraph and pointer"; delete activeGraph; activeGraph = 0; // see why here: https://goo.gl/tQxpGA } } /** * @brief Called whenever the app window is resized. */ void MainWindow::resizeEvent(QResizeEvent *e) { Q_UNUSED(e); // int w0=e->oldSize().width(); // int h0=e->oldSize().height(); // int w=width(); // int h=height(); // statusMessage( // tr("Window resized to (%1, %2)px.") // .arg(w).arg(h) // ); } SocNetV-3.7/src/mainwindow/network/000077500000000000000000000000001524402210300173315ustar00rootroot00000000000000SocNetV-3.7/src/mainwindow/network/mainwindow_network_file.cpp000066400000000000000000001211431524402210300247630ustar00rootroot00000000000000/** * @file mainwindow_network_file.cpp * @brief Implements MainWindow Network menu: new/open/save/close/print, recent files, file dialog plumbing, and load/save status reactions. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "webcrawler.h" #include "forms/dialogpreviewfile.h" #include "forms/dialogdatasetselect.h" #include "forms/dialogranderdosrenyi.h" #include "forms/dialograndsmallworld.h" #include "forms/dialograndscalefree.h" #include "forms/dialograndregular.h" #include "forms/dialograndlattice.h" #include "forms/dialogwebcrawler.h" #include "forms/dialogexportimage.h" #include "forms/dialogexportpdf.h" #include "widgets/graphtablewidget.h" #include "graph/io/table_export.h" #include #include #include #include #include /** * @brief Updates the Recent Files QActions in the menu */ void MainWindow::slotNetworkFileRecentUpdateActions() { int numRecentFiles = qMin(recentFiles.size(), (int)MaxRecentFiles); for (int i = 0; i < numRecentFiles; ++i) { QString text = tr("&%1 %2").arg(i + 1).arg(QFileInfo(recentFiles[i]).fileName()); recentFileActs[i]->setText(text); recentFileActs[i]->setData(recentFiles[i]); recentFileActs[i]->setVisible(true); } for (int j = numRecentFiles; j < MaxRecentFiles; ++j) recentFileActs[j]->setVisible(false); // separatorAct->setVisible(numRecentFiles > 0); } /** * @brief Starts a new network (closing the current one). */ void MainWindow::slotNetworkNew() { slotNetworkClose(); } /** * @brief Chooses a network file to load * * If m_fileName is empty, opens a file selection dialog * Then calls slotNetworkFilePreview() * Called on application loading from command line with filename parameter * Called from slotNetworkImport* methods * Called from slotNetworkFileLoadRecent * * @param m_fileName * @param fileFormat * @param checkSelectFileType */ void MainWindow::slotNetworkFileChoose(QString m_fileName, int fileFormat, const bool &checkSelectFileType) { qCDebug(lcMainWindow) << " m_fileName: " << m_fileName << " fileFormat " << fileFormat << " checkSelectFileType " << checkSelectFileType; previous_fileName = fileName; QString fileType_filter; /* * CASE 1: No filename provided. This happens when: * - User clicked Open Network File or * - User clicked Import Network * * Prepare known filetypes and * Open a file selection dialog for the user * */ if (m_fileName.isNull() || m_fileName.isEmpty()) { fileType = fileFormat; // prepare supported filetype extensions switch (fileType) { case FileType::GRAPHML: fileType_filter = tr("GraphML (*.graphml *.xml);;All (*)"); break; case FileType::PAJEK: fileType_filter = tr("Pajek (*.net *.paj *.pajek);;All (*)"); break; case FileType::ADJACENCY: fileType_filter = tr("Adjacency (*.csv *.sm *.adj *.txt);;All (*)"); break; case FileType::GRAPHVIZ: fileType_filter = tr("GraphViz (*.dot);;All (*)"); break; case FileType::UCINET: fileType_filter = tr("UCINET (*.dl *.dat);;All (*)"); break; case FileType::GML: fileType_filter = tr("GML (*.gml);;All (*)"); break; case FileType::EDGELIST_WEIGHTED: fileType_filter = tr("Weighted Edge List (*.txt *.list *.edgelist *.lst *.wlst);;All (*)"); break; case FileType::EDGELIST_SIMPLE: fileType_filter = tr("Simple Edge List (*.txt *.list *.edgelist *.lst);;All (*)"); break; case FileType::TWOMODE: fileType_filter = tr("Two-Mode Sociomatrix (*.2sm *.aff);;All (*)"); break; default: // All fileType_filter = tr("GraphML (*.graphml *.xml);;" "GML (*.gml *.xml);;" "Pajek (*.net *.pajek *.paj);;" "UCINET (*.dl *.dat);;" "Adjacency (*.csv *.adj *.sm *.txt);;" "GraphViz (*.dot);;" "Weighted Edge List (*.txt *.edgelist *.list *.lst *.wlst);;" "Simple Edge List (*.txt *.edgelist *.list *.lst);;" "Two-Mode Sociomatrix (*.2sm *.aff);;" "All (*)"); break; } // prepare the filedialog QFileDialog *fileDialog = new QFileDialog(this); fileDialog->setFileMode(QFileDialog::ExistingFile); fileDialog->setNameFilter(fileType_filter); fileDialog->setViewMode(QFileDialog::Detail); fileDialog->setDirectory(getLastPath()); // connect its signals to our slots connect(fileDialog, &QFileDialog::filterSelected, this, &MainWindow::slotNetworkFileDialogFilterSelected); connect(fileDialog, &QFileDialog::fileSelected, this, &MainWindow::slotNetworkFileDialogFileSelected); connect(fileDialog, &QFileDialog::rejected, this, &MainWindow::slotNetworkFileDialogRejected); // open the filedialog statusMessage(tr("Choose a network file...")); if (fileDialog->exec()) { m_fileName = (fileDialog->selectedFiles()).at(0); qCDebug(lcMainWindow) << "m_fileName " << m_fileName; } else { // display some error statusMessage(tr("Nothing to do...")); } return; } /* * CASE 2: Filename provided. This happens when: * - Application starts from command line with filename parameter or * - User selects a Recent File or * - User selects a file in a previous slotNetworkFileChoose call * * If checkSelectFileType==TRUE (that is on app start or Recent File), * it tries to understand fileType by file extension. If file has unknown * file extension or an ambiguous file extension used by many different file * formats, then it asks the user to provide the fileType. Then it loads the * file * * If checkSelectFileType==FALSE, then it loads the file with given fileType. * */ if (checkSelectFileType || fileFormat == FileType::UNRECOGNIZED) { // This happens only on application startup or on loading a recent file. if (!m_fileName.endsWith(".graphml", Qt::CaseInsensitive) && !m_fileName.endsWith(".net", Qt::CaseInsensitive) && !m_fileName.endsWith(".paj", Qt::CaseInsensitive) && !m_fileName.endsWith(".pajek", Qt::CaseInsensitive) && !m_fileName.endsWith(".dl", Qt::CaseInsensitive) && !m_fileName.endsWith(".dat", Qt::CaseInsensitive) && !m_fileName.endsWith(".gml", Qt::CaseInsensitive) && !m_fileName.endsWith(".wlst", Qt::CaseInsensitive) && !m_fileName.endsWith(".wlist", Qt::CaseInsensitive) && !m_fileName.endsWith(".dot", Qt::CaseInsensitive) && !m_fileName.endsWith(".2sm", Qt::CaseInsensitive) && !m_fileName.endsWith(".sm", Qt::CaseInsensitive) && !m_fileName.endsWith(".csv", Qt::CaseInsensitive) && !m_fileName.endsWith(".aff", Qt::CaseInsensitive)) { // ambigious file type. Open an input dialog for the user to choose // what kind of network file this is. tempFileNameNoPath = m_fileName.split("/"); QStringList fileTypes; fileTypes << tr("GraphML") << tr("GML") << tr("Pajek") << tr("UCINET") << tr("Adjacency") << tr("GraphViz") << tr("Edge List (weighted)") << tr("Edge List (simple, non-weighted)") << tr("Two-mode sociomatrix"); bool ok; QString userFileType = QInputDialog::getItem( this, tr("Selected file has ambiguous file extension!"), tr("You selected: %1 \n" "The name of this file has either an unknown extension \n" "or an extension used by different network file formats.\n\n" "SocNetV supports the following social network file " "formats. \nIn parentheses the expected extension. \n" "- GraphML (.graphml or .xml)\n" "- GML (.gml or .xml)\n" "- Pajek (.paj or .pajek or .net)\n" "- UCINET (.dl .dat) \n" "- GraphViz (.dot)\n" "- Adjacency Matrix (.csv, .txt, .sm or .adj)\n" "- Simple Edge List (.list or .lst)\n" "- Weighted Edge List (.wlist or .wlst)\n" "- Two-Mode / affiliation (.2sm or .aff) \n\n" "If you are sure the file is of a supported format, please \n" "select the right format from the list below.") .arg(tempFileNameNoPath.last()), fileTypes, 0, false, &ok); if (ok && !userFileType.isEmpty()) { if (userFileType == "GraphML") { fileFormat = FileType::GRAPHML; } else if (userFileType == "GraphML") { fileFormat = FileType::PAJEK; } else if (userFileType == "GML") { fileFormat = FileType::GML; } else if (userFileType == "UCINET") { fileFormat = FileType::UCINET; } else if (userFileType == "Adjacency") { fileFormat = FileType::ADJACENCY; } else if (userFileType == "GraphViz") { fileFormat = FileType::GRAPHVIZ; } else if (userFileType == "Edge List (weighted)") { fileFormat = FileType::EDGELIST_WEIGHTED; } else if (userFileType == "Edge List (simple, non-weighted)") { fileFormat = FileType::EDGELIST_SIMPLE; } else if (userFileType == "Two-mode sociomatrix") { fileFormat = FileType::TWOMODE; } } else { statusMessage(tr("Opening network file aborted.")); // if a file was previously opened, get back to it. if (activeGraph->isLoaded()) { fileName = previous_fileName; } return; } } else if (m_fileName.endsWith(".graphml", Qt::CaseInsensitive) || m_fileName.endsWith(".xml", Qt::CaseInsensitive)) { fileFormat = FileType::GRAPHML; } else if (m_fileName.endsWith(".net", Qt::CaseInsensitive) || m_fileName.endsWith(".paj", Qt::CaseInsensitive) || m_fileName.endsWith(".pajek", Qt::CaseInsensitive)) { fileFormat = FileType::PAJEK; } else if (m_fileName.endsWith(".dl", Qt::CaseInsensitive) || m_fileName.endsWith(".dat", Qt::CaseInsensitive)) { fileFormat = FileType::UCINET; } else if (m_fileName.endsWith(".sm", Qt::CaseInsensitive) || m_fileName.endsWith(".csv", Qt::CaseInsensitive) || m_fileName.endsWith(".adj", Qt::CaseInsensitive) || m_fileName.endsWith(".txt", Qt::CaseInsensitive)) { fileFormat = FileType::ADJACENCY; } else if (m_fileName.endsWith(".dot", Qt::CaseInsensitive)) { fileFormat = FileType::GRAPHVIZ; } else if (m_fileName.endsWith(".gml", Qt::CaseInsensitive)) { fileFormat = FileType::GML; } else if (m_fileName.endsWith(".list", Qt::CaseInsensitive) || m_fileName.endsWith(".lst", Qt::CaseInsensitive)) { fileFormat = FileType::EDGELIST_SIMPLE; } else if (m_fileName.endsWith(".wlist", Qt::CaseInsensitive) || m_fileName.endsWith(".wlst", Qt::CaseInsensitive)) { fileFormat = FileType::EDGELIST_WEIGHTED; } else if (m_fileName.endsWith(".2sm", Qt::CaseInsensitive) || m_fileName.endsWith(".aff", Qt::CaseInsensitive)) { fileFormat = FileType::TWOMODE; } else fileFormat = FileType::UNRECOGNIZED; } qCDebug(lcMainWindow) << "Calling slotNetworkFilePreview" << "with m_fileName" << m_fileName << "and fileFormat " << fileFormat; slotNetworkFilePreview(m_fileName, fileFormat); } /** * @brief Displays a status message when the user aborts the file dialog */ void MainWindow::slotNetworkFileDialogRejected() { qCDebug(lcMainWindow) << "Dialog rejected. If a file was previously opened, get back to it."; statusMessage(tr("Opening aborted")); } /** * @brief Called when user the selects a file filter (i.e. GraphML) in the fileDialog * @param filter */ void MainWindow::slotNetworkFileDialogFilterSelected(const QString &filter) { qCDebug(lcMainWindow) << "User selected network file filter" << filter; if (filter.startsWith("GraphML", Qt::CaseInsensitive)) { fileType = FileType::GRAPHML; qCDebug(lcMainWindow) << "fileType FileType::GRAPHML"; } else if (filter.contains("PAJEK", Qt::CaseInsensitive)) { fileType = FileType::PAJEK; qCDebug(lcMainWindow) << "fileType FileType::PAJEK"; } else if (filter.contains("DL", Qt::CaseInsensitive) || filter.contains("UCINET", Qt::CaseInsensitive)) { fileType = FileType::UCINET; qCDebug(lcMainWindow) << "fileType FileType::UCINET"; } else if (filter.contains("Adjacency", Qt::CaseInsensitive)) { fileType = FileType::ADJACENCY; qCDebug(lcMainWindow) << "fileType FileType::ADJACENCY"; } else if (filter.contains("GraphViz", Qt::CaseInsensitive)) { fileType = FileType::GRAPHVIZ; qCDebug(lcMainWindow) << "fileType FileType::GRAPHVIZ"; } else if (filter.contains("GML", Qt::CaseInsensitive)) { fileType = FileType::GML; qCDebug(lcMainWindow) << "fileType FileType::GML"; } else if (filter.contains("Simple Edge List", Qt::CaseInsensitive)) { fileType = FileType::EDGELIST_SIMPLE; qCDebug(lcMainWindow) << "fileType FileType::EDGELIST_SIMPLE"; } else if (filter.contains("Weighted Edge List", Qt::CaseInsensitive)) { fileType = FileType::EDGELIST_WEIGHTED; qCDebug(lcMainWindow) << "fileType FileType::EDGELIST_WEIGHTED"; } else if (filter.contains("Two-Mode", Qt::CaseInsensitive)) { fileType = FileType::TWOMODE; qCDebug(lcMainWindow) << "fileType FileType::TWOMODE"; } else { fileType = FileType::UNRECOGNIZED; qCDebug(lcMainWindow) << "fileType FileType::UNRECOGNIZED"; } } /** * @brief Called when user selects a file in the fileDialog * Calls slotNetworkFileChoose() again. * @param fileName * */ void MainWindow::slotNetworkFileDialogFileSelected(const QString &fileName) { qCDebug(lcMainWindow) << "User selected filename:" << fileName << "calling slotNetworkFileChoose() with fileType" << fileType; slotNetworkFileChoose(fileName, fileType, ((fileType == FileType::UNRECOGNIZED) ? true : false)); } /** * @brief Saves the network to a file * * First, it checks if a fileName is currently set * If not, calls slotNetworkSaveAs (which prompts for a fileName before returning here) * If a fileName is set, it checks if fileFormat is supported and saves the network. * If not supported, or the file is new, just tries to save in GraphML * For other exporting options the user is informed to use the export menu. * * @param fileFormat */ void MainWindow::slotNetworkSave(const int &fileFormat) { statusMessage(tr("Saving file...")); if (activeNodes() == 0) { statusMessage(QString(tr("Nothing to save. There are no vertices."))); } if (activeGraph->isSaved()) { statusMessage(QString(tr("Graph already saved."))); } if (fileName.isEmpty()) { slotNetworkSaveAs(); return; } QFileInfo fileInfo(fileName); fileNameNoPath = fileInfo.fileName(); bool saveZeroWeightEdges = appSettings["saveZeroWeightEdges"] == "true" ? true : false; bool saveEdgeWeights = true; // if the specified format is one of the supported ones, just save it. if (activeGraph->isFileFormatExportSupported(fileFormat)) { activeGraph->saveToFile(fileName, fileFormat, saveEdgeWeights, saveZeroWeightEdges); } // else if it is GraphML or new file not saved yet, just save it. else if (activeGraph->getFileFormat() == FileType::GRAPHML || (activeGraph->isSaved() && !activeGraph->isLoaded())) { activeGraph->saveToFile(fileName, FileType::GRAPHML, saveEdgeWeights, saveZeroWeightEdges); } // else check whether Graph thinks this is supported and save it else if (activeGraph->isFileFormatExportSupported( activeGraph->getFileFormat())) { activeGraph->saveToFile(fileName, activeGraph->getFileFormat(), saveEdgeWeights, saveZeroWeightEdges); } // In any other case, save in GraphML. // First, inform the user that we will save in that format. else { switch ( slotHelpMessageToUser(USER_MSG_QUESTION, tr("Save to GraphML?"), tr("Default File Format: GraphML "), tr("This network will be saved in GraphML format " "which is the default file format of SocNetV. \n\n" "Is this OK? \n\n" "If not, press Cancel, then go to Network > Export menu " "to see other supported formats to export your data to."))) { case QMessageBox::Yes: fileName = QFileInfo(fileName).absolutePath() + "/" + QFileInfo(fileName).baseName(); fileName.append(".graphml"); fileNameNoPath = QFileInfo(fileName).fileName(); setLastPath(fileName); // store this path activeGraph->saveToFile(fileName, FileType::GRAPHML, saveEdgeWeights, saveZeroWeightEdges); break; case QMessageBox::Cancel: case QMessageBox::No: statusMessage(tr("Save aborted...")); break; } } } /** * @brief Prompts the user to save the network in a new file. * Always uses the GraphML format and extension. */ void MainWindow::slotNetworkSaveAs() { qCDebug(lcMainWindow) << "User wants to save the file as a new name..."; statusMessage(tr("Enter or select a filename to save the network...")); QString fn = QFileDialog::getSaveFileName( this, tr("Save Network to GraphML File Named..."), getLastPath(), tr("GraphML (*.graphml *.xml);;All (*)")); if (!fn.isEmpty()) { if (QFileInfo(fn).suffix().isEmpty()) { fn.append(".graphml"); slotHelpMessageToUser( USER_MSG_INFO, tr("Appending .graphml extension."), tr("Missing file extension. \n" "Appended the standard .graphml extension to the given filename."), tr("Final Filename: ") + QFileInfo(fn).fileName()); } else if (!QFileInfo(fn).suffix().contains("graphml", Qt::CaseInsensitive) && !QFileInfo(fn).suffix().contains("xml", Qt::CaseInsensitive)) { fn = QFileInfo(fn).absolutePath() + "/" + QFileInfo(fn).baseName(); fn.append(".graphml"); slotHelpMessageToUser( USER_MSG_INFO, tr("Using .graphml extension."), tr("Wrong file extension. \n" "Appended the standard .graphml extension to the given filename."), tr("Final Filename: ") + QFileInfo(fn).fileName()); } fileName = fn; QFileInfo fileInfo(fileName); fileNameNoPath = fileInfo.fileName(); setLastPath(fileName); // store this path slotNetworkSave(FileType::GRAPHML); } else { statusMessage(tr("Saving aborted")); return; } } /** * @brief Updates the 'save' status of the network * * Updates Save icon and window title (if saved) * status > 0 means network has been saved * status = 0 means network has changed and needs saving * status < 0 means network has changed but there was an error saving it. * * @param status */ void MainWindow::slotNetworkSavedStatus(const int &status) { if (status < 0) { statusMessage(tr("Error! Could not save this file: %1").arg(fileNameNoPath)); networkSaveAct->setIcon(QIcon(":/images/file_download_48px_notsaved.svg")); networkSaveAct->setEnabled(true); } else if (status == 0) { // Network needs saving // UX: Maybe change it to a more prominent color for the user to see? networkSaveAct->setIcon(QIcon(":/images/file_download_48px_notsaved.svg")); networkSaveAct->setEnabled(true); } else { // Network is saved. networkSaveAct->setIcon(QIcon(":/images/file_download_48px.svg")); networkSaveAct->setEnabled(false); setWindowTitle(fileNameNoPath); statusMessage(tr("Network saved under filename: %1").arg(fileNameNoPath)); } } /** * @brief Closes the current network, saving it if needed. */ bool MainWindow::slotNetworkClose() { qCDebug(lcMainWindow) << "Request to close current network file. Check if it is saved..."; statusMessage(tr("Closing network file...")); // An interactive script has no one to click the save-confirmation dialog below - treat // unsaved changes as discarded so the script can run unattended. See #261. if (!activeGraph->isSaved() && !m_interactiveScriptLines.isEmpty()) { qCDebug(lcMainWindow) << "Interactive script active - discarding unsaved changes without prompting."; } else if (!activeGraph->isSaved()) { switch ( slotHelpMessageToUser( USER_MSG_QUESTION, tr("Closing Network..."), tr("Network has not been saved. \n" "Do you want to save before closing it?"))) { case QMessageBox::Yes: slotNetworkSave(); break; case QMessageBox::No: break; case QMessageBox::Cancel: return false; break; } } qCDebug(lcMainWindow) << "Closing network file. Calling initApp ..."; initApp(); qCDebug(lcMainWindow) << "Network file closed..."; statusMessage(tr("Ready.")); return true; } /** * @brief Sends the active network to the printer */ void MainWindow::slotNetworkPrint() { statusMessage(tr("Printing...")); QPrintDialog dialog(printer, this); if (dialog.exec() == QDialog::Accepted) { QPainter painter(printer); graphicsWidget->render(&painter); }; statusMessage(tr("Ready.")); } /** * @brief Opens the preview dialog with the selected file contents * * The aim is to let the user see the file and possibly select the appropriate text codec. * * @param m_fileName * @param fileFormat * @return */ bool MainWindow::slotNetworkFilePreview(const QString &m_fileName, const int &fileFormat) { qCDebug(lcMainWindow) << "Previewing file: " << m_fileName; if (m_fileName.isEmpty()) { statusMessage(tr("No file selected.")); return false; } if (!m_encodingOverride.isEmpty()) { qCDebug(lcMainWindow) << "Encoding override set via --encoding, skipping preview dialog:" << m_encodingOverride; slotNetworkFileLoad(m_fileName, m_encodingOverride, fileFormat); return true; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QFile file(m_fileName); if (!file.open(QFile::ReadOnly)) { QApplication::restoreOverrideCursor(); slotHelpMessageToUserError( tr("Cannot read file %1:\n%2") .arg(m_fileName) .arg(file.errorString())); return false; } // Read data and pass them to the dialog QByteArray data = file.readAll(); m_dialogPreviewFile->setEncodedData(data, m_fileName, fileFormat); // Restore the cursor QApplication::restoreOverrideCursor(); // Show the dialog m_dialogPreviewFile->exec(); return true; } /** * @brief Loads a selected file entry from the "Recent Files" menu */ void MainWindow::slotNetworkFileLoadRecent() { QAction *action = qobject_cast(sender()); if (action) { qCDebug(lcMainWindow) << "Loading recent file: " << action->data().toString(); slotNetworkFileChoose(action->data().toString()); } } /** * @brief Loads the given network file * * Inits the app, then calls the loadFile method of Graph. * * @param m_fileName * @param codecName * @param fileFormat */ void MainWindow::slotNetworkFileLoad(const QString &fileNameToLoad, const QString &codecName, const int &fileFormat) { qCDebug(lcMainWindow) << "Request to to load the file:" << fileNameToLoad << "codecName" << codecName << "fileFormat" << fileFormat; initApp(); userSelectedCodecName = codecName; // var for future use in a Settings dialog QString delimiter = QString(); int sm_two_mode = 0; int sm_has_labels = 0; if (fileFormat == FileType::TWOMODE) { switch ( slotHelpMessageToUser( USER_MSG_QUESTION_CUSTOM, tr("Two-Mode Sociomatrix β€” Select Import Mode"), tr("Two-mode sociomatrix import"), tr("This file contains a two-mode (bipartite) sociomatrix, " "where rows represent one set of actors (e.g. persons) " "and columns represent another set (e.g. events or groups).\n\n" "How would you like to import it?\n\n" "Bipartite graph: creates both sets of nodes and connects " "each person to the events they attend. (Recommended)\n\n" "Person network: creates only the person nodes and connects " "two persons if they share at least one event.\n\n" "Event network: creates only the event nodes and connects " "two events if they share at least one person."), QMessageBox::NoButton, QMessageBox::Ok, tr("Bipartite graph"), tr("Person network"), tr("Event network"))) { case 1: sm_two_mode = 1; // bipartite β€” default break; case 2: sm_two_mode = 2; // person (Mode-1) projection break; case 3: sm_two_mode = 3; // event (Mode-2) projection break; default: sm_two_mode = 1; // user closed the dialog β€” go bipartite break; } } else if (fileFormat == FileType::ADJACENCY) { // Ask if there are labels defined on the first line of the ADJACENCY file switch ( slotHelpMessageToUser( USER_MSG_QUESTION_CUSTOM, tr("Opt for labels"), tr("Node labels?"), tr("This file contains an adjacency matrix (sociomatrix). " "Please specify whether there are node labels defined " "on the first (comment) line. \n"), QMessageBox::NoButton, QMessageBox::Ok, tr("Yes"), tr("No") )) { case 1: sm_has_labels = 1; break; case 2: sm_has_labels = 0; break; } } // Ask for data delimiter if (fileFormat == FileType::ADJACENCY || fileFormat == FileType::EDGELIST_SIMPLE || fileFormat == FileType::EDGELIST_WEIGHTED) { bool ok; delimiter = QInputDialog::getText( this, tr("Column delimiter in file "), tr("SocNetV supports edge list and adjacency " "files with arbitrary column delimiters. \n" "The default delimiter is one or more spaces.\n\n" "If the column delimiter in this file is " "other than simple space or TAB, \n" "please enter it below.\n\n" "For instance, if the delimiter is a " "comma or pipe enter \",\" or \"|\" respectively.\n\n" "Leave empty to use space or TAB as delimiter."), QLineEdit::Normal, QString(""), &ok); if (!ok || delimiter.isEmpty() || delimiter.isNull()) { delimiter = " "; } qCDebug(lcMainWindow) << "selected delimiter" << delimiter; } // Change the cursor to wait cursor QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); qCDebug(lcMainWindow) << "Calling graph to do the file load loading..."; activeGraph->loadFile( fileNameToLoad, codecName, fileFormat, delimiter, sm_two_mode, sm_has_labels); } /** * @brief Informs the user (and the MW) about the type of the network loaded * * Called from Parser/Graph when a network file is loaded to * display the appropiate message. * * @param type * @param fName * @param netName * @param totalNodes * @param totalEdges * @param elapsedTime * @param message */ void MainWindow::slotNetworkFileLoaded(const int &type, const QString &fName, const QString &netName, const int &totalNodes, const int &totalEdges, const qreal &density, const qint64 &elapsedTime, const QString &message) { // Restore the cursor override QApplication::restoreOverrideCursor(); if (type <= 0 || fName.isEmpty()) { qCDebug(lcMainWindow) << "ERROR LOADING FILE. FILE UNRECOGNIZED. Message from Parser: " << message << "Calling initApp()"; statusMessage(tr("Error loading requested file. Aborted.")); slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error loading file"), tr("Error loading network file"), tr("Sorry, the selected file is not in a supported format or encoding, " "or contains formatting errors. \n\n" "The error message was: \n\n" "%1" "\n\n" "What now? Review the message above to see if it helps you to fix the data file. " "Try a different codec in the preview window " "or if the file is of a legacy format (i.e. Pajek, UCINET, GraphViz, etc), " "please use the options in the Import sub menu. \n") .arg(message)); initApp(); return; } // A file has been loaded successfully. // Update our MW UI and save file path in settings qCDebug(lcMainWindow) << "Got signal that a file was loaded:" << " filename" << fName << " type " << type << " totalNodes" << totalNodes << " totalEdges" << totalEdges; fileName = fName; previous_fileName = fileName; QFileInfo fileInfo(fileName); fileNameNoPath = fileInfo.fileName(); Q_ASSERT_X(!fileNameNoPath.isEmpty(), "not empty filename ", "empty filename "); setWindowTitle(fileNameNoPath); setLastPath(fileName); // store this path and file QString fileFormatHuman; switch (type) { case FileType::NOT_SAVED: break; case FileType::GRAPHML: fileFormatHuman = "GraphML"; break; case FileType::PAJEK: fileFormatHuman = "Pajek"; break; case FileType::ADJACENCY: fileFormatHuman = "Adjacency"; break; case FileType::GRAPHVIZ: fileFormatHuman = "GraphViz"; break; case FileType::UCINET: fileFormatHuman = "UCINET"; break; case FileType::GML: fileFormatHuman = "GML"; break; case FileType::EDGELIST_WEIGHTED: fileFormatHuman = "Weighted list"; break; case FileType::EDGELIST_SIMPLE: fileFormatHuman = "Simple list"; break; case FileType::TWOMODE: fileFormatHuman = "Two-mode affiliation"; break; default: // Every non-expected case slotHelpMessageToUser(USER_MSG_CRITICAL, tr("Error"), tr("Error"), tr("Unrecognized format. Please specify the file-format using the Import Menu.")); break; } // Update LCDs rightPanelNodesLCD->setText(QString::number(totalNodes)); rightPanelEdgesLCD->setText(QString::number(totalEdges)); rightPanelDensityLCD->setText(QString::number(density)); statusMessage(tr("%1 formatted network, named '%2', loaded. Nodes: %3, Edges: %4, Density: %5. Elapsed time: %6 ms") .arg(fileFormatHuman) .arg(netName) .arg(totalNodes) .arg(totalEdges) .arg(density) .arg(elapsedTime)); networkSaveAct->setIcon(QIcon(":/images/file_download_48px.svg")); networkSaveAct->setEnabled(false); // Refresh the data table panel if it is visible if (m_tableWidget && m_tableDock->isVisible()) m_tableWidget->refresh(activeGraph); QApplication::restoreOverrideCursor(); } /** * @brief Refreshes LCD values and toggles the networkSave icon, when the network has been modified. * * @param directed * @param vertices * @param edges * @param density * @param needsSaving */ void MainWindow::slotNetworkChanged(const bool &directed, const int &vertices, const int &edges, const qreal &density, const bool &needsSaving) { qCDebug(lcMainWindow) << "Got signal that network changed. Updating mainwindow UI (LCDs, save icon, etc). Params: " << "directed" << directed << "vertices" << vertices << "edges" << edges << "density" << density << "needsSaving" << needsSaving; if (needsSaving) { networkSaveAct->setIcon(QIcon(":/images/file_download_48px_notsaved.svg")); networkSaveAct->setEnabled(true); } else { networkSaveAct->setIcon(QIcon(":/images/file_download_48px.svg")); networkSaveAct->setEnabled(false); } rightPanelNodesLCD->setText(QString::number(vertices)); if (!directed) { rightPanelEdgesLCD->setStatusTip(tr("Shows the total number of undirected edges in the network.")); rightPanelEdgesLCD->setToolTip(tr("The total number of undirected edges in the network.")); rightPanelNetworkTypeLCD->setStatusTip(tr("Undirected data mode. Toggle the menu option Edit->Edges->Undirected Edges to change it")); rightPanelNetworkTypeLCD->setToolTip(tr("The loaded network, if any, is undirected and \n" "any edge you add between nodes will be undirected.\n" "If you want to work with directed edges and/or \n" "transform the loaded network (if any) to directed \n" "disable the option Edit->Edges->Undirected \n" "or press CTRL+E+U")); rightPanelNetworkTypeLCD->setWhatsThis(tr("The loaded network, if any, is undirected and \n" "any edge you add between nodes will be undirected.\n" "If you want to work with directed edges and/or \n" "transform the loaded network (if any) to directed \n" "disable the option Edit->Edges->Undirected")); if (toolBoxEditEdgeModeSelect->currentIndex() == 0) { // Block signals: this is a UI-state sync, not a user edit-mode change. Without this, // setCurrentIndex() re-enters slotEditEdgeMode(), which calls setDirected/setUndirected // (mutating the graph) and triggers optionsEdgeArrowsAct->trigger(), which forces a // synchronous full-canvas repaint via statusMessage(). See #260. toolBoxEditEdgeModeSelect->blockSignals(true); toolBoxEditEdgeModeSelect->setCurrentIndex(1); toolBoxEditEdgeModeSelect->blockSignals(false); } rightPanelNetworkTypeLCD->setText("Undirected"); rightPanelEdgesLabel->setText(tr("Edges:")); rightPanelSelectedEdgesLabel->setText(tr("Edges:")); editEdgeUndirectedAllAct->setChecked(true); } else { rightPanelEdgesLCD->setStatusTip(tr("Shows the total number of directed edges in the network.")); rightPanelEdgesLCD->setToolTip(tr("The total number of directed edges in the network.")); rightPanelNetworkTypeLCD->setStatusTip(tr("Directed data mode. Toggle the menu option Edit->Edges->Undirected Edges to change it")); rightPanelNetworkTypeLCD->setToolTip(tr("The loaded network, if any, is directed and \n" "any link you add between nodes will be a directed arc.\n" "If you want to work with undirected edges and/or \n" "transform the loaded network (if any) to undirected \n" "enable the option Edit->Edges->Undirected")); rightPanelNetworkTypeLCD->setWhatsThis(tr("The loaded network, if any, is directed and \n" "any link you add between nodes will be a directed arc.\n" "If you want to work with undirected edges and/or \n" "transform the loaded network (if any) to undirected \n" "enable the option Edit->Edges->Undirected")); rightPanelNetworkTypeLCD->setText("Directed"); if (toolBoxEditEdgeModeSelect->currentIndex() == 1) { // See the matching comment in the "undirected" branch above. toolBoxEditEdgeModeSelect->blockSignals(true); toolBoxEditEdgeModeSelect->setCurrentIndex(0); toolBoxEditEdgeModeSelect->blockSignals(false); } rightPanelEdgesLabel->setText(tr("Arcs:")); rightPanelSelectedEdgesLabel->setText(tr("Arcs:")); editEdgeUndirectedAllAct->setChecked(false); } rightPanelEdgesLCD->setText(QString::number(edges)); rightPanelDensityLCD->setText(QString::number(density, 'f', 3)); qCDebug(lcMainWindow) << "Finished updating mainwindow."; } SocNetV-3.7/src/mainwindow/network/mainwindow_network_import_export.cpp000066400000000000000000000563761524402210300267760ustar00rootroot00000000000000/** * @file mainwindow_network_import_export.cpp * @brief Implements MainWindow Network menu: import (GraphML, GML, Pajek, Adjacency, GraphViz, UCINET, EdgeList, two-mode) and export (image, PDF, Pajek, SM, DOT, DL, GW, list, CSV, JSON) actions. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "webcrawler.h" #include "forms/dialogpreviewfile.h" #include "forms/dialogdatasetselect.h" #include "forms/dialogranderdosrenyi.h" #include "forms/dialograndsmallworld.h" #include "forms/dialograndscalefree.h" #include "forms/dialograndregular.h" #include "forms/dialograndlattice.h" #include "forms/dialogwebcrawler.h" #include "forms/dialogexportimage.h" #include "forms/dialogexportpdf.h" #include "widgets/graphtablewidget.h" #include "graph/io/table_export.h" #include #include #include #include #include /** * @brief Imports a network from a GraphML formatted file */ void MainWindow::slotNetworkImportGraphML() { bool m_checkSelectFileType = false; slotNetworkFileChoose(QString(), FileType::GRAPHML, m_checkSelectFileType); } /** * @brief Imports a network from a GML formatted file */ void MainWindow::slotNetworkImportGML() { bool m_checkSelectFileType = false; slotNetworkFileChoose(QString(), FileType::GML, m_checkSelectFileType); } /** * @brief Imports a network from a Pajek-like formatted file */ void MainWindow::slotNetworkImportPajek() { bool m_checkSelectFileType = false; slotNetworkFileChoose(QString(), FileType::PAJEK, m_checkSelectFileType); } /** * @brief Imports a network from a Adjacency matrix formatted file */ void MainWindow::slotNetworkImportAdjacency() { bool m_checkSelectFileType = false; slotNetworkFileChoose(QString(), FileType::ADJACENCY, m_checkSelectFileType); } /** * @brief Imports a network from a Dot (GraphViz) formatted file */ void MainWindow::slotNetworkImportGraphviz() { bool m_checkSelectFileType = false; slotNetworkFileChoose(QString(), FileType::GRAPHVIZ, m_checkSelectFileType); } /** * @brief Imports a network from a UCINET formatted file */ void MainWindow::slotNetworkImportUcinet() { bool m_checkSelectFileType = false; slotNetworkFileChoose(QString(), FileType::UCINET, m_checkSelectFileType); } /** * @brief Imports a network from a simple List or weighted List formatted file */ void MainWindow::slotNetworkImportEdgeList() { qCDebug(lcMainWindow) << "Importing an edge list network file..."; bool m_checkSelectFileType = false; switch ( slotHelpMessageToUser(USER_MSG_QUESTION_CUSTOM, tr("Select type..."), tr("Select type of edge list format"), tr("SocNetV can parse two kinds of edgelist formats: \n\n" "A. Edge lists with edge weights, " "where each line has exactly 3 columns: " "source target weight, i.e.:\n" "1 2 1 \n" "2 3 1 \n" "3 4 2 \n" "4 5 1 \n\n" "B. Simple edge lists without weights, where each line " "has two or more columns in the form: source, target1, target2, ... , i.e.:\n" "1 2 3 4 5 6\n" "2 3 4 \n" "3 5 8 7\n\n" "Please select the appropriate type of edge list format of " "the file you want to load:"), QMessageBox::NoButton, QMessageBox::NoButton, tr("Weighted"), tr("Simple non-weighted") )) { case 1: qCDebug(lcMainWindow) << "Weighted list selected! "; slotNetworkFileChoose(QString(), FileType::EDGELIST_WEIGHTED, m_checkSelectFileType); break; case 2: qCDebug(lcMainWindow) << "Simple list selected! "; slotNetworkFileChoose(QString(), FileType::EDGELIST_SIMPLE, m_checkSelectFileType); break; } } /** * @brief Imports a network from a two mode sociomatrix formatted file */ void MainWindow::slotNetworkImportTwoModeSM() { qCDebug(lcMainWindow) << "Importing a two mode sociomatrix network file..."; bool m_checkSelectFileType = false; slotNetworkFileChoose(QString(), FileType::TWOMODE, m_checkSelectFileType); } /** * @brief Opens the Export to Image Dialog */ void MainWindow::slotNetworkExportImageDialog() { qCDebug(lcMainWindow) << "Opening Image export dialog..."; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } statusMessage(tr("Opening Image export dialog. ")); m_dialogExportImage = new DialogExportImage(this); connect(m_dialogExportImage, &DialogExportImage::userChoices, this, &MainWindow::slotNetworkExportImage); m_dialogExportImage->exec(); } /** * @brief Exports the network to an image file * * @param filename * @param format * @param quality * @param compression */ void MainWindow::slotNetworkExportImage(const QString &filename, const QByteArray &format, const int &quality, const int &compression) { qCDebug(lcMainWindow) << "Exporting network to image file" << filename; if (filename.isEmpty()) { statusMessage(tr("No filename. Exporting to Image aborted.")); return; } // store this path setLastPath(filename); // Get network name from the filename tempFileNameNoPath = filename.split("/"); QString name = tempFileNameNoPath.last(); name.truncate(name.lastIndexOf(".")); // // Grab network from canvas // qCDebug(lcMainWindow) << "Grabbing network from the canvas"; qreal ratio = 1; qreal w = graphicsWidget->width() * ratio; qreal h = graphicsWidget->height() * ratio; QImage picture = QImage(w, h, QImage::Format_ARGB32_Premultiplied); qCDebug(lcMainWindow) << "Creating painter..."; QPainter p; qCDebug(lcMainWindow) << "Begin painter on picture..."; p.begin(&picture); qCDebug(lcMainWindow) << "render scene on painter..."; graphicsWidget->render(&p); // // Add name and optionally log // qCDebug(lcMainWindow) << "Adding name (and logo).."; p.setFont(QFont("Helvetica", 10, QFont::Normal, false)); if (appSettings["printLogo"] == "true") { QImage logo(":/images/socnetv-logo.png"); p.drawImage(5, 5, logo); p.drawText(7, 47, name); } else { p.drawText(5, 15, name); } qCDebug(lcMainWindow) << "End painter on picture..."; p.end(); QString author = "SocNetV v" + VERSION; qCDebug(lcMainWindow) << "slotNetworkExportImage() - saving image to file:" << filename << "format" << format << "quality:" << quality << "compression:" << compression << "Author:" << author; // // Write image to a file // QImageWriter imgWriter; imgWriter.setFormat(format); imgWriter.setQuality(quality); imgWriter.setCompression(compression); imgWriter.setFileName(filename); imgWriter.setText("Author", author); imgWriter.setText("", "Created by " + author); imgWriter.setOptimizedWrite(true); imgWriter.setProgressiveScanWrite(true); if (imgWriter.write(picture)) { slotHelpMessageToUser(USER_MSG_INFO, tr("Network exported to image file."), tr("Network exported to image file."), tr("Image filename: %1").arg(tempFileNameNoPath.last())); } else { slotHelpMessageToUser( USER_MSG_CRITICAL, tr("Error exporting to image file!"), tr("Error while exporting network to image file:"), imgWriter.errorString()); } } /** * @brief Opens the Export to PDF Dialog */ void MainWindow::slotNetworkExportPDFDialog() { qCDebug(lcMainWindow) << "MW::slotNetworkExportPDFDialog()"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } statusMessage(tr("Opening PDF export dialog. ")); m_dialogExportPDF = new DialogExportPDF(this); connect(m_dialogExportPDF, &DialogExportPDF::userChoices, this, &MainWindow::slotNetworkExportPDF); m_dialogExportPDF->exec(); } /** * @brief Exports the visible part of the network to a PDF Document * * @param pdfName * @param orientation * @param dpi * @param printerMode * @param pageSize */ void MainWindow::slotNetworkExportPDF(QString &pdfName, const QPageLayout::Orientation &orientation, const int &dpi, const QPrinter::PrinterMode printerMode = QPrinter::ScreenResolution, const QPageSize &pageSize = QPageSize(QPageSize::A4)) { qCDebug(lcMainWindow) << "MW::slotNetworkExportPDF()"; // Q_UNUSED(dpi); if (pdfName.isEmpty()) { statusMessage(tr("No filename. Exporting to PDF aborted.")); return; } else { setLastPath(pdfName); // store this path tempFileNameNoPath = pdfName.split("/"); QString name = tempFileNameNoPath.last(); name.truncate(name.lastIndexOf(".")); printerPDF = new QPrinter(printerMode); printerPDF->setOutputFormat(QPrinter::PdfFormat); printerPDF->setOutputFileName(pdfName); printerPDF->setPageOrientation(orientation); printerPDF->setPageSize(pageSize); printerPDF->setFontEmbeddingEnabled(true); printerPDF->setResolution(dpi); QPainter p; p.begin(printerPDF); graphicsWidget->render(&p, QRect(0, 0, printerPDF->width(), printerPDF->height()), graphicsWidget->viewport()->rect()); p.setFont(QFont("Helvetica", 8, QFont::Normal, false)); if (appSettings["printLogo"] == "true") { QImage logo(":/images/socnetv-logo.png"); p.drawImage(5, 5, logo); p.drawText(7, 47, name); } else { p.drawText(5, 15, name); } qCDebug(lcMainWindow) << "End painter on QPrinter..."; p.end(); delete printerPDF; // Reset to nullptr (WS7 MW0 finding, ASan-confirmed): printerPDF is a MainWindow member, // and closeEvent() also does `delete printerPDF;` on app quit. Without nulling it here, // that was a use-after-free/double-free on any session that exported a PDF at least once // before quitting - pre-existing, unrelated to the printer's own use in slotNetworkPrint() // (which never reassigns/deletes it). printerPDF = nullptr; } qCDebug(lcMainWindow) << "Exporting PDF to " << pdfName; tempFileNameNoPath = pdfName.split("/"); setLastPath(pdfName); slotHelpMessageToUser(USER_MSG_INFO, tr("Network exported to PDF file."), tr("Network exported to PDF file."), tr("PDF filename: %1").arg(tempFileNameNoPath.last())); } /** * @brief Exports the network to a Pajek-formatted file * Calls the relevant Graph method. */ void MainWindow::slotNetworkExportPajek() { qCDebug(lcMainWindow) << "MW::slotNetworkExportPajek"; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } statusMessage(tr("Exporting active network under new filename...")); QString fn = QFileDialog::getSaveFileName( this, tr("Export Network to File Named..."), getLastPath(), tr("Pajek (*.paj *.net *.pajek);;All (*)")); if (!fn.isEmpty()) { if (QFileInfo(fn).suffix().isEmpty()) { slotHelpMessageToUser(USER_MSG_INFO, tr("Missing file extension. I will use .paj instead."), tr("Missing file extension. I will use the .paj extension."), tr("Appending an extension .paj to the given filename...")); fn.append(".paj"); } fileName = fn; setLastPath(fileName); QFileInfo fileInfo(fileName); fileNameNoPath = fileInfo.fileName(); } else { statusMessage(tr("Saving aborted")); return; } activeGraph->saveToFile(fileName, FileType::PAJEK); } /** * @brief Exports the network to an adjacency matrix-formatted file * Calls the relevant Graph method. */ void MainWindow::slotNetworkExportSM() { qCDebug(lcMainWindow, "MW: slotNetworkExportSM()"); if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } statusMessage(tr("Exporting active network under new filename...")); QString fn = QFileDialog::getSaveFileName( this, tr("Export Network to File Named..."), getLastPath(), tr("Adjacency (*.csv *.txt *.adj *.sm *.net);;All (*)")); if (!fn.isEmpty()) { if (QFileInfo(fn).suffix().isEmpty()) { slotHelpMessageToUser(USER_MSG_INFO, tr("Missing file extension. I will use .csv instead."), tr("Missing file extension. I will use the .csv extension."), tr("Appending an extension .csv to the given filename...")); fn.append(".csv"); } fileName = fn; setLastPath(fileName); QFileInfo fileInfo(fileName); fileNameNoPath = fileInfo.fileName(); } else { statusMessage(tr("Saving aborted")); return; } bool saveEdgeWeights = false; if (activeGraph->isWeighted()) { switch ( slotHelpMessageToUser(USER_MSG_QUESTION, tr("Weighted graph. Social network with valued/weighted edges"), tr("Social network with valued/weighted edges"), tr("This social network includes valued/weighted edges " "(the depicted graph is weighted). " "Do you want to save the edge weights in the adjacency file?\n" "Select Yes if you want to save edge values " "in the resulting file. \n" "Select No, if you don't want edge values " "to be saved. In the later case, all non-zero values will be truncated to 1.")) ) { case QMessageBox::Yes: saveEdgeWeights = true; break; case QMessageBox::No: saveEdgeWeights = false; break; case QMessageBox::Cancel: statusMessage(tr("Save aborted...")); return; break; } } activeGraph->saveToFile(fileName, FileType::ADJACENCY, saveEdgeWeights); } /** * @brief Exports the active relation to a GraphViz DOT (.dot) file. * * Warns the user if the graph has multiple relations (only the active one is * written) or if it carries custom attributes (those are preserved in DOT). */ void MainWindow::slotNetworkExportDot() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } statusMessage(tr("Exporting active relation to GraphViz DOT file…")); if (activeGraph->relations() > 1) { slotHelpMessageToUser( USER_MSG_INFO, tr("Only active relation will be exported"), tr("Multi-relation graph β€” GraphViz DOT format"), tr("GraphViz DOT supports a single graph. " "Only the currently active relation \"%1\" will be written.") .arg(activeGraph->relationCurrentName())); } QString fn = QFileDialog::getSaveFileName( this, tr("Export Network to GraphViz DOT File…"), getLastPath(), tr("GraphViz DOT (*.dot);;All (*)")); if (fn.isEmpty()) { statusMessage(tr("Export aborted.")); return; } if (QFileInfo(fn).suffix().isEmpty()) fn.append(".dot"); setLastPath(fn); activeGraph->saveToFile(fn, FileType::GRAPHVIZ); } /** * @brief Exports the current graph to a UCINET DL-formatted file. * * Uses FULLMATRIX format. Multi-relation graphs are fully supported: * all relations are written as consecutive matrices in the DATA section. */ bool MainWindow::slotNetworkExportDL() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return false; } statusMessage(tr("Exporting network to UCINET DL file…")); QString fn = QFileDialog::getSaveFileName( this, tr("Export Network to UCINET DL File…"), getLastPath(), tr("UCINET DL (*.dl *.dat);;All (*)")); if (fn.isEmpty()) { statusMessage(tr("Export aborted.")); return false; } if (QFileInfo(fn).suffix().isEmpty()) fn.append(".dl"); setLastPath(fn); activeGraph->saveToFile(fn, FileType::UCINET); return true; } /** TODO: Exports the network to a GW-formatted file */ bool MainWindow::slotNetworkExportGW() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return false; } if (fileName.isEmpty()) { statusMessage(tr("Saving network under new filename...")); QString fn = QFileDialog::getSaveFileName( this, "Export GW", getLastPath(), 0); if (!fn.isEmpty()) { fileName = fn; setLastPath(fileName); } else { statusMessage(tr("Saving aborted")); return false; } } return true; } /** * @brief Exports the active relation to a simple or weighted edge list file. * * Asks the user whether to include edge weights. Warns when multiple * relations are present (only the active relation is written). Node labels * are used as identifiers; spaces are replaced with underscores. */ bool MainWindow::slotNetworkExportList() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return false; } if (activeGraph->relations() > 1) { slotHelpMessageToUser( USER_MSG_INFO, tr("Only active relation will be exported"), tr("Multi-relation graph β€” Edge List format"), tr("Edge List format supports a single relation. " "Only the currently active relation \"%1\" will be written.") .arg(activeGraph->relationCurrentName())); } // Ask weighted or simple bool weighted = false; if (activeGraph->isWeighted()) { switch (slotHelpMessageToUser( USER_MSG_QUESTION, tr("Include edge weights?"), tr("Weighted graph"), tr("This network has weighted edges. " "Do you want to include edge weights in the exported file?\n\n" "Yes β†’ weighted edge list (source target weight)\n" "No β†’ simple edge list (source target)"))) { case QMessageBox::Yes: weighted = true; break; case QMessageBox::No: weighted = false; break; case QMessageBox::Cancel: statusMessage(tr("Export aborted.")); return false; } } statusMessage(tr("Exporting active relation to Edge List file…")); const QString filter = weighted ? tr("Weighted Edge List (*.wlst *.csv *.txt);;All (*)") : tr("Simple Edge List (*.lst *.csv *.txt);;All (*)"); const QString defaultExt = weighted ? ".wlst" : ".lst"; QString fn = QFileDialog::getSaveFileName( this, tr("Export Network to Edge List File…"), getLastPath(), filter); if (fn.isEmpty()) { statusMessage(tr("Export aborted.")); return false; } if (QFileInfo(fn).suffix().isEmpty()) fn.append(defaultExt); setLastPath(fn); activeGraph->saveToFile( fn, weighted ? FileType::EDGELIST_WEIGHTED : FileType::EDGELIST_SIMPLE); return true; } /** * @brief Exports all node data (unfiltered) to a CSV file chosen by the user. */ void MainWindow::slotNetworkExportNodesCSV() { m_tableWidget->refresh(activeGraph); const QString path = QFileDialog::getSaveFileName( this, tr("Export Nodes as CSV"), tr("nodes.csv"), tr("CSV files (*.csv)")); if (path.isEmpty()) return; if (TableExport::toCSV(m_tableWidget->nodeModel(), path)) statusMessage(tr("Nodes exported to %1").arg(path)); else statusMessage(tr("Export failed: could not write to %1").arg(path)); } /** * @brief Exports all edge data (unfiltered) to a CSV file chosen by the user. */ void MainWindow::slotNetworkExportEdgesCSV() { m_tableWidget->refresh(activeGraph); const QString path = QFileDialog::getSaveFileName( this, tr("Export Edges as CSV"), tr("edges.csv"), tr("CSV files (*.csv)")); if (path.isEmpty()) return; if (TableExport::toCSV(m_tableWidget->edgeModel(), path)) statusMessage(tr("Edges exported to %1").arg(path)); else statusMessage(tr("Export failed: could not write to %1").arg(path)); } /** * @brief Exports all node data (unfiltered) to a JSON file chosen by the user. */ void MainWindow::slotNetworkExportNodesJSON() { m_tableWidget->refresh(activeGraph); const QString path = QFileDialog::getSaveFileName( this, tr("Export Nodes as JSON"), tr("nodes.json"), tr("JSON files (*.json)")); if (path.isEmpty()) return; if (TableExport::toJSON(m_tableWidget->nodeModel(), path)) statusMessage(tr("Nodes exported to %1").arg(path)); else statusMessage(tr("Export failed: could not write to %1").arg(path)); } /** * @brief Exports all edge data (unfiltered) to a JSON file chosen by the user. */ void MainWindow::slotNetworkExportEdgesJSON() { m_tableWidget->refresh(activeGraph); const QString path = QFileDialog::getSaveFileName( this, tr("Export Edges as JSON"), tr("edges.json"), tr("JSON files (*.json)")); if (path.isEmpty()) return; if (TableExport::toJSON(m_tableWidget->edgeModel(), path)) statusMessage(tr("Edges exported to %1").arg(path)); else statusMessage(tr("Export failed: could not write to %1").arg(path)); } SocNetV-3.7/src/mainwindow/network/mainwindow_network_random.cpp000066400000000000000000000447351524402210300253370ustar00rootroot00000000000000/** * @file mainwindow_network_random.cpp * @brief Implements MainWindow Network menu: sample dataset selection and random network generation (Erdos-Renyi, Scale-Free, Small-World, Regular, Gaussian, Ring/general Lattice). * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "webcrawler.h" #include "forms/dialogpreviewfile.h" #include "forms/dialogdatasetselect.h" #include "forms/dialogranderdosrenyi.h" #include "forms/dialograndsmallworld.h" #include "forms/dialograndscalefree.h" #include "forms/dialograndregular.h" #include "forms/dialograndlattice.h" #include "forms/dialogwebcrawler.h" #include "forms/dialogexportimage.h" #include "forms/dialogexportpdf.h" #include #include #include #include /** * @brief Displays the dataset selection dialog */ void MainWindow::slotNetworkDataSetSelect() { qCDebug(lcMainWindow) << "MW::slotNetworkDataSetSelect()"; // Close the current network if (!this->slotNetworkClose()) { // User cancelled. Do not proceed. return; } m_datasetSelectDialog = new DialogDataSetSelect(this); connect(m_datasetSelectDialog, SIGNAL(userChoices(QString)), this, SLOT(slotNetworkDataSetRecreate(QString))); m_datasetSelectDialog->exec(); } /** * @brief Recreates famous and widely used data sets in network analysis studies * * @param m_fileName * */ void MainWindow::slotNetworkDataSetRecreate(const QString m_fileName) { int fileFormat = 0; qCDebug(lcMainWindow) << "MW::slotNetworkDataSetRecreate() datadir+fileName: " << appSettings["dataDir"] + m_fileName; activeGraph->writeDataSetToFile(appSettings["dataDir"], m_fileName); if (m_fileName.endsWith(".graphml")) { fileFormat = FileType::GRAPHML; } else if (m_fileName.endsWith(".pajek") || m_fileName.endsWith(".paj") || m_fileName.endsWith(".net")) { fileFormat = FileType::PAJEK; } else if (m_fileName.endsWith(".sm") || m_fileName.endsWith(".adj") || m_fileName.endsWith(".csv")) { fileFormat = FileType::ADJACENCY; } else if (m_fileName.endsWith(".dot")) { fileFormat = FileType::GRAPHVIZ; } else if (m_fileName.endsWith(".dl")) { fileFormat = FileType::UCINET; } else if (m_fileName.endsWith(".gml")) { fileFormat = FileType::GML; } else if (m_fileName.endsWith(".wlst")) { fileFormat = FileType::EDGELIST_WEIGHTED; } else if (m_fileName.endsWith(".lst")) { fileFormat = FileType::EDGELIST_SIMPLE; } else if (m_fileName.endsWith(".2sm")) { fileFormat = FileType::TWOMODE; } slotNetworkFileLoad(appSettings["dataDir"] + m_fileName, "UTF-8", fileFormat); } /** * @brief Shows a dialog to create an Erdos-Renyi random network */ void MainWindow::slotNetworkRandomErdosRenyiDialog() { qCDebug(lcMainWindow) << "Showing the dialog to create a random Erdos-Renyi network "; // Close the current network if (!this->slotNetworkClose()) { // User cancelled. Do not proceed. return; } statusMessage(tr("Generate a random Erdos-Renyi network. ")); m_randErdosRenyiDialog = new DialogRandErdosRenyi( this, appSettings["randomErdosEdgeProbability"].toFloat(0)); connect(m_randErdosRenyiDialog, &DialogRandErdosRenyi::userChoices, this, &MainWindow::slotNetworkRandomErdosRenyi); m_randErdosRenyiDialog->exec(); } /** * @brief Creates an Erdos-Renyi random symmetric network * * @param newNodes * @param model * @param edges * @param eprob * @param mode * @param diag */ void MainWindow::slotNetworkRandomErdosRenyi(const int newNodes, const QString model, const int edges, const qreal eprob, const QString mode, const bool diag) { qCDebug(lcMainWindow) << "Request to create an Erdos-Renyi random network..."; appSettings["randomErdosEdgeProbability"] = QString::number(eprob); const qint64 expectedEdges = (model == "G(n,p)") ? static_cast(eprob * newNodes * (newNodes - 1)) : static_cast(edges); if (!confirmGenerationSize(expectedEdges, tr("ErdΕ‘s–RΓ©nyi network"), false)) { return; } auto success = std::make_shared(false); runGraphOperationAsync( [this, newNodes, model, edges, eprob, mode, diag, success]() { *success = activeGraph->randomNetErdosCreate(newNodes, model, edges, eprob, mode, diag); }, tr("Creating new Erdos-Renyi random network. Please wait... "), [this, newNodes, eprob, success]() { if (!*success) { statusMessage(tr("ErdΕ‘s–RΓ©nyi network creation cancelled or did not finish.")); return; } setWindowTitle("Untitled Erdos-Renyi random network"); double threshold = log(newNodes) / newNodes; if ((eprob) > threshold) slotHelpMessageToUser( USER_MSG_INFO, tr("ErdΕ‘s–RΓ©nyi random network created."), tr("Random network created. \n" "A new random network has been created according to the ErdΕ‘s–RΓ©nyi model."), tr("On average, edges should be %1. This graph is almost surely connected because: \n" "probability > ln(n) that is: %2 < %3") .arg(QString::number(eprob * newNodes * (newNodes - 1))) .arg(QString::number(eprob)) .arg(QString::number(threshold))); else slotHelpMessageToUser( USER_MSG_INFO, tr("ErdΕ‘s–RΓ©nyi random network created."), tr("Random network created. \n" "A new random network has been created according to the ErdΕ‘s–RΓ©nyi model."), tr("On average, edges should be %1. This graph is almost surely not connected because: \n" "probability < ln(n) that is: %2 < %3") .arg(QString::number(eprob * newNodes * (newNodes - 1))) .arg(QString::number(eprob)) .arg(QString::number(threshold))); }); } /** * @brief Shows a dialog to create a scale-free random network */ void MainWindow::slotNetworkRandomScaleFreeDialog() { qCDebug(lcMainWindow) << "Showing the dialog to create a random scale-free network "; // Close the current network if (!this->slotNetworkClose()) { // User cancelled. Do not proceed. return; } statusMessage(tr("Generate a random Scale-Free network. ")); m_randScaleFreeDialog = new DialogRandScaleFree(this); connect(m_randScaleFreeDialog, &DialogRandScaleFree::userChoices, this, &MainWindow::slotNetworkRandomScaleFree); m_randScaleFreeDialog->exec(); } /** * @brief Creates a scale-free random network * @param nodes * @param power * @param initialNodes * @param edgesPerStep * @param zeroAppeal * @param mode */ void MainWindow::slotNetworkRandomScaleFree(const int &newNodes, const int &power, const int &initialNodes, const int &edgesPerStep, const qreal &zeroAppeal, const QString &mode) { qCDebug(lcMainWindow) << "Request to create a new scale-free random network..."; auto success = std::make_shared(false); runGraphOperationAsync( [this, newNodes, power, initialNodes, edgesPerStep, zeroAppeal, mode, success]() { *success = activeGraph->randomNetScaleFreeCreate(newNodes, power, initialNodes, edgesPerStep, zeroAppeal, mode); }, tr("Creating new scale-free random network. Please wait... "), [this, newNodes, success]() { if (!*success) { statusMessage(tr("Scale-free network creation cancelled or did not finish.")); return; } setWindowTitle("Untitled scale-free network"); slotHelpMessageToUser( USER_MSG_INFO, tr("Scale-free random network created."), tr("Random network created. \n" "A new scale-free random network with %1 nodes has been created according to the BarabΓ‘si–Albert model.") .arg(newNodes), tr("A scale-free network is a network whose degree distribution follows a power law.")); }); } /** * @brief Shows a dialog to create a small-world random network */ void MainWindow::slotNetworkRandomSmallWorldDialog() { qCDebug(lcMainWindow) << "Showing the dialog to create a random small-world network "; // Close the current network if (!this->slotNetworkClose()) { // User cancelled. Do not proceed. return; } statusMessage(tr("Generate a random Small-World network. ")); m_randSmallWorldDialog = new DialogRandSmallWorld(this); connect(m_randSmallWorldDialog, &DialogRandSmallWorld::userChoices, this, &MainWindow::slotNetworkRandomSmallWorld); m_randSmallWorldDialog->exec(); } /** * @brief Creates a small-world random network * * @param newNodes * @param degree * @param beta * @param mode * @param diag */ void MainWindow::slotNetworkRandomSmallWorld(const int &newNodes, const int °ree, const qreal &beta, const QString &mode, const bool &diag) { Q_UNUSED(diag); qCDebug(lcMainWindow) << "Request to create a new small-world random network..."; auto success = std::make_shared(false); runGraphOperationAsync( [this, newNodes, degree, beta, mode, success]() { *success = activeGraph->randomNetSmallWorldCreate(newNodes, degree, beta, mode); }, tr("Creating new small-world random network. Please wait... "), [this, newNodes, success]() { if (!*success) { statusMessage(tr("Small-world network creation cancelled or did not finish.")); return; } setWindowTitle("Untitled small-world network"); slotHelpMessageToUser( USER_MSG_INFO, tr("Small-World random network created."), tr("Random network created. \n" "A new random network with %1 nodes has been created according to the Watts & Strogatz model.") .arg(newNodes), tr("A small-world network has short average path lengths and high clustering coefficient.")); }); } /** * @brief Shows a dialog to create a d-regular random network */ void MainWindow::slotNetworkRandomRegularDialog() { qCDebug(lcMainWindow) << "Showing the dialog to create a random d-regular network "; // Close the current network if (!this->slotNetworkClose()) { // User cancelled. Do not proceed. return; } statusMessage(tr("Generate a d-regular random network. ")); m_randRegularDialog = new DialogRandRegular(this); connect(m_randRegularDialog, &DialogRandRegular::userChoices, this, &MainWindow::slotNetworkRandomRegular); m_randRegularDialog->exec(); } /** * @brief Creates a pseudo-random d-regular network where every node has the same degree * * @param newNodes * @param degree * @param mode * @param diag */ void MainWindow::slotNetworkRandomRegular(const int &newNodes, const int °ree, const QString &mode, const bool &diag) { initApp(); auto success = std::make_shared(false); runGraphOperationAsync( [this, newNodes, degree, mode, diag, success]() { *success = activeGraph->randomNetRegularCreate(newNodes, degree, mode, diag); }, tr("Creating new d-regular random network. Please wait... "), [this, newNodes, degree, success]() { if (!*success) { statusMessage(tr("d-regular network creation cancelled or did not finish.")); return; } setWindowTitle("Untitled d-regular network"); slotHelpMessageToUser( USER_MSG_INFO, tr("d-regular network created."), tr("Random network created. \n" "A new d-regular random network with %1 nodes has been created.") .arg(newNodes), tr("Each node has the same number %1 of neighbours, aka the same degree d.") .arg(degree)); }); } void MainWindow::slotNetworkRandomGaussian() { } /** * @brief Creates a ring lattice network * * A ring lattice is a network where each node has degree d: * - d/2 edges to the "right" * - d/2 edges to the "left" */ void MainWindow::slotNetworkRandomRingLattice() { // Close the current network if (!this->slotNetworkClose()) { // User cancelled. Do not proceed. return; } bool ok; statusMessage("You have selected to create a ring lattice network. "); int newNodes = (QInputDialog::getInt( this, tr("Create ring lattice"), tr("This will create a ring lattice network, " "where each node has degree d:\n d/2 edges to the right " "and d/2 to the left.\n" "Please enter the number of nodes you want:"), 100, 4, maxRandomlyCreatedNodes, 1, &ok)); if (!ok) { statusMessage("You did not enter an integer. Aborting."); return; } int degree = QInputDialog::getInt( this, tr("Create ring lattice..."), tr("Now, enter an even number d. \n" "This is the total number of edges each new node will have:"), 2, 2, newNodes - 1, 2, &ok); if ((degree % 2) == 1) { slotHelpMessageToUser( USER_MSG_CRITICAL, tr("Error. Cannot create such network."), tr("Error. Cannot create such network!\n\n" "The degree %1 is not an even number.") .arg(degree), tr("A ring lattice is a graph with N vertices each connected to d neighbors, d / 2 on each side. \n" "Please try again entering an even number as degree.") ); return; } auto success = std::make_shared(false); runGraphOperationAsync( [this, newNodes, degree, success]() { *success = activeGraph->randomNetRingLatticeCreate(newNodes, degree, true); }, tr("Creating new ring-lattice random network. Please wait... "), [this, newNodes, success]() { if (!*success) { return; } setWindowTitle("Untitled ring-lattice network"); slotHelpMessageToUser( USER_MSG_INFO, tr("Ring lattice random network created."), tr("Random network created. \n" "A new ring-lattice random network with %1 nodes has been created.") .arg(newNodes), tr("A ring lattice is a graph with N vertices each connected to d neighbors, d / 2 on each side.") ); }); } /** * @brief Shows a dialog to create a "random" lattice network. */ void MainWindow::slotNetworkRandomLatticeDialog() { qCDebug(lcMainWindow) << "Showing the Random Lattice Dialog..."; statusMessage(tr("Generate a lattice network. ")); m_randLatticeDialog = new DialogRandLattice(this); connect(m_randLatticeDialog, &DialogRandLattice::userChoices, this, &MainWindow::slotNetworkRandomLattice); m_randLatticeDialog->exec(); } /** * @brief Creates a 'random' lattice network, i.e. a connected network where every node * has the same degree and is connected with its neighborhood * * A lattice is a network whose drawing forms a regular tiling * Lattices are also known as meshes or grids. * * @param newNodes * @param length * @param dimension * @param nei * @param mode * @param circular */ void MainWindow::slotNetworkRandomLattice(const int &newNodes, const int &length, const int &dimension, const int &nei, const QString &mode, const bool &circular) { qCDebug(lcMainWindow) << "Request to create a new lattice random network..."; initApp(); auto success = std::make_shared(false); runGraphOperationAsync( [this, newNodes, length, dimension, nei, mode, circular, success]() { *success = activeGraph->randomNetLatticeCreate(newNodes, length, dimension, nei, mode, circular); }, tr("Creating new lattice random network. Please wait... "), [this, newNodes, success]() { if (!*success) { statusMessage(tr("Lattice network creation cancelled or did not finish.")); return; } setWindowTitle("Untitled lattice network"); slotHelpMessageToUser( USER_MSG_INFO, tr("Lattice random network created."), tr("Random network created. \n" "A new lattice random network with %1 nodes has been created.") .arg(newNodes), tr("A lattice is a network whose drawing forms a regular tiling. " "Lattices are also known as meshes or grids.")); }); } SocNetV-3.7/src/mainwindow/network/mainwindow_network_view.cpp000066400000000000000000000241521524402210300250200ustar00rootroot00000000000000/** * @file mainwindow_network_view.cpp * @brief Implements MainWindow Network menu: file preview in system browser, internal text editor, sociomatrix view, and the Network toolbox auto-create combo box. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "webcrawler.h" #include "forms/dialogpreviewfile.h" #include "forms/dialogdatasetselect.h" #include "forms/dialogranderdosrenyi.h" #include "forms/dialograndsmallworld.h" #include "forms/dialograndscalefree.h" #include "forms/dialograndregular.h" #include "forms/dialograndlattice.h" #include "forms/dialogwebcrawler.h" #include "forms/dialogexportimage.h" #include "forms/dialogexportpdf.h" #include #include #include #include /** * @brief Called when user selects something in the Network Auto Create * selectbox of the toolbox * @param selectedIndex */ void MainWindow::toolBoxNetworkAutoCreateSelectChanged(const int &selectedIndex) { qCDebug(lcMainWindow) << "selected net auto create, index: " << selectedIndex; switch (selectedIndex) { case 0: break; case 1: // famous data-sets slotNetworkDataSetSelect(); break; case 2: // scale-free slotNetworkRandomScaleFreeDialog(); break; case 3: // sw slotNetworkRandomSmallWorldDialog(); break; case 4: // erdos slotNetworkRandomErdosRenyiDialog(); break; case 5: // lattice slotNetworkRandomLatticeDialog(); break; case 6: // d-regular slotNetworkRandomRegularDialog(); break; case 7: // ring lattice slotNetworkRandomRingLattice(); break; case 8: // web crawler slotNetworkWebCrawlerDialog(); break; }; qCDebug(lcMainWindow) << "Calling initComboBoxes() "; initComboBoxes(); } /** * @brief Displays the file of the loaded network. * * If the network has been modified, it prompts the user * to save the network, then view its file. */ void MainWindow::slotNetworkFileView() { qCDebug(lcMainWindow) << "Request to display current network file. Filename:" << fileName.toLatin1() << "isLoaded:" << activeGraph->isLoaded() << "isSaved:" << activeGraph->isSaved() << "graph filename:" << activeGraph->getFileName(); if (activeGraph->isLoaded() && activeGraph->isSaved()) { // network unmodified, read loaded file again. QFile f(fileName); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { qCDebug(lcMainWindow, "Error in open!"); return; } TextEditor *ed = new TextEditor(fileName, this, false); QFileInfo fileInfo(fileName); fileNameNoPath = fileInfo.fileName(); ed->setWindowTitle(fileNameNoPath); ed->show(); m_textEditors << ed; statusMessage(tr("Displaying network data file %1").arg(fileNameNoPath)); } else if (!activeGraph->isSaved()) { if (!activeGraph->isLoaded()) { // new network, not saved yet int response = slotHelpMessageToUser( USER_MSG_QUESTION, tr("New network not saved yet. You might want to save it first."), tr("This new network you created has not been saved yet."), tr("Do you want to open a file dialog to save your work " "(then I will display the file)?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (response == QMessageBox::Yes) { slotNetworkSaveAs(); } else { return; } } else { // loaded network, but modified int response = slotHelpMessageToUser( USER_MSG_QUESTION, tr("Current network has been modified. Save to the original file?"), tr("Current social network has been modified since last save."), tr("Do you want to save it to the original file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (response == QMessageBox::Yes) { slotNetworkSave(); } else if (response == QMessageBox::No) { slotNetworkSaveAs(); } else { // user pressed Cancel return; } } slotNetworkFileView(); } else { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); } } /** * @brief Opens the embedded text editor */ void MainWindow::slotNetworkTextEditor() { qCDebug(lcMainWindow) << "slotNetworkTextEditor() : "; TextEditor *ed = new TextEditor("", this, false); ed->setWindowTitle(tr("New Network File")); ed->show(); m_textEditors << ed; statusMessage(tr("Enter your network data here")); } /** * @brief Displays the adjacency matrix of the network. * * It uses a different method for writing the matrix to a file. * While slotNetworkExportSM uses << operator of Matrix class * (via adjacencyMatrix of Graph class), this is using directly the * writeMatrixAdjacency method of Graph class. * * Report format (HTML or CSV) follows the Settings > Reports > Output format preference. * CSV output always opens via the system's default handler (QDesktopServices::openUrl), * regardless of the "view reports in system browser" setting - the internal TextEditor's * plain-text mode isn't a useful CSV view. */ void MainWindow::slotNetworkViewSociomatrix() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const int reportFormat = appSettings["initReportsOutputFormat"].toInt(); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-adjacency-" + dateTime + ext; qCDebug(lcMainWindow) << "MW::slotNetworkViewSociomatrix() - dataDir" << appSettings["dataDir"] << "fn" << fn; // AVOID activeGraph->writeMatrix(fn,MATRIX_ADJACENCY), no preserving of node numbers // when nodes are deleted. auto success = std::make_shared(false); runGraphOperationAsync( [this, fn, reportFormat, success]() { *success = activeGraph->writeMatrixAdjacency(fn, true, reportFormat); }, tr("Creating and writing adjacency matrix"), [this, fn, reportFormat, success]() { if (!*success) { statusMessage(tr("Computation canceled, or could not write to file.")); return; } if (reportFormat == ReportFormat::Csv || appSettings["viewReportsInSystemBrowser"] == "true") { qCDebug(lcMainWindow) << "MW::slotNetworkViewSociomatrix() - " "calling QDesktopServices::openUrl for" << QUrl::fromLocalFile(fn); QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Adjacency matrix saved as ") + QDir::toNativeSeparators(fn)); }); } /** * @brief Displays a text-only plot of the network adjacency matrix */ void MainWindow::slotNetworkViewSociomatrixPlotText() { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } int N = activeNodes(); QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-adjacency-plot-" + dateTime + ".html"; bool simpler = false; if (N > 999) { qreal MB = (N * N * 10) / (1024 * 1024); switch (slotHelpMessageToUser( USER_MSG_QUESTION, tr("Very large network to plot!"), tr("Warning: Really large network"), tr("To plot a %1 x %1 matrix arranged in HTML table, " "I will need time to write a very large .html file , circa %2 MB in size. " "Instead, I can create a simpler / smaller HTML file without table. " "Press Yes to continue with simpler version, " "Press No to create large file with HTML table.") .arg(N) .arg(MB))) { case QMessageBox::Yes: simpler = true; break; case QMessageBox::No: simpler = false; break; default: return; break; } } runGraphOperationAsync( [this, fn, simpler]() { activeGraph->writeMatrixAdjacencyPlot(fn, simpler); }, tr("Creating plot of adjacency matrix of %1 nodes.").arg(N), [this, fn]() { if (activeGraph->progressCanceled()) { statusMessage(tr("Computation canceled.")); return; } if (appSettings["viewReportsInSystemBrowser"] == "true") { QDesktopServices::openUrl(QUrl::fromLocalFile(fn)); } else { TextEditor *ed = new TextEditor(fn, this, true); ed->show(); m_textEditors << ed; } statusMessage(tr("Visual form of adjacency matrix saved as ") + QDir::toNativeSeparators(fn)); }); } SocNetV-3.7/src/mainwindow/network/mainwindow_network_web.cpp000066400000000000000000000324771524402210300246340ustar00rootroot00000000000000/** * @file mainwindow_network_web.cpp * @brief Implements MainWindow Network menu web crawler dialog/launch and the QNetworkAccessManager request/reply/SSL error handlers. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "webcrawler.h" #include "forms/dialogpreviewfile.h" #include "forms/dialogdatasetselect.h" #include "forms/dialogranderdosrenyi.h" #include "forms/dialograndsmallworld.h" #include "forms/dialograndscalefree.h" #include "forms/dialograndregular.h" #include "forms/dialograndlattice.h" #include "forms/dialogwebcrawler.h" #include "forms/dialogexportimage.h" #include "forms/dialogexportpdf.h" #include #include #include #include /** * @brief Shows the web crawler dialog */ void MainWindow::slotNetworkWebCrawlerDialog() { // Close the current network if (!this->slotNetworkClose()) { // User cancelled. Do not proceed. return; } qCDebug(lcMainWindow) << "Opening web crawler dialog..."; m_WebCrawlerDialog = new DialogWebCrawler(this); connect(m_WebCrawlerDialog, &DialogWebCrawler::userChoices, this, &MainWindow::slotNetworkWebCrawler); m_WebCrawlerDialog->exec(); } /** * @brief Starts the web crawler with the user options * * @param startUrl * @param urlPatternsIncluded * @param urlPatternsExcluded * @param linkClasses * @param maxNodes * @param maxLinksPerPage * @param intLinks * @param childLinks * @param parentLinks * @param selfLinks * @param extLinks * @param extLinksCrawl * @param socialLinks * @param delayedRequests */ void MainWindow::slotNetworkWebCrawler(const QUrl &startUrl, const QStringList &urlPatternsIncluded, const QStringList &urlPatternsExcluded, const QStringList &linkClasses, const int &maxNodes, const int &maxLinksPerPage, const bool &intLinks, const bool &childLinks, const bool &parentLinks, const bool &selfLinks, const bool &extLinks, const bool &extLinksCrawl, const bool &socialLinks, const bool &delayedRequests) { // Check ssl if (!QSslSocket::supportsSsl()) { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("No SSL support."), tr("I cannot verify that your computer Operating System has OpenSSL support. \n\n" "OpenSSL is an Open Source software library for the Transport Layer Security (TLS) protocol (aka SSL), for applications that secure communications over computer networks. It is widely used by Internet servers, including the majority of HTTPS websites. \n\n" "Without OpenSSL libraries installed in your computer, I cannot crawl webpages/URLs using https:// \n\n" "So, please download and install OpenSSL in your OS and try again."), tr("Hint: Go to Help > System Information to see which OpenSSL version you need to install.")); return; } // Start the web crawler qCDebug(lcMainWindow) << "Calling Graph::startWebCrawler() to start the crawler process."; activeGraph->startWebCrawler( startUrl, urlPatternsIncluded, urlPatternsExcluded, linkClasses, maxNodes, maxLinksPerPage, intLinks, childLinks, parentLinks, selfLinks, extLinks, extLinksCrawl, socialLinks, delayedRequests); } /** * @brief Makes a network request to the given url * * Creates the QNetworkReply object to handle the reply. * * @param url * @param requestType */ void MainWindow::slotNetworkManagerRequest(const QUrl &url, const NetworkRequestType &requestType) { qCDebug(lcMainWindow) << "New network request for url:" << url.toString() << "requestType:" << requestType; // Create a network request object QNetworkRequest request; // Set request url request.setUrl(url); // Set request headers request.setRawHeader( "User-Agent", "SocNetV harmless spider - see https://socnetv.org"); // Create a network reply object through which we will make the call and handle the reply content qCDebug(lcMainWindow) << "Creating a network reply object and making the call..."; QNetworkReply *reply = networkManager->get(request); // Connect signals and slots switch (requestType) { case NetworkRequestType::Crawler: // Wire the reply to the activeGraph, which in turn will pass it to the web crawler connect(reply, &QNetworkReply::finished, activeGraph, &Graph::slotHandleCrawlerRequestReply); break; case NetworkRequestType::CheckUpdate: connect(reply, &QNetworkReply::finished, this, &MainWindow::slotHelpCheckUpdateParse); default: break; } #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) connect(reply, &QNetworkReply::errorOccurred, this, &MainWindow::slotNetworkManagerReplyError); #endif } /** * @brief Shows a message box to the user when a NetworkReply encounters errors. * * The message box contains info about the error code. * * @param code */ void MainWindow::slotNetworkManagerReplyError(const QNetworkReply::NetworkError &code) { // Get network reply from the sender QNetworkReply *reply = qobject_cast(sender()); // Get reply error string QString replyErrorMsg = reply->errorString(); // Will store the Qt description of the error QString errorMsg; #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) switch (code) { case QNetworkReply::NoError: errorMsg = "No error message!"; break; case QNetworkReply::ConnectionRefusedError: errorMsg = "the remote server refused the connection (the server is not accepting requests)"; break; case QNetworkReply::RemoteHostClosedError: errorMsg = "the remote server closed the connection prematurely, before the entire reply was received and processed"; break; case QNetworkReply::HostNotFoundError: errorMsg = "the remote host name was not found (invalid hostname)"; break; case QNetworkReply::TimeoutError: errorMsg = "the connection to the remote server timed out"; break; case QNetworkReply::OperationCanceledError: errorMsg = "the operation was canceled via calls to abort() or close() before it was finished."; break; case QNetworkReply::SslHandshakeFailedError: errorMsg = "the SSL/TLS handshake failed and the encrypted channel could not be established. The sslErrors() signal should have been emitted."; break; case QNetworkReply::TemporaryNetworkFailureError: errorMsg = "the connection was broken due to disconnection from the network, however the system has initiated roaming to another access point. The request should be resubmitted and will be processed as soon as the connection is re-established."; break; case QNetworkReply::NetworkSessionFailedError: errorMsg = "the connection was broken due to disconnection from the network or failure to start the network."; break; case QNetworkReply::BackgroundRequestNotAllowedError: errorMsg = "the background request is not currently allowed due to platform policy."; break; case QNetworkReply::TooManyRedirectsError: errorMsg = "while following redirects, the maximum limit was reached. The limit is by default set to 50 or as set by QNetworkRequest::setMaxRedirectsAllowed(). (This value was introduced in 5.6.)"; break; case QNetworkReply::InsecureRedirectError: errorMsg = "while following redirects, the network access API detected a redirect from a encrypted protocol (https) to an unencrypted one (http). (This value was introduced in 5.6.)"; break; case QNetworkReply::ProxyConnectionRefusedError: errorMsg = "the connection to the proxy server was refused (the proxy server is not accepting requests)"; break; case QNetworkReply::ProxyConnectionClosedError: errorMsg = "the proxy server closed the connection prematurely, before the entire reply was received and processed"; break; case QNetworkReply::ProxyNotFoundError: errorMsg = "the proxy host name was not found (invalid proxy hostname)"; break; case QNetworkReply::ProxyTimeoutError: errorMsg = "the connection to the proxy timed out or the proxy did not reply in time to the request sent"; break; case QNetworkReply::ProxyAuthenticationRequiredError: errorMsg = "the proxy requires authentication in order to honour the request but did not accept any credentials offered (if any)"; break; case QNetworkReply::ContentAccessDenied: errorMsg = "the access to the remote content was denied (similar to HTTP error 403)"; break; case QNetworkReply::ContentOperationNotPermittedError: errorMsg = "the operation requested on the remote content is not permitted"; break; case QNetworkReply::ContentNotFoundError: errorMsg = "the remote content was not found at the server (similar to HTTP error 404)"; break; case QNetworkReply::AuthenticationRequiredError: errorMsg = "the remote server requires authentication to serve the content but the credentials provided were not accepted (if any)"; break; case QNetworkReply::ContentReSendError: errorMsg = "the request needed to be sent again, but this failed for example because the upload data could not be read a second time."; break; case QNetworkReply::ContentConflictError: errorMsg = "the request could not be completed due to a conflict with the current state of the resource."; break; case QNetworkReply::ContentGoneError: errorMsg = "the requested resource is no longer available at the server."; break; case QNetworkReply::InternalServerError: errorMsg = "the server encountered an unexpected condition which prevented it from fulfilling the request."; break; case QNetworkReply::OperationNotImplementedError: errorMsg = "the server does not support the functionality required to fulfill the request."; break; case QNetworkReply::ServiceUnavailableError: errorMsg = "the server is unable to handle the request at this time."; break; case QNetworkReply::ProtocolUnknownError: errorMsg = "the Network Access API cannot honor the request because the protocol is not known"; break; case QNetworkReply::ProtocolInvalidOperationError: errorMsg = "the requested operation is invalid for this protocol"; break; case QNetworkReply::UnknownNetworkError: errorMsg = "an unknown network-related error was detected"; break; case QNetworkReply::UnknownProxyError: errorMsg = "an unknown proxy-related error was detected"; break; case QNetworkReply::UnknownContentError: errorMsg = "an unknown error related to the remote content was detected"; break; case QNetworkReply::ProtocolFailure: errorMsg = "a breakdown in protocol was detected (parsing error, invalid or unexpected responses, etc.)"; break; case QNetworkReply::UnknownServerError: errorMsg = "an unknown error related to the server response was detected"; break; } #endif slotHelpMessageToUserError("Network Error! \n\n" "Request to: '" + reply->request().url().toString() + "' encountered this error: \n\n" + replyErrorMsg + "\n\n" + "Error description: \n\n" + errorMsg + "\n\nPlease, try again. "); } /** * @brief Shows a message box to the user when the Network Manager encounters any SSL error. * * @param reply * @param errors */ void MainWindow::slotNetworkManagerSslErrors(QNetworkReply *reply, const QList &errors) { QString sslErrorString; // Read errors and get the error decriptions. foreach (QSslError error, errors) { sslErrorString = error.errorString(); } // Show the user a message box slotHelpMessageToUserError("SSL Error! \n\n" "Request to: '" + reply->request().url().toString() + "' encountered this SSL error: \n\n" + sslErrorString + "\n\n Please, try again later. "); } SocNetV-3.7/src/mainwindow/options/000077500000000000000000000000001524402210300173335ustar00rootroot00000000000000SocNetV-3.7/src/mainwindow/options/mainwindow_options.cpp000066400000000000000000001005721524402210300237730ustar00rootroot00000000000000/** * @file mainwindow_options.cpp * @brief Implements MainWindow Options/Settings menu: the Settings dialog launcher and every node/edge/canvas/window appearance toggle it wires. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "graphicsedge.h" #include "texteditor.h" #include "forms/dialogsettings.h" #include /** * @brief Opens the Settings dialog */ void MainWindow::slotOpenSettingsDialog() { // build dialog m_settingsDialog = new DialogSettings(appSettings, nodeShapeList, iconPathList, this); connect(m_settingsDialog, &DialogSettings::saveSettings, this, &MainWindow::saveSettings); connect(m_settingsDialog, &DialogSettings::setReportsDataDir, activeGraph, &Graph::setReportsDataDir); connect(m_settingsDialog, &DialogSettings::setReportsRealNumberPrecision, activeGraph, &Graph::setReportsRealNumberPrecision); connect(m_settingsDialog, &DialogSettings::setReportsLabelLength, activeGraph, &Graph::setReportsLabelLength); connect(m_settingsDialog, &DialogSettings::setReportsChartType, activeGraph, &Graph::setReportsChartType); connect(m_settingsDialog, &DialogSettings::setReportsOutputFormat, activeGraph, &Graph::setReportsOutputFormat); connect(m_settingsDialog, &DialogSettings::setDebugMsgs, this, &MainWindow::slotOptionsDebugMessages); connect(m_settingsDialog, &DialogSettings::setProgressDialog, this, &MainWindow::slotOptionsProgressDialogVisibility); connect(m_settingsDialog, &DialogSettings::setPrintLogo, this, &MainWindow::slotOptionsEmbedLogoExporting); connect(m_settingsDialog, &DialogSettings::setCustomStylesheet, this, &MainWindow::slotOptionsCustomStylesheet); connect(m_settingsDialog, &DialogSettings::setToolBar, this, &MainWindow::slotOptionsWindowToolbarVisibility); connect(m_settingsDialog, &DialogSettings::setStatusBar, this, &MainWindow::slotOptionsWindowStatusbarVisibility); connect(m_settingsDialog, &DialogSettings::setLeftPanel, this, &MainWindow::slotOptionsWindowLeftPanelVisibility); connect(m_settingsDialog, &DialogSettings::setRightPanel, this, &MainWindow::slotOptionsWindowRightPanelVisibility); connect(m_settingsDialog, &DialogSettings::setCanvasBgColor, this, &MainWindow::slotOptionsBackgroundColor); connect(m_settingsDialog, &DialogSettings::setCanvasBgImage, this, &MainWindow::slotOptionsBackgroundImage); connect(m_settingsDialog, &DialogSettings::setCanvasOpenGL, this, &MainWindow::slotOptionsCanvasOpenGL); connect(m_settingsDialog, &DialogSettings::setCanvasAntialiasing, this, &MainWindow::slotOptionsCanvasAntialiasing); connect(m_settingsDialog, &DialogSettings::setCanvasAntialiasingAutoAdjust, this, &MainWindow::slotOptionsCanvasAntialiasingAutoAdjust); connect(m_settingsDialog, &DialogSettings::setCanvasSmoothPixmapTransform, this, &MainWindow::slotOptionsCanvasSmoothPixmapTransform); connect(m_settingsDialog, &DialogSettings::setCanvasSavePainterState, this, &MainWindow::slotOptionsCanvasSavePainterState); connect(m_settingsDialog, &DialogSettings::setCanvasCacheBackground, this, &MainWindow::slotOptionsCanvasCacheBackground); connect(m_settingsDialog, &DialogSettings::setCanvasEdgeHighlighting, this, &MainWindow::slotOptionsCanvasEdgeHighlighting); connect(m_settingsDialog, &DialogSettings::setCanvasUpdateMode, this, &MainWindow::slotOptionsCanvasUpdateMode); connect(m_settingsDialog, &DialogSettings::setCanvasIndexMethod, this, &MainWindow::slotOptionsCanvasIndexMethod); connect(m_settingsDialog, SIGNAL(setNodeColor(QColor)), this, SLOT(slotEditNodeColorAll(QColor))); connect(m_settingsDialog, &DialogSettings::setNodeShape, this, &MainWindow::slotEditNodeShape); connect(m_settingsDialog, &DialogSettings::setNodeSize, this, &MainWindow::slotEditNodeSizeAll); connect(m_settingsDialog, &DialogSettings::setNodeNumbersVisibility, this, &MainWindow::slotOptionsNodeNumbersVisibility); connect(m_settingsDialog, &DialogSettings::setNodeNumbersInside, this, &MainWindow::slotOptionsNodeNumbersInside); connect(m_settingsDialog, &DialogSettings::setNodeNumberColor, this, &MainWindow::slotEditNodeNumbersColor); connect(m_settingsDialog, &DialogSettings::setNodeNumberSize, this, &MainWindow::slotEditNodeNumberSize); connect(m_settingsDialog, &DialogSettings::setNodeNumberDistance, this, &MainWindow::slotEditNodeNumberDistance); connect(m_settingsDialog, &DialogSettings::setNodeLabelsVisibility, this, &MainWindow::slotOptionsNodeLabelsVisibility); connect(m_settingsDialog, &DialogSettings::setNodeLabelSize, this, &MainWindow::slotEditNodeLabelSize); connect(m_settingsDialog, &DialogSettings::setNodeLabelColor, this, &MainWindow::slotEditNodeLabelsColor); connect(m_settingsDialog, &DialogSettings::setNodeLabelDistance, this, &MainWindow::slotEditNodeLabelDistance); connect(m_settingsDialog, &DialogSettings::setEdgesVisibility, this, &MainWindow::slotOptionsEdgesVisibility); connect(m_settingsDialog, &DialogSettings::setEdgesBezier, this, &MainWindow::slotOptionsEdgesBezier); connect(m_settingsDialog, &DialogSettings::setEdgeArrowsVisibility, this, &MainWindow::slotOptionsEdgeArrowsVisibility); connect(m_settingsDialog, &DialogSettings::setEdgeArrowSize, this, &MainWindow::slotOptionsEdgeArrowSize); connect(m_settingsDialog, &DialogSettings::setEdgeOffsetFromNode, this, &MainWindow::slotOptionsEdgeOffsetFromNode); connect(m_settingsDialog, &DialogSettings::setEdgeColor, this, &MainWindow::slotEditEdgeColorAll); connect(m_settingsDialog, &DialogSettings::setEdgeWeightNumbersVisibility, this, &MainWindow::slotOptionsEdgeWeightNumbersVisibility); connect(m_settingsDialog, &DialogSettings::setEdgeLabelsVisibility, this, &MainWindow::slotOptionsEdgeLabelsVisibility); connect(m_settingsDialog, &DialogSettings::setSaveZeroWeightEdges, this, &MainWindow::slotOptionsSaveZeroWeightEdges); connect(m_settingsDialog, &DialogSettings::setShowZeroWeightEdges, // #30 this, &MainWindow::slotOptionsShowZeroWeightEdges); // show settings dialog m_settingsDialog->exec(); } /** * @brief Toggles visibility of node numbers. * Persists the choice in appSettings["initNodeNumbersVisibility"]. * @param toggle */ void MainWindow::slotOptionsNodeNumbersVisibility(bool toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsNodeNumbersVisibility()" << toggle; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); statusMessage(tr("Toggle Nodes Numbers. Please wait...")); appSettings["initNodeNumbersVisibility"] = (toggle) ? "true" : "false"; graphicsWidget->setNodeNumberVisibility(toggle); optionsNodeNumbersVisibilityAct->setChecked(toggle); if (!toggle) { statusMessage(tr("Node Numbers are invisible now. " "Click the same option again to display them.")); } else { statusMessage(tr("Node Numbers are visible again...")); } QApplication::restoreOverrideCursor(); return; } /** * @brief Toggles whether node numbers are drawn inside or outside nodes. * Shows node numbers first if they are currently hidden. * Persists the choice in appSettings["initNodeNumbersInside"]. * @param toggle */ void MainWindow::slotOptionsNodeNumbersInside(bool toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsNodeNumbersInside()" << toggle; statusMessage(tr("Toggle Numbers inside nodes. Please wait...")); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // if node numbers are hidden, show them first. if (toggle && appSettings["initNodeNumbersVisibility"] != "true") slotOptionsNodeNumbersVisibility(true); appSettings["initNodeNumbersInside"] = (toggle) ? "true" : "false"; graphicsWidget->setNumbersInsideNodes(toggle); optionsNodeNumbersVisibilityAct->setChecked(toggle); if (toggle) { statusMessage(tr("Numbers inside nodes...")); } else { statusMessage(tr("Numbers outside nodes...")); } QApplication::restoreOverrideCursor(); } /** * @brief Toggles visibility of node labels. * Persists the choice in appSettings["initNodeLabelsVisibility"]. * @param toggle */ void MainWindow::slotOptionsNodeLabelsVisibility(bool toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsNodeLabelsVisibility()" << toggle; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); statusMessage(tr("Toggle Nodes Labels. Please wait...")); appSettings["initNodeLabelsVisibility"] = (toggle) ? "true" : "false"; graphicsWidget->setNodeLabelsVisibility(toggle); optionsNodeLabelsVisibilityAct->setChecked(toggle); if (!toggle) { statusMessage(tr("Node Labels are invisible now. " "Click the same option again to display them.")); } else { statusMessage(tr("Node Labels are visible again...")); } QApplication::restoreOverrideCursor(); } /** * @brief Toggles visibility of all edges. * Persists the choice in appSettings["initEdgesVisibility"]. * @param toggle */ void MainWindow::slotOptionsEdgesVisibility(bool toggle) { if (!activeEdges()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); statusMessage(tr("Toggle Edges. Please wait...")); appSettings["initEdgesVisibility"] = (toggle) ? "true" : "false"; graphicsWidget->setAllItemsVisibility(TypeEdge, toggle); if (!toggle) { statusMessage(tr("Edges are invisible now. Click again the same menu to display them.")); } else { statusMessage(tr("Edges visible again...")); } QApplication::restoreOverrideCursor(); } /** * @brief Toggles visibility of directional arrows on edges. * Persists the choice in appSettings["initEdgeArrows"]. * @param toggle */ void MainWindow::slotOptionsEdgeArrowsVisibility(bool toggle) { qCDebug(lcMainWindow) << "Request to toggle edges arrows to:" << toggle; statusMessage(tr("Toggling Edges' Arrows. Please wait...")); appSettings["initEdgeArrows"] = (toggle) ? "true" : "false"; graphicsWidget->setEdgeArrowsVisibility(toggle); if (toggle) { statusMessage(tr("Arrows in edges: on.")); } else { statusMessage(tr("Arrows in edges: off.")); } } /** * @brief Applies a new arrow size to all edges and persists the setting. * @param size Arrow size in pixels (2–20). */ void MainWindow::slotOptionsEdgeArrowSize(const int &size) { qCDebug(lcMainWindow) << "MW::slotOptionsEdgeArrowSize - new size" << size; appSettings["initEdgeArrowSize"] = QString::number(size); graphicsWidget->setEdgeArrowSize(size); statusMessage(tr("Changed edge arrow size to %1.").arg(size)); } /** * @brief Toggles edge weights during computations * @param toggle */ void MainWindow::slotOptionsEdgeWeightsDuringComputation(bool toggle) { askedAboutWeights = false; askAboutEdgeWeights(toggle); activeGraph->setModStatus(activeGraph->ModStatus::EdgeCount); } /** * @brief Toggles drawing edges as Bezier curves or straight lines. * Persists the choice in appSettings["initEdgeShape"] ("bezier" or "line"). * @param toggle */ void MainWindow::slotOptionsEdgesBezier(bool toggle) { if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } statusMessage(tr("Toggle edges bezier. Please wait...")); appSettings["initEdgeShape"] = toggle ? "bezier" : "line"; graphicsWidget->setEdgesBezier(toggle); statusMessage(tr("Edges drawn as %1.").arg(toggle ? tr("Bezier curves") : tr("straight lines"))); } /** * @brief MainWindow::slotOptionsEdgeThicknessPerWeight * @param toggle */ void MainWindow::slotOptionsEdgeThicknessPerWeight(bool toggle) { if (toggle) { } else { } } /** * @brief Changes the distance of edge arrows from nodes * Called from Edit menu option and DialogSettings * if offset=0, asks the user to enter a new offset * if v1=0 and v2=0, it changes all edges * @param v1 * @param v2 * @param offset */ void MainWindow::slotOptionsEdgeOffsetFromNode(const int &offset, const int &v1, const int &v2) { bool ok = false; qCDebug(lcMainWindow) << "MW::slotOptionsEdgeOffsetFromNode - new offset " << offset; int newOffset = offset; if (!newOffset) { newOffset = QInputDialog::getInt( this, "Change edge offset", tr("Change all edges offset from their nodes to: (1-16)"), appSettings["initNodeLabelDistance"].toInt(0, 10), 1, 16, 1, &ok); if (!ok) { statusMessage(tr("Change edge offset aborted.")); return; } } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); if (v1 && v2) { // change one edge offset only graphicsWidget->setEdgeOffsetFromNode(v1, v2, newOffset); } else { // change all appSettings["initEdgeOffsetFromNode"] = QString::number(newOffset); graphicsWidget->setEdgeOffsetFromNode(v1, v2, newOffset); } QApplication::restoreOverrideCursor(); statusMessage(tr("Changed edge offset from nodes.")); } /** * @brief Toggles visibility of edge weight numbers. * Persists the choice in appSettings["initEdgeWeightNumbersVisibility"]. * @param toggle */ void MainWindow::slotOptionsEdgeWeightNumbersVisibility(bool toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsEdgeWeightNumbersVisibility - Toggling Edges Weights"; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); statusMessage(tr("Toggle Edges Weights. Please wait...")); appSettings["initEdgeWeightNumbersVisibility"] = (toggle) ? "true" : "false"; graphicsWidget->setEdgeWeightNumbersVisibility(toggle); activeGraph->edgeWeightNumbersVisibilitySet(toggle); optionsEdgeWeightNumbersAct->setChecked(toggle); if (!toggle) { statusMessage(tr("Edge weights are invisible now. " "Click the same option again to display them.")); } else { statusMessage(tr("Edge weights are visible again...")); } QApplication::restoreOverrideCursor(); } /** * @brief Toggles visibility of edge labels. * Persists the choice in appSettings["initEdgeLabelsVisibility"]. * @param toggle */ void MainWindow::slotOptionsEdgeLabelsVisibility(bool toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsEdgeLabelsVisibility - Toggling Edges Weights"; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); statusMessage(tr("Toggle Edges Labels. Please wait...")); appSettings["initEdgeLabelsVisibility"] = (toggle) ? "true" : "false"; graphicsWidget->setEdgeLabelsVisibility(toggle); activeGraph->edgeLabelsVisibilitySet(toggle); optionsEdgeLabelsAct->setChecked(toggle); if (!toggle) { statusMessage(tr("Edge labels are invisible now. " "Click the same option again to display them.")); } else { statusMessage(tr("Edge labels are visible again...")); } QApplication::restoreOverrideCursor(); } /** * @brief Turns on/off saving zero-edge edge weights (only for GraphML at the moment) * @param toggle */ void MainWindow::slotOptionsSaveZeroWeightEdges(bool toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsSaveZeroWeightEdges - Toggling saving zero weight edges"; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); statusMessage(tr("Toggle zero-weight edges saving. Please wait...")); appSettings["saveZeroWeightEdges"] = (toggle) ? "true" : "false"; if (toggle) { statusMessage(tr("Zero-weight edges will be saved to graphml files. ")); } else { statusMessage(tr("Zero-weight edges will NOT be saved to graphml files.")); } QApplication::restoreOverrideCursor(); } /** * @brief Turns on/off drawing of zero-weight edges on the canvas (#30) * @param toggle */ void MainWindow::slotOptionsShowZeroWeightEdges(bool toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsShowZeroWeightEdges - toggle:" << toggle; appSettings["showZeroWeightEdges"] = (toggle) ? "true" : "false"; activeGraph->showZeroWeightEdgesSet(toggle); statusMessage(toggle ? tr("Zero-weight edges will be drawn on the canvas.") : tr("Zero-weight edges will not be drawn on the canvas.")); } /** * @brief Turns opengl on or off * @param toggle */ void MainWindow::slotOptionsCanvasOpenGL(const bool &toggle) { statusMessage(tr("Toggle openGL. Please wait...")); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // Inform graphicsWidget about the change graphicsWidget->setOptionsOpenGL(toggle); if (!toggle) { appSettings["opengl"] = "false"; statusMessage(tr("Using openGL off.")); } else { appSettings["opengl"] = "true"; statusMessage(tr("Using OpenGL on.")); } QApplication::restoreOverrideCursor(); } /** * @brief Turns antialiasing on or off * @param toggle */ void MainWindow::slotOptionsCanvasAntialiasing(bool toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsCanvasAntialiasingAutoAdjust() " << toggle; statusMessage(tr("Toggle anti-aliasing. Please wait...")); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); graphicsWidget->setOptionsAntialiasing(toggle); if (!toggle) { appSettings["antialiasing"] = "false"; statusMessage(tr("Anti-aliasing off.")); } else { appSettings["antialiasing"] = "true"; statusMessage(tr("Anti-aliasing on.")); } QApplication::restoreOverrideCursor(); } /** * @brief Turns antialiasing auto-adjustment on or off * @param toggle */ void MainWindow::slotOptionsCanvasAntialiasingAutoAdjust(const bool &toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsCanvasAntialiasingAutoAdjust() " << toggle; statusMessage(tr("Toggle anti-aliasing auto adjust. Please wait...")); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); graphicsWidget->setOptionsNoAntialiasingAutoAdjust(toggle); if (!toggle) { appSettings["canvasAntialiasingAutoAdjustment"] = "false"; statusMessage(tr("Antialiasing auto-adjustment off.")); } else { appSettings["canvasAntialiasingAutoAdjustment"] = "true"; statusMessage(tr("Antialiasing auto-adjustment on.")); } QApplication::restoreOverrideCursor(); } /** * @brief Turns smooth pixmap transformations on or off * @param toggle */ void MainWindow::slotOptionsCanvasSmoothPixmapTransform(const bool &toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsCanvasSmoothPixmapTransform() " << toggle; statusMessage(tr("Toggle smooth pixmap transformations. Please wait...")); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); if (!toggle) { graphicsWidget->setRenderHint(QPainter::SmoothPixmapTransform, toggle); appSettings["canvasSmoothPixmapTransform"] = "false"; statusMessage(tr("Smooth pixmap transformations off.")); } else { graphicsWidget->setRenderHint(QPainter::SmoothPixmapTransform, toggle); appSettings["canvasSmoothPixmapTransform"] = "true"; statusMessage(tr("Smooth pixmap transformations on.")); } QApplication::restoreOverrideCursor(); } /** * @brief Turns saving painter state on or off * @param toggle */ void MainWindow::slotOptionsCanvasSavePainterState(const bool &toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsCanvasSavePainterState() " << toggle; statusMessage(tr("Toggle saving painter state. Please wait...")); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); if (!toggle) { graphicsWidget->setOptimizationFlag(QGraphicsView::DontSavePainterState, true); appSettings["canvasPainterStateSave"] = "false"; statusMessage(tr("Saving painter state off.")); } else { graphicsWidget->setOptimizationFlag(QGraphicsView::DontSavePainterState, false); appSettings["canvasPainterStateSave"] = "true"; statusMessage(tr("Saving painter state on.")); } QApplication::restoreOverrideCursor(); } /** * @brief Turns caching of canvas background on or off * @param toggle */ void MainWindow::slotOptionsCanvasCacheBackground(const bool &toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsCanvasCacheBackground() " << toggle; statusMessage(tr("Toggle canvas background caching state. Please wait...")); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); if (!toggle) { graphicsWidget->setCacheMode(QGraphicsView::CacheNone); appSettings["canvasCacheBackground"] = "false"; statusMessage(tr("Canvas background caching off.")); } else { graphicsWidget->setCacheMode(QGraphicsView::CacheBackground); appSettings["canvasCacheBackground"] = "true"; statusMessage(tr("Canvas background caching on.")); } QApplication::restoreOverrideCursor(); } /** * @brief Turns selected edge highlighting * @param toggle */ void MainWindow::slotOptionsCanvasEdgeHighlighting(const bool &toggle) { qCDebug(lcMainWindow) << "MW::slotOptionsCanvasEdgeHighlighting() " << toggle; statusMessage(tr("Toggle edge highlighting state. Please wait...")); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); if (!toggle) { graphicsWidget->setEdgeHighlighting(toggle); appSettings["canvasEdgeHighlighting"] = "false"; statusMessage(tr("Edge highlighting off.")); } else { graphicsWidget->setEdgeHighlighting(toggle); appSettings["canvasEdgeHighlighting"] = "true"; statusMessage(tr("Edge highlighting on.")); } QApplication::restoreOverrideCursor(); } /** * @brief Sets canvas update mode * @param toggle */ void MainWindow::slotOptionsCanvasUpdateMode(const QString &mode) { qCDebug(lcMainWindow) << "MW::slotOptionsCanvasUpdateMode() " << mode; statusMessage(tr("Setting canvas update mode. Please wait...")); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); if (mode == "Full") { graphicsWidget->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); } else if (mode == "Minimal") { graphicsWidget->setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); } else if (mode == "Smart") { graphicsWidget->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate); } else if (mode == "Bounding Rectangle") { graphicsWidget->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); } else if (mode == "None") { graphicsWidget->setViewportUpdateMode(QGraphicsView::NoViewportUpdate); } else { // graphicsWidget->setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); } appSettings["canvasUpdateMode"] = mode; statusMessage(tr("Canvas update mode: ") + mode); QApplication::restoreOverrideCursor(); } /** * @brief Changes the indexing method of the graphics scene. * * Called from Settings dialog. * * @param method */ void MainWindow::slotOptionsCanvasIndexMethod(const QString &method) { qCDebug(lcMainWindow) << "Changing graphics scene index method to:" << method; statusMessage(tr("Setting canvas index method. Please wait...")); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); if (method == "BspTreeIndex") { // Qt default graphicsWidget->scene()->setItemIndexMethod(QGraphicsScene::BspTreeIndex); } else if (method == "NoIndex") { // for animated scenes graphicsWidget->scene()->setItemIndexMethod(QGraphicsScene::NoIndex); } else { // default graphicsWidget->scene()->setItemIndexMethod(QGraphicsScene::BspTreeIndex); } appSettings["canvasIndexMethod"] = method; statusMessage(tr("Canvas index method: ") + method); QApplication::restoreOverrideCursor(); } /** * @brief MainWindow::slotOptionsEmbedLogoExporting * * @param toggle */ void MainWindow::slotOptionsEmbedLogoExporting(bool toggle) { if (!toggle) { statusMessage(tr("SocNetV logo print off.")); appSettings["printLogo"] = "false"; } else { appSettings["printLogo"] = "true"; statusMessage(tr("SocNetV logo print on.")); } } /** * @brief Turns progress dialogs on or off * @param toggle * */ void MainWindow::slotOptionsProgressDialogVisibility(bool toggle) { statusMessage(tr("Toggle progressbar...")); if (!toggle) { appSettings["showProgressBar"] = "false"; statusMessage(tr("Progress bars off.")); } else { appSettings["showProgressBar"] = "true"; statusMessage(tr("Progress bars on.")); } } /** * @brief * Turns debugging messages on or off * @param toggle */ void MainWindow::slotOptionsDebugMessages(bool toggle) { if (!toggle) { qCDebug(lcMainWindow) << "Disabling debugging messages"; appSettings["printDebug"] = "false"; QLoggingCategory::setFilterRules("default.debug=false\n" "socnetv.*.debug=false"); statusMessage(tr("Debug messages off.")); } else { appSettings["printDebug"] = "true"; QLoggingCategory::setFilterRules("default.debug=true\n" "socnetv.*.debug=true"); qCDebug(lcMainWindow) << "Enabled debugging messages"; statusMessage(tr("Debug messages on.")); } } /** * @brief * Called from Options menu and Settings dialog * @param color QColor */ void MainWindow::slotOptionsBackgroundColor(QColor color) { if (!color.isValid()) { color = QColorDialog::getColor(QColor(appSettings["initBackgroundColor"]), this, "Change the background color"); } if (color.isValid()) { appSettings["initBackgroundColor"] = color.name(); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); graphicsWidget->setBackgroundBrush( QBrush(QColor(appSettings["initBackgroundColor"]))); QApplication::restoreOverrideCursor(); statusMessage(tr("Background changed.")); } else { // user pressed Cancel statusMessage(tr("Invalid color. ")); } } /** * @brief Toggles a custom background image on the canvas. * When enabled, opens a file dialog to pick the image. * Persists the path in appSettings["initBackgroundImage"]. * @param toggle */ void MainWindow::slotOptionsBackgroundImageSelect(bool toggle) { statusMessage(tr("Toggle BackgroundImage...")); QString m_fileName; if (toggle == false) { statusMessage(tr("BackgroundImage off.")); graphicsWidget->setBackgroundBrush( QBrush(QColor(appSettings["initBackgroundColor"]))); } else { m_fileName = QFileDialog::getOpenFileName( this, tr("Select one image"), getLastPath(), tr("Images (*.png *.jpg *.jpeg);;All (*.*)")); if (m_fileName.isNull()) appSettings["initBackgroundImage"] = ""; appSettings["initBackgroundImage"] = m_fileName; slotOptionsBackgroundImage(); } } /** * @brief * Enables/disables displaying a user-defined custom image in the background * Called from Settings Dialog and */ void MainWindow::slotOptionsBackgroundImage() { statusMessage(tr("Toggle BackgroundImage...")); if (appSettings["initBackgroundImage"].isEmpty()) { statusMessage(tr("BackgroundImage off.")); graphicsWidget->setBackgroundBrush( QBrush(QColor(appSettings["initBackgroundColor"]))); } else { setLastPath(appSettings["initBackgroundImage"]); graphicsWidget->setBackgroundBrush(QImage(appSettings["initBackgroundImage"])); graphicsWidget->setCacheMode(QGraphicsView::CacheBackground); statusMessage(tr("BackgroundImage on.")); } } /** * @brief Toggles full screen mode (F11) * @param toggle */ void MainWindow::slotOptionsWindowFullScreen(bool toggle) { if (toggle == false) { setWindowState(windowState() ^ Qt::WindowFullScreen); statusMessage(tr("Full screen mode off. Press F11 again to enter full screen.")); } else { setWindowState(windowState() ^ Qt::WindowFullScreen); statusMessage(tr("Full screen mode on. Press F11 again to exit full screen.")); } } /** * @brief Turns Toolbar on or off * @param toggle * */ void MainWindow::slotOptionsWindowToolbarVisibility(bool toggle) { statusMessage(tr("Toggle toolbar...")); if (toggle == false) { toolBar->hide(); appSettings["showToolBar"] = "false"; statusMessage(tr("Toolbar off.")); } else { toolBar->show(); appSettings["showToolBar"] = "true"; statusMessage(tr("Toolbar on.")); } } /** * @brief Turns window statusbar on or off * @param toggle */ void MainWindow::slotOptionsWindowStatusbarVisibility(bool toggle) { statusMessage(tr("Toggle statusbar...")); if (toggle == false) { statusBar()->hide(); appSettings["showStatusBar"] = "false"; statusMessage(tr("Status bar off.")); } else { statusBar()->show(); appSettings["showStatusBar"] = "true"; statusMessage(tr("Status bar on.")); } } /** * @brief Toggles left panel * @param toggle */ void MainWindow::slotOptionsWindowLeftPanelVisibility(bool toggle) { statusMessage(tr("Toggle left panel...")); if (toggle == false) { m_leftScrollArea->hide(); appSettings["showLeftPanel"] = "false"; statusMessage(tr("Left Panel off.")); } else { m_leftScrollArea->show(); appSettings["showLeftPanel"] = "true"; statusMessage(tr("Left Panel on.")); } } /** * @brief Toggles right panel * @param toggle */ void MainWindow::slotOptionsWindowRightPanelVisibility(bool toggle) { statusMessage(tr("Toggle left panel...")); if (toggle == false) { rightPanel->hide(); appSettings["showRightPanel"] = "false"; statusMessage(tr("Right Panel off.")); } else { rightPanel->show(); appSettings["showRightPanel"] = "true"; statusMessage(tr("Right Panel on.")); } } /** * @brief Toggles the use of our own Qt StyleSheet * * The .qss file is defined in project resources * * @param checked */ void MainWindow::slotOptionsCustomStylesheet(const bool checked = true) { if (checked) { slotStyleSheetByName(":/qss/default.qss"); appSettings["useCustomStyleSheet"] = "true"; } else { slotStyleSheetByName(""); appSettings["useCustomStyleSheet"] = "false"; } } /** * @brief Loads a custom Qt StyleSheet (.qss file) * * If sheetFileName is empty, the app uses platform-specific Qt style * * @param sheetFileName */ void MainWindow::slotStyleSheetByName(const QString &sheetFileName) { qCDebug(lcMainWindow) << "Opening stylesheet file: " << sheetFileName; QString styleSheet = ""; if (!sheetFileName.isEmpty()) { QFile file(sheetFileName); if (!file.open(QFile::ReadOnly)) { qCDebug(lcMainWindow) << "Could not open (for reading) file:" << sheetFileName; slotHelpMessageToUserError( tr("Cannot read stylesheet file %1:\n%2") .arg(sheetFileName) .arg(file.errorString())); return; } styleSheet = QString::fromLatin1(file.readAll()); } qApp->setStyleSheet(styleSheet); } SocNetV-3.7/src/mainwindow/scripting/000077500000000000000000000000001524402210300176425ustar00rootroot00000000000000SocNetV-3.7/src/mainwindow/scripting/mainwindow_interactive_script.cpp000066400000000000000000001766151524402210300265230ustar00rootroot00000000000000/** * @file mainwindow_interactive_script.cpp * @brief Implements MainWindow::runInteractiveScript() and processNextInteractiveCommand() - the WS12 CLI scripting mode command dispatcher. * @author Dimitris B. Kalamaras (http://dimitris.apeiro.gr) * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "mainwindow.h" #include "graph.h" #include "graphicswidget.h" #include "texteditor.h" #include "forms/dialogsettings.h" #include /** * @brief Loads a plain-text interactive script and starts executing it. See #261. * * One command per line: 'delay X' (wait X seconds) or 'new' (File > New). Commands run one at a * time via processNextInteractiveCommand(), each dispatched through the real Qt event loop so the * script behaves like an actual sequence of user actions rather than a tight synchronous loop - * and each only advances to the next line once its own dispatched work has genuinely finished, * not just been queued (see the dispatch-pattern note on processNextInteractiveCommand() itself). * * @param scriptPath */ void MainWindow::runInteractiveScript(const QString &scriptPath) { QFile file(scriptPath); if (!file.open(QFile::ReadOnly | QFile::Text)) { qWarning() << "Cannot read interactive script:" << scriptPath << file.errorString(); return; } QTextStream in(&file); m_interactiveScriptLines = in.readAll().split('\n'); m_interactiveScriptIndex = 0; qCDebug(lcMainWindow) << "Loaded interactive script:" << scriptPath << "-" << m_interactiveScriptLines.size() << "line(s)"; QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); } /** * @brief Executes one line of the interactive script, then schedules the next. See #261. * * Three dispatch shapes recur throughout this function. Whichever one a command uses, the rule is * always the same: only advance to the next command (call `processNextInteractiveCommand()`, or * queue it via `QTimer::singleShot`/`QMetaObject::invokeMethod`) once this command's own work has * genuinely finished - never merely queued or triggered. Getting this wrong is a real, * reproducible bug, not a style preference: it lets the next script command race this one's * still-in-flight work, found via an out-of-bounds crash where an unwrapped 'quit' right after * 'erdos' tore down Graph state while 'erdos' was still creating nodes. * * - **No dispatch** (`new`, `render`, `bulk-node-size`, `bulk-edge-color`): the work is already a * direct, blocking call on this (GUI) thread, no cross-thread queuing involved - genuinely done * by the time the call returns, so advancing immediately afterward is correct as-is. * - **Single-step dispatch** (`relation`, `erdos`, `erdos-m`, `save`, `add-node`, `add-edge`, * `add-relation`, `click-node`, `move`): the work is handed over via * `QMetaObject::invokeMethod(activeGraph, lambda, Qt::QueuedConnection)` - "queue this lambda to * run on activeGraph's thread, whenever that thread is free." `invokeMethod()` only *queues* the * job and returns immediately - it does not wait for the lambda to finish. `BENCH` logging AND * the call advancing to the next command (via a nested * `QMetaObject::invokeMethod(this, ..., Qt::QueuedConnection)` back to the GUI thread) must * therefore both happen *inside* that same lambda, at the point the work is genuinely done - * never outside/after the `invokeMethod()` call itself. * - **Two-step dispatch** (`filter_ego`, `filter_isolates`, `symmetrize_strongties`, * `symmetrize_cocitation`, `unilateral`, `distances`, `distances_bench`, * `report-centrality-degree`, `report-centrality-closeness`, `report-centrality-closeness-ir`, * `report-centrality-betweenness`, `report-centrality-stress`, `report-centrality-eccentricity`, * `report-centrality-power`, `report-centrality-information`, `report-centrality-eigenvector`, * `report-prestige-degree`, `report-prestige-proximity`, `report-prestige-pagerank`): used when * the work can * take many seconds and should show a progress dialog, via the `runGraphOperationAsync()` * helper. It takes two separate lambdas - one that performs the (possibly slow) computation, one * that runs only after that computation has fully completed, to report on it and advance the * script. Because both lambdas need to see the same timer/result values, and a plain local * variable would not survive between two separate lambdas, those values are wrapped in * `std::shared_ptr` instead - each lambda holds its own copy of the pointer, all pointing at the * same shared value. * * Every command logs one `"BENCH ... elapsed_ms="` line via `qInfo()` (not * `qDebug()`/`qCDebug()`) on completion, so this keeps printing even when debug output is quiet by * default. */ void MainWindow::processNextInteractiveCommand() { if (m_interactiveScriptIndex >= m_interactiveScriptLines.size()) { qCDebug(lcMainWindow) << "Interactive script finished."; return; } const QString line = m_interactiveScriptLines.at(m_interactiveScriptIndex).trimmed(); ++m_interactiveScriptIndex; if (line.isEmpty() || line.startsWith('#')) { processNextInteractiveCommand(); return; } qCDebug(lcMainWindow) << "Interactive script command:" << line; if (line == "new") { QElapsedTimer timer; timer.start(); slotNetworkNew(); qInfo() << "BENCH new N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); } else if (line.startsWith("delay ")) { bool ok = false; const double seconds = line.mid(6).trimmed().toDouble(&ok); if (!ok || seconds < 0) { qWarning() << "Malformed 'delay' command, skipping:" << line; processNextInteractiveCommand(); return; } auto timer = std::make_shared(); timer->start(); QTimer::singleShot(qRound(seconds * 1000), this, [this, timer]() { // elapsed_ms should read ~= the requested delay - a cheap sanity check that // scripted delays aren't drifting under load. qInfo() << "BENCH delay elapsed_ms=" << timer->elapsed(); processNextInteractiveCommand(); }); } else if (line.startsWith("relation ")) { // Dispatched the same way editRelationChangeCombo's activated(int) does - a queued // cross-thread call to Graph::relationSet(int), not a direct synchronous call. bool ok = false; const int relNum = line.mid(9).trimmed().toInt(&ok); if (!ok || relNum < 0) { qWarning() << "Malformed 'relation' command, skipping:" << line; processNextInteractiveCommand(); return; } QMetaObject::invokeMethod(activeGraph, [this, relNum]() { QElapsedTimer timer; timer.start(); activeGraph->relationSet(relNum); qInfo() << "BENCH relation N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); // Advance only after this queued lambda actually finishes on graphThread, not // immediately after invokeMethod() returns - otherwise the next script command can // race ahead while this one is still running (confirmed via a reproducible crash // for erdos/erdos-m, the same "single-step dispatch" pattern as this command). QMetaObject::invokeMethod(this, &MainWindow::processNextInteractiveCommand, Qt::QueuedConnection); }, Qt::QueuedConnection); } else if (line.startsWith("erdos ")) { // erdos

- creates a deterministic-shape (though not // deterministic-content, since it's still randomized) large Erdos-Renyi G(n,p) network // for reliable stress-testing, without depending on an external large dataset file // being present. Bypasses slotNetworkRandomErdosRenyi()'s modal "network created" info // dialog, which would otherwise block a scripted run with no one to click it. const QStringList parts = line.mid(6).trimmed().split(' ', Qt::SkipEmptyParts); bool nOk = false, pOk = false; const int n = parts.value(0).toInt(&nOk); const qreal p = parts.value(1).toDouble(&pOk); const QString directedness = parts.value(2, "directed"); if (!nOk || !pOk || n <= 0 || p < 0.0 || p > 1.0 || (directedness != "directed" && directedness != "undirected")) { qWarning() << "Malformed 'erdos' command, skipping:" << line; processNextInteractiveCommand(); return; } const qint64 expectedEdges = static_cast(p * n * (n - 1)); if (!confirmGenerationSize(expectedEdges, tr("ErdΕ‘s–RΓ©nyi network"), true)) { processNextInteractiveCommand(); return; } const QString mode = (directedness == "undirected") ? "graph" : "digraph"; QMetaObject::invokeMethod(activeGraph, [this, n, p, mode]() { QElapsedTimer timer; timer.start(); activeGraph->randomNetErdosCreate(n, "G(n,p)", 0, p, mode, false); qInfo() << "BENCH erdos N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); // Advance only after this queued lambda actually finishes on graphThread, not // immediately after invokeMethod() returns - otherwise the next script command // (e.g. 'quit', or another 'erdos'/'erdos-m') can race ahead while this one is // still running. Confirmed via a reproducible out-of-bounds QList crash: a script // with no delay between 'erdos' and 'quit' tore down Graph state mid-loop. QMetaObject::invokeMethod(this, &MainWindow::processNextInteractiveCommand, Qt::QueuedConnection); }, Qt::QueuedConnection); } else if (line.startsWith("erdos-m ")) { // erdos-m - same as 'erdos' but G(n,M): exactly M edges // placed at random, instead of each edge existing independently with probability p. // Node and edge *count* are then exactly reproducible run to run (which edges land // where is still randomized) - unlike G(n,p), where edge count only concentrates // around its expected value. Used by the WS6.6 render-perf fixture, where a stable // N/E shape matters for comparing timing thresholds across runs. const QStringList parts = line.mid(8).trimmed().split(' ', Qt::SkipEmptyParts); bool nOk = false, mOk = false; const int n = parts.value(0).toInt(&nOk); const int m = parts.value(1).toInt(&mOk); const QString directedness = parts.value(2, "directed"); if (!nOk || !mOk || n <= 0 || m < 0 || (directedness != "directed" && directedness != "undirected")) { qWarning() << "Malformed 'erdos-m' command, skipping:" << line; processNextInteractiveCommand(); return; } if (!confirmGenerationSize(m, tr("ErdΕ‘s–RΓ©nyi network"), true)) { processNextInteractiveCommand(); return; } const QString mode = (directedness == "undirected") ? "graph" : "digraph"; QMetaObject::invokeMethod(activeGraph, [this, n, m, mode]() { QElapsedTimer timer; timer.start(); activeGraph->randomNetErdosCreate(n, "G(n,M)", m, 0, mode, false); qInfo() << "BENCH erdos-m N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); // Advance only after this queued lambda actually finishes on graphThread - see the // matching comment on 'erdos' above for why (a reproducible crash otherwise). QMetaObject::invokeMethod(this, &MainWindow::processNextInteractiveCommand, Qt::QueuedConnection); }, Qt::QueuedConnection); } else if (line == "unilateral") { // Direct Graph::edgeFilterUnilateral() call via runGraphOperationAsync, matching // slotEditFilterEdgesUnilateral()'s own dispatch - same convention as // 'filter_isolates'/'symmetrize_strongties' below. Previously triggered the real // QAction instead, then advanced immediately without waiting for the (already async, // since WS15 P3) slot to actually finish - the same race class confirmed on 'erdos'. const bool toggleTo = !editFilterEdgesUnilateralAct->isChecked(); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, toggleTo]() { activeGraph->edgeFilterUnilateral(toggleTo); }, tr("Filtering unilateral edges (script)..."), [this, timer]() { qInfo() << "BENCH unilateral N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line.startsWith("save ")) { // save - always saves as GraphML, bypassing slotNetworkSave()'s fileName-based // format detection and "Save As" dialog fallback (which would block a scripted run). const QString path = line.mid(5).trimmed(); if (path.isEmpty()) { qWarning() << "Malformed 'save' command, skipping:" << line; processNextInteractiveCommand(); return; } QMetaObject::invokeMethod(activeGraph, [this, path]() { QElapsedTimer timer; timer.start(); activeGraph->saveToFile(path, FileType::GRAPHML, true, true); qInfo() << "BENCH save N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); // Advance only after this queued lambda actually finishes on graphThread - see the // matching comment on 'erdos' above for why (a reproducible crash otherwise). QMetaObject::invokeMethod(this, &MainWindow::processNextInteractiveCommand, Qt::QueuedConnection); }, Qt::QueuedConnection); } else if (line == "add-node") { QMetaObject::invokeMethod(activeGraph, [this]() { QElapsedTimer timer; timer.start(); activeGraph->vertexCreateAtPosRandom(false); qInfo() << "BENCH add-node N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); // Advance only after this queued lambda actually finishes on graphThread - see the // matching comment on 'erdos' above for why (a reproducible crash otherwise). QMetaObject::invokeMethod(this, &MainWindow::processNextInteractiveCommand, Qt::QueuedConnection); }, Qt::QueuedConnection); } else if (line.startsWith("add-edge ")) { const QStringList parts = line.mid(9).trimmed().split(' ', Qt::SkipEmptyParts); bool sOk = false, tOk = false; const int source = parts.value(0).toInt(&sOk); const int target = parts.value(1).toInt(&tOk); const qreal weight = parts.value(2, "1").toDouble(); if (!sOk || !tOk) { qWarning() << "Malformed 'add-edge' command, skipping:" << line; processNextInteractiveCommand(); return; } QMetaObject::invokeMethod(activeGraph, [this, source, target, weight]() { QElapsedTimer timer; timer.start(); activeGraph->edgeCreate(source, target, weight, "black", EdgeType::Directed, true, false, QString(), true); qInfo() << "BENCH add-edge N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); // Advance only after this queued lambda actually finishes on graphThread - see the // matching comment on 'erdos' above for why (a reproducible crash otherwise). QMetaObject::invokeMethod(this, &MainWindow::processNextInteractiveCommand, Qt::QueuedConnection); }, Qt::QueuedConnection); } else if (line.startsWith("add-relation ")) { const QString name = line.mid(13).trimmed(); if (name.isEmpty()) { qWarning() << "Malformed 'add-relation' command, skipping:" << line; processNextInteractiveCommand(); return; } QMetaObject::invokeMethod(activeGraph, [this, name]() { QElapsedTimer timer; timer.start(); activeGraph->relationAdd(name, true); qInfo() << "BENCH add-relation N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); // Advance only after this queued lambda actually finishes on graphThread - see the // matching comment on 'erdos' above for why (a reproducible crash otherwise). QMetaObject::invokeMethod(this, &MainWindow::processNextInteractiveCommand, Qt::QueuedConnection); }, Qt::QueuedConnection); } else if (line.startsWith("click-node ")) { // click-node - sets Graph::vertexClicked() without going through GraphicsWidget's // real mouse-press/selection-changed chain (which also gates filterNodesByEgoNetworkAct's // enabled state - irrelevant here since the commands below call Graph:: methods directly, // not via that QAction). Prerequisite for 'filter_ego'. bool ok = false; const int id = line.mid(11).trimmed().toInt(&ok); if (!ok) { qWarning() << "Malformed 'click-node' command, skipping:" << line; processNextInteractiveCommand(); return; } QMetaObject::invokeMethod(activeGraph, [this, id]() { QElapsedTimer timer; timer.start(); activeGraph->vertexClickedSet(id, QPointF()); qInfo() << "BENCH click-node id=" << id << "elapsed_ms=" << timer.elapsed(); // Advance only after this queued lambda actually finishes on graphThread - see the // matching comment on 'erdos' above for why (a reproducible crash otherwise). Also // makes 'filter_ego' below's own FIFO-ordering workaround belt-and-braces rather // than load-bearing, since vertexClickedSet() is now guaranteed complete before the // next command starts. QMetaObject::invokeMethod(this, &MainWindow::processNextInteractiveCommand, Qt::QueuedConnection); }, Qt::QueuedConnection); } else if (line == "filter_ego") { // WS15 P3 Group C test aid: mirrors slotFilterNodesByEgoNetwork()'s real // vertexFilterByEgoNetwork() call and runGraphOperationAsync dispatch, skipping only the // GUI-only filter-chip/filter-bar bookkeeping (same philosophy as distances_bench skipping // the disk write) - added specifically to reproduce and verify the fix for the reported // multi-minute freeze on a large network (2000+ nodes) with only the OS beachball as // feedback. Needs 'click-node ' first. // // vertexClicked() is read *inside* the graphThread-dispatched lambda below, not here on // the GUI thread before dispatch (unlike the real slotFilterNodesByEgoNetwork(), which // reads it on the GUI thread - safe there since a real user's node-click and menu-click // are two separate actions with ample time between them for click-node's own queued // vertexClickedSet() call to complete). Back-to-back scripted commands have no such gap: // reading it here raced with click-node's still-pending queued call and read 0. Reading // it inside this lambda instead makes it FIFO-ordered with click-node's own queued call, // since both run on graphThread's single event queue. auto v1 = std::make_shared(0); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, v1]() { *v1 = activeGraph->vertexClicked(); activeGraph->vertexFilterByEgoNetwork(*v1); }, tr("Filtering ego network (script)..."), [this, v1, timer]() { qInfo() << "BENCH filter_ego v1=" << *v1 << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line.startsWith("filter_isolates ")) { // filter_isolates - direct Graph::vertexIsolatedAllToggle() call via // runGraphOperationAsync, same dispatch as the real editFilterNodesIsolatesAct-driven // slotEditFilterNodesIsolates(), skipping only the QAction/status-message side effects. const QString arg = line.mid(16).trimmed(); if (arg != "on" && arg != "off") { qWarning() << "Malformed 'filter_isolates' command, skipping:" << line; processNextInteractiveCommand(); return; } const bool disableIsolates = (arg == "on"); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, disableIsolates]() { activeGraph->vertexIsolatedAllToggle(disableIsolates); }, tr("Filtering isolate nodes (script)..."), [this, disableIsolates, timer]() { qInfo() << "BENCH filter_isolates disable=" << disableIsolates << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line.startsWith("symmetrize_strongties ")) { // symmetrize_strongties - direct Graph::addRelationSymmetricStrongTies() // call via runGraphOperationAsync. Only safe to script on a single-relation network - the // real slotEditEdgeSymmetrizeStrongTies() shows a modal chooser dialog when multiple // relations exist, which would block an unattended script (same reason 'erdos'/'save' // bypass their own real slots' modal dialogs). const QString arg = line.mid(22).trimmed(); if (arg != "all" && arg != "current") { qWarning() << "Malformed 'symmetrize_strongties' command, skipping:" << line; processNextInteractiveCommand(); return; } const bool allRelations = (arg == "all"); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, allRelations]() { activeGraph->addRelationSymmetricStrongTies(allRelations); }, tr("Symmetrizing strong ties (script)..."), [this, allRelations, timer]() { qInfo() << "BENCH symmetrize_strongties all=" << allRelations << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "symmetrize_cocitation") { // Direct Graph::relationAddCocitation() call via runGraphOperationAsync - no modal // dialog in the real slot for this one, so no bypass needed. auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this]() { activeGraph->relationAddCocitation(); }, tr("Computing cocitation relation (script)..."), [this, timer]() { qInfo() << "BENCH symmetrize_cocitation N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "distances" || line.startsWith("distances ")) { // distances [weights] [inverse] [dropisolates] [csv] - mirrors the real Cohesion > // Distances Matrix menu action (slotAnalyzeMatrixDistances()) exactly: same computation // (writeMatrix() -> graphMatrixDistanceGeodesicCreate()), same runGraphOperationAsync() // dispatch, same output file - just without opening a TextEditor afterward (no one // present to view it in a script). Also skips askAboutEdgeWeights()'s modal prompt // entirely - the tokens below already answer what it would ask. WS16 (#113): the 'csv' // token selects ReportFormat::Csv explicitly, rather than reading the persisted Settings // preference - a script has no Settings dialog to reflect, and an explicit token matches // the shape of every other boolean token here. const QStringList tokens = line.mid(9).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool inverseWeights = tokens.contains("inverse"); const bool dropIsolates = tokens.contains("dropisolates"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-matrix-geodesic-distances-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, inverseWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writeMatrix(fn, MATRIX_DISTANCES, considerWeights, inverseWeights, dropIsolates, "Rows", false, reportFormat); }, tr("Computing geodesic distances. Please wait..."), [this, considerWeights, inverseWeights, dropIsolates, success, timer]() { qInfo() << "BENCH distances weights=" << considerWeights << "inverse=" << inverseWeights << "dropisolates=" << dropIsolates << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "distances_bench" || line.startsWith("distances_bench ")) { // distances_bench [weights] [inverse] [dropisolates] [centralities] - benchmarking-only // variant of 'distances' above: same dispatch mechanism (runGraphOperationAsync) and // same underlying computation, but skips the disk write entirely, for isolating pure // computation cost. 'centralities' has no real-menu equivalent (the GUI computes each // centrality index via ~9 separate menu actions, not one combined action), so it lives // here rather than on 'distances'. const QStringList tokens = line.mid(15).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool inverseWeights = tokens.contains("inverse"); const bool dropIsolates = tokens.contains("dropisolates"); const bool computeCentralities = tokens.contains("centralities"); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, computeCentralities, considerWeights, inverseWeights, dropIsolates]() { activeGraph->graphDistancesGeodesic(computeCentralities, considerWeights, inverseWeights, dropIsolates); }, tr("Computing geodesic distances (benchmark, no disk write). Please wait..."), [this, considerWeights, inverseWeights, dropIsolates, computeCentralities, timer]() { qInfo() << "BENCH distances_bench weights=" << considerWeights << "inverse=" << inverseWeights << "dropisolates=" << dropIsolates << "centralities=" << computeCentralities << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-centrality-degree" || line.startsWith("report-centrality-degree ")) { // report-centrality-degree [weights] [dropisolates] [csv] - WS16 (#113) baseline: mirrors // the real Analyze > Centrality > Degree menu action (slotAnalyzeCentralityDegree()) // exactly, same as 'distances' mirrors slotAnalyzeMatrixDistances() above. This is the // first centrality/prestige report ever exercised headlessly - none of the other 11 // writeCentrality*/writePrestige* functions have a script command yet. Also skips // askAboutEdgeWeights()'s modal prompt entirely - the tokens below already answer what it // would ask. The 'csv' token selects ReportFormat::Csv explicitly (Step 2), matching // 'distances'' handling. const QStringList tokens = line.mid(24).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool dropIsolates = tokens.contains("dropisolates"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-centrality-out-degree-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityDegree(fn, considerWeights, dropIsolates, reportFormat); }, tr("Computing Degree Centralities. Please wait..."), [this, considerWeights, dropIsolates, success, timer]() { qInfo() << "BENCH report-centrality-degree weights=" << considerWeights << "dropisolates=" << dropIsolates << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-centrality-closeness" || line.startsWith("report-centrality-closeness ")) { // report-centrality-closeness [weights] [inverse] [dropisolates] [csv] - WS16 Step 2: // mirrors slotAnalyzeCentralityCloseness() exactly, same shape as report-centrality-degree. const QStringList tokens = line.mid(27).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool inverseWeights = tokens.contains("inverse"); const bool dropIsolates = tokens.contains("dropisolates"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-centrality-closeness-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, inverseWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityCloseness( fn, considerWeights, inverseWeights, dropIsolates, reportFormat); }, tr("Computing Closeness Centralities. Please wait..."), [this, considerWeights, inverseWeights, dropIsolates, success, timer]() { qInfo() << "BENCH report-centrality-closeness weights=" << considerWeights << "inverse=" << inverseWeights << "dropisolates=" << dropIsolates << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-centrality-closeness-ir" || line.startsWith("report-centrality-closeness-ir ")) { // report-centrality-closeness-ir [weights] [inverse] [dropisolates] [csv] - WS16 Step 2: // mirrors slotAnalyzeCentralityClosenessIR() exactly. const QStringList tokens = line.mid(30).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool inverseWeights = tokens.contains("inverse"); const bool dropIsolates = tokens.contains("dropisolates"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-centrality-closeness-influence-range-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, inverseWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityClosenessInfluenceRange( fn, considerWeights, inverseWeights, dropIsolates, reportFormat); }, tr("Computing Influence Range Closeness Centralities. Please wait..."), [this, considerWeights, inverseWeights, dropIsolates, success, timer]() { qInfo() << "BENCH report-centrality-closeness-ir weights=" << considerWeights << "inverse=" << inverseWeights << "dropisolates=" << dropIsolates << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-centrality-betweenness" || line.startsWith("report-centrality-betweenness ")) { // report-centrality-betweenness [weights] [inverse] [dropisolates] [csv] - WS16 Step 2: // mirrors slotAnalyzeCentralityBetweenness() exactly. const QStringList tokens = line.mid(29).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool inverseWeights = tokens.contains("inverse"); const bool dropIsolates = tokens.contains("dropisolates"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-centrality-betweenness-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, inverseWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityBetweenness( fn, considerWeights, inverseWeights, dropIsolates, reportFormat); }, tr("Computing Betweenness Centralities. Please wait..."), [this, considerWeights, inverseWeights, dropIsolates, success, timer]() { qInfo() << "BENCH report-centrality-betweenness weights=" << considerWeights << "inverse=" << inverseWeights << "dropisolates=" << dropIsolates << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-centrality-stress" || line.startsWith("report-centrality-stress ")) { // report-centrality-stress [weights] [inverse] [dropisolates] [csv] - WS16 Step 2: // mirrors slotAnalyzeCentralityStress() exactly. const QStringList tokens = line.mid(24).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool inverseWeights = tokens.contains("inverse"); const bool dropIsolates = tokens.contains("dropisolates"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-centrality-stress-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, inverseWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityStress( fn, considerWeights, inverseWeights, dropIsolates, reportFormat); }, tr("Computing Stress Centralities. Please wait..."), [this, considerWeights, inverseWeights, dropIsolates, success, timer]() { qInfo() << "BENCH report-centrality-stress weights=" << considerWeights << "inverse=" << inverseWeights << "dropisolates=" << dropIsolates << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-centrality-eccentricity" || line.startsWith("report-centrality-eccentricity ")) { // report-centrality-eccentricity [weights] [inverse] [dropisolates] [csv] - WS16 Step 2: // mirrors slotAnalyzeCentralityEccentricity() exactly. const QStringList tokens = line.mid(30).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool inverseWeights = tokens.contains("inverse"); const bool dropIsolates = tokens.contains("dropisolates"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-centrality-eccentricity-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, inverseWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityEccentricity( fn, considerWeights, inverseWeights, dropIsolates, reportFormat); }, tr("Computing Eccentricity Centralities. Please wait..."), [this, considerWeights, inverseWeights, dropIsolates, success, timer]() { qInfo() << "BENCH report-centrality-eccentricity weights=" << considerWeights << "inverse=" << inverseWeights << "dropisolates=" << dropIsolates << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-centrality-power" || line.startsWith("report-centrality-power ")) { // report-centrality-power [weights] [inverse] [dropisolates] [csv] - WS16 Step 2: // mirrors slotAnalyzeCentralityPower() exactly. const QStringList tokens = line.mid(23).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool inverseWeights = tokens.contains("inverse"); const bool dropIsolates = tokens.contains("dropisolates"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-centrality-power-Gil-Schmidt-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, inverseWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityPower( fn, considerWeights, inverseWeights, dropIsolates, reportFormat); }, tr("Computing Power Centralities. Please wait..."), [this, considerWeights, inverseWeights, dropIsolates, success, timer]() { qInfo() << "BENCH report-centrality-power weights=" << considerWeights << "inverse=" << inverseWeights << "dropisolates=" << dropIsolates << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-centrality-information" || line.startsWith("report-centrality-information ")) { // report-centrality-information [weights] [inverse] [csv] - WS16 Step 2: mirrors // slotAnalyzeCentralityInformation() exactly (no dropIsolates - Information Centrality // doesn't take one; it always excludes isolates internally). const QStringList tokens = line.mid(29).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool inverseWeights = tokens.contains("inverse"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-centrality-information-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, inverseWeights, reportFormat, success]() { *success = activeGraph->writeCentralityInformation( fn, considerWeights, inverseWeights, reportFormat); }, tr("Computing Information Centralities. Please wait..."), [this, considerWeights, inverseWeights, success, timer]() { qInfo() << "BENCH report-centrality-information weights=" << considerWeights << "inverse=" << inverseWeights << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-centrality-eigenvector" || line.startsWith("report-centrality-eigenvector ")) { // report-centrality-eigenvector [weights] [inverse] [csv] - WS16 Step 2: mirrors // slotAnalyzeCentralityEigenvector() exactly (dropIsolates is fixed false there, so no // token for it here either). const QStringList tokens = line.mid(29).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool inverseWeights = tokens.contains("inverse"); const bool dropIsolates = false; const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-centrality-eigenvector-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, inverseWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writeCentralityEigenvector( fn, considerWeights, inverseWeights, dropIsolates, reportFormat); }, tr("Computing Eigenvector Centralities. Please wait..."), [this, considerWeights, inverseWeights, success, timer]() { qInfo() << "BENCH report-centrality-eigenvector weights=" << considerWeights << "inverse=" << inverseWeights << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-prestige-degree" || line.startsWith("report-prestige-degree ")) { // report-prestige-degree [weights] [dropisolates] [csv] - WS16 Step 2: mirrors // slotAnalyzePrestigeDegree() exactly. const QStringList tokens = line.mid(22).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool dropIsolates = tokens.contains("dropisolates"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-prestige-degree-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writePrestigeDegree(fn, considerWeights, dropIsolates, reportFormat); }, tr("Computing Degree Prestige. Please wait..."), [this, considerWeights, dropIsolates, success, timer]() { qInfo() << "BENCH report-prestige-degree weights=" << considerWeights << "dropisolates=" << dropIsolates << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-prestige-proximity" || line.startsWith("report-prestige-proximity ")) { // report-prestige-proximity [dropisolates] [csv] - WS16 Step 2: mirrors // slotAnalyzePrestigeProximity() exactly (considerWeights/inverseWeights are fixed // true/false there, so no tokens for them here either). const QStringList tokens = line.mid(25).trimmed().split(' ', Qt::SkipEmptyParts); const bool dropIsolates = tokens.contains("dropisolates"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-prestige-proximity-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, dropIsolates, reportFormat, success]() { *success = activeGraph->writePrestigeProximity(fn, true, false, dropIsolates, reportFormat); }, tr("Computing Proximity Prestige. Please wait..."), [this, dropIsolates, success, timer]() { qInfo() << "BENCH report-prestige-proximity dropisolates=" << dropIsolates << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-prestige-pagerank" || line.startsWith("report-prestige-pagerank ")) { // report-prestige-pagerank [dropisolates] [csv] - WS16 Step 2: mirrors // slotAnalyzePrestigePageRank() exactly. const QStringList tokens = line.mid(24).trimmed().split(' ', Qt::SkipEmptyParts); const bool dropIsolates = tokens.contains("dropisolates"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-prestige-pagerank-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, dropIsolates, reportFormat, success]() { *success = activeGraph->writePrestigePageRank(fn, dropIsolates, reportFormat); }, tr("Computing PageRank Prestige. Please wait..."), [this, dropIsolates, success, timer]() { qInfo() << "BENCH report-prestige-pagerank dropisolates=" << dropIsolates << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-reciprocity" || line.startsWith("report-reciprocity ")) { // report-reciprocity [weights] [csv] - WS16 Step 3: mirrors slotAnalyzeReciprocity() // exactly. const QStringList tokens = line.mid(18).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-reciprocity-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, reportFormat, success]() { *success = activeGraph->writeReciprocity(fn, considerWeights, reportFormat); }, tr("Computing Reciprocity. Please wait..."), [this, considerWeights, success, timer]() { qInfo() << "BENCH report-reciprocity weights=" << considerWeights << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-eccentricity" || line.startsWith("report-eccentricity ")) { // report-eccentricity [weights] [inverse] [dropisolates] [csv] - WS16 Step 3: mirrors // slotAnalyzeEccentricity() exactly. const QStringList tokens = line.mid(19).trimmed().split(' ', Qt::SkipEmptyParts); const bool considerWeights = tokens.contains("weights"); const bool inverseWeights = tokens.contains("inverse"); const bool dropIsolates = tokens.contains("dropisolates"); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-eccentricity-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, considerWeights, inverseWeights, dropIsolates, reportFormat, success]() { *success = activeGraph->writeEccentricity( fn, considerWeights, inverseWeights, dropIsolates, reportFormat); }, tr("Computing Eccentricity. Please wait..."), [this, considerWeights, inverseWeights, dropIsolates, success, timer]() { qInfo() << "BENCH report-eccentricity weights=" << considerWeights << "inverse=" << inverseWeights << "dropisolates=" << dropIsolates << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-clustering-coefficient" || line.startsWith("report-clustering-coefficient ")) { // report-clustering-coefficient [csv] - WS16 Step 3: mirrors // slotAnalyzeClusteringCoefficient() exactly (considerWeights is fixed true there, so // no token for it here either). const QStringList tokens = line.mid(29).trimmed().split(' ', Qt::SkipEmptyParts); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-clustering-coefficient-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, reportFormat, success]() { *success = activeGraph->writeClusteringCoefficient(fn, true, reportFormat); }, tr("Computing Clustering Coefficients. Please wait..."), [this, success, timer]() { qInfo() << "BENCH report-clustering-coefficient" << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "report-triad-census" || line.startsWith("report-triad-census ")) { // report-triad-census [csv] - WS16 Step 3: mirrors slotAnalyzeCommunitiesTriadCensus() // exactly (considerWeights is fixed true there, so no token for it here either). const QStringList tokens = line.mid(19).trimmed().split(' ', Qt::SkipEmptyParts); const int reportFormat = tokens.contains("csv") ? ReportFormat::Csv : ReportFormat::Html; const QString dateTime = QDateTime::currentDateTime().toString(QString("yy-MM-dd-hhmmss")); const QString ext = (reportFormat == ReportFormat::Csv) ? ".csv" : ".html"; const QString fn = appSettings["dataDir"] + "socnetv-report-triad-census-" + dateTime + ext; auto success = std::make_shared(false); auto timer = std::make_shared(); timer->start(); runGraphOperationAsync( [this, fn, reportFormat, success]() { *success = activeGraph->writeTriadCensus(fn, true, reportFormat); }, tr("Computing Triad Census. Please wait..."), [this, success, timer]() { qInfo() << "BENCH report-triad-census" << "success=" << *success << "N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer->elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); }); } else if (line == "render") { // WS10/WS6.6 render-perf benchmarking aid: forces a synchronous repaint (unlike // update(), which only schedules one), so this measures the actual paint cost of // the current canvas state rather than whether a repaint got scheduled at all. QElapsedTimer timer; timer.start(); graphicsWidget->viewport()->repaint(); qInfo() << "BENCH render N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); } else if (line.startsWith("bulk-node-size ")) { // bulk-node-size - calls slotEditNodeSizeAll() directly with a nonzero size so // its modal QInputDialog (used when newSize==0) never opens, which would otherwise // block a scripted run with no one to click it. bool ok = false; const int newSize = line.mid(15).trimmed().toInt(&ok); if (!ok || newSize <= 0) { qWarning() << "Malformed 'bulk-node-size' command, skipping:" << line; processNextInteractiveCommand(); return; } QElapsedTimer timer; timer.start(); slotEditNodeSizeAll(newSize); qInfo() << "BENCH bulk-node-size N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); } else if (line.startsWith("bulk-edge-color ")) { // bulk-edge-color - calls slotEditEdgeColorAll() directly with a valid QColor // so its modal QColorDialog (used when color is invalid) never opens. const QString name = line.mid(16).trimmed(); const QColor color(name); if (!color.isValid()) { qWarning() << "Malformed 'bulk-edge-color' command, skipping:" << line; processNextInteractiveCommand(); return; } QElapsedTimer timer; timer.start(); slotEditEdgeColorAll(color); qInfo() << "BENCH bulk-edge-color N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); QTimer::singleShot(0, this, &MainWindow::processNextInteractiveCommand); } else if (line.startsWith("move ")) { // move - simulates a single-node drag (and its cascading edge-geometry // updates) by setting an absolute canvas position, same backlog item named in // roadmap_ws12_cli_scripting_mode.md. const QStringList parts = line.mid(5).trimmed().split(' ', Qt::SkipEmptyParts); bool nodeOk = false, xOk = false, yOk = false; const int node = parts.value(0).toInt(&nodeOk); const int x = parts.value(1).toInt(&xOk); const int y = parts.value(2).toInt(&yOk); if (!nodeOk || !xOk || !yOk) { qWarning() << "Malformed 'move' command, skipping:" << line; processNextInteractiveCommand(); return; } QMetaObject::invokeMethod(activeGraph, [this, node, x, y]() { QElapsedTimer timer; timer.start(); activeGraph->vertexPosSet(node, x, y); qInfo() << "BENCH move N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); // Advance only after this queued lambda actually finishes on graphThread - see the // matching comment on 'erdos' above for why (a reproducible crash otherwise). QMetaObject::invokeMethod(this, &MainWindow::processNextInteractiveCommand, Qt::QueuedConnection); }, Qt::QueuedConnection); } else if (line == "quit") { // Ends the app cleanly so a scripted run doesn't need to be killed externally. Goes // through the real close() / closeEvent() path (not a raw QCoreApplication::quit(), // which still routes through closeEvent() during Qt's shutdown) with // m_interactiveScriptQuitting set, so the "save changes?" modal - which would otherwise // block forever with no one present to answer it - is skipped. QElapsedTimer timer; timer.start(); qCDebug(lcMainWindow) << "Interactive script requested quit."; // Log BENCH *before* close(), not after: close() can trigger teardown of activeGraph // as part of application shutdown, so reading activeNodes()/activeEdges() afterward // would touch already-torn-down state. qInfo() << "BENCH quit N=" << activeNodes() << "E=" << activeEdges() << "elapsed_ms=" << timer.elapsed(); m_interactiveScriptQuitting = true; close(); } else { qWarning() << "Unknown interactive script command, skipping:" << line; processNextInteractiveCommand(); } } SocNetV-3.7/src/matrix.cpp000077500000000000000000002776071524402210300155220ustar00rootroot00000000000000/** * @file matrix.cpp * @brief Implements the Matrix class for handling adjacency and sociomatrix data structures in network analysis. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "matrix.h" #define TINY 1.0e-20 // Fix #269: relative-tolerance threshold for ludcmp()'s pivot-magnitude singularity check. // The checked value is already scaled by its row's own maximum, so this is a dimensionless // relative test - not tied to the matrices' raw value range. #define RELATIVE_SINGULARITY_TOLERANCE 1.0e-12 #include //allows the use of RAND_MAX macro #include #include #include //needed for fabs, qFloor etc #include Q_LOGGING_CATEGORY(lcMatrix, "socnetv.matrix") /** * @brief Constructs a rowDim x colDim matrix, all cells zero-initialized. Defaults to 0x0 * (an empty matrix) - use resize()/zeroMatrix()/identityMatrix() to size it later. * @param rowDim * @param colDim */ Matrix::Matrix (int rowDim, int colDim) : m_rows (rowDim), m_cols(colDim) { m_data = new (nothrow) qreal[ static_cast(m_rows) * m_cols ](); Q_CHECK_PTR( m_data ); rebuildRowPtr(); } /** * @brief Copy constructor: creates a Matrix identical to (an independent copy of) b. * Allows the `Matrix a = b` declaration form. * @param b */ Matrix::Matrix(const Matrix &b) { qCDebug(lcMatrix)<< "Matrix:: constructor"; m_rows=b.m_rows; m_cols=b.m_cols ; const size_t n = static_cast(m_rows) * m_cols; m_data = new (nothrow) qreal[n]; Q_CHECK_PTR( m_data ); for (size_t i=0; i 0){ qCDebug(lcMatrix) << "Matrix::clear() deleting old rows"; m_rows=0; m_cols=0; delete [] m_data; delete [] m_rowPtr; } } /** * @brief Resizes this matrix to m x n, discarding any previous contents. All cells start * zero-initialized. * @param m New row count. * @param n New column count. */ void Matrix::resize (const int m, const int n) { qCDebug(lcMatrix) << "Matrix: resize() "; clear(); m_rows = m; m_cols = n; m_data = new (nothrow) qreal[ static_cast(m_rows) * m_cols ](); Q_CHECK_PTR( m_data ); rebuildRowPtr(); } /** * @brief Scans every cell of this matrix (including the diagonal) and reports the smallest * and largest values found, plus whether any cell has a fractional (non-integer) part - * used by report writers to decide display precision and, for distance matrices, whether * the max value is RAND_MAX (meaning some pair is unreachable). * Complexity: O(rows()*cols()). * @param min Output: the smallest value found. * @param max Output: the largest value found. * @param hasRealNumbers Output: true if any cell has a non-zero fractional part. */ void Matrix::findMinMaxValues (qreal &min, qreal & max, bool &hasRealNumbers){ max=0; min=RAND_MAX; hasRealNumbers = false; for (int r = 0; r < rows(); ++r) { for (int c = 0; c < cols(); ++c) { if ( fmod (item(r,c), 1.0) != 0 ) { hasRealNumbers = true; } if ( item(r,c) > max) { max = item(r,c) ; } if ( item(r,c) < min){ min = item(r,c) ; } } } } /** * @brief Like findMinMaxValues(), but skips the diagonal (r==c) and also reports which * pair of distinct vertices achieved the min/max - used to find the closest and farthest * pair of nodes in a distance/dissimilarity matrix (e.g. by hierarchical clustering, which * repeatedly needs "which two clusters are nearest right now"). * Complexity: O(rows()*cols()). * @param min Output: the smallest off-diagonal value found. * @param max Output: the largest off-diagonal value found. * @param imin Output: row of the cell where the minimum was found. * @param jmin Output: column of the cell where the minimum was found. * @param imax Output: row of the cell where the maximum was found. * @param jmax Output: column of the cell where the maximum was found. */ void Matrix::NeighboursNearestFarthest (qreal &min, qreal & max, int &imin, int &jmin, int &imax, int &jmax){ max=0; min=RAND_MAX; for (int r = 0; r < rows(); ++r) { for (int c = 0; c < cols(); ++c) { if (r==c) continue; if ( item(r,c) > max) { max = item(r,c) ; imax = r; jmax=c; } if ( item(r,c) < min){ min = item(r,c) ; imin = r; jmin=c; } } } } /** * @brief Makes this square matrix the identity square matrix I * @param dim */ void Matrix::identityMatrix(int dim) { qCDebug(lcMatrix) << "Matrix::identityMatrix() -- deleting old rows"; clear(); m_rows=dim; m_cols=dim; m_data = new (nothrow) qreal[ static_cast(m_rows) * m_cols ](); Q_CHECK_PTR( m_data ); rebuildRowPtr(); for (int i=0;i(m_rows) * m_cols ](); Q_CHECK_PTR( m_data ); rebuildRowPtr(); } // item()/setItem()/clearItem() are now inline in matrix.h. /** * @brief Deletes row erased and column erased from this (square) matrix, shifting every * later row/column back by one to close the gap. Complexity: O(rows()*cols()) - rebuilds * the whole matrix into a new, smaller buffer (see the comment inside). * @param erased row/col to delete */ void Matrix::deleteRowColumn(int erased){ qCDebug(lcMatrix) << "Matrix:deleteRowColumn() - will delete row and column" << erased << "m_rows before" << m_rows; // Removing a column changes every row's stride, so the smaller matrix has to be built // into a fresh buffer rather than shrunk in place. const int oldRows = m_rows; const int newRows = m_rows - 1; qreal *newData = new (nothrow) qreal[ static_cast(newRows) * newRows ]; Q_CHECK_PTR( newData ); for (int i=0; i(i) * newRows + j] = m_data[static_cast(srcI) * oldRows + srcJ]; } } delete [] m_data; delete [] m_rowPtr; m_data = newData; m_rows = newRows; m_cols = newRows; rebuildRowPtr(); qCDebug(lcMatrix) << "Matrix:deleteRowColumn() - finished, new matrix:"; } /** * @brief Fills every cell of this matrix with the given value. Complexity: O(rows()*cols()). * @param value */ void Matrix::fillMatrix(qreal value ) { for (int i=0;i< rows() ; i++) for (int j=0;j< cols(); j++) setItem(i,j, value); } /** * @brief Replaces this matrix with I - this (the identity matrix minus this matrix), in * place. Complexity: O(rows()*cols()). * @return this, now holding I - this. */ Matrix& Matrix::subtractFromI () { for (int i=0;i< rows();i++) for (int j=0;jsetItem(i,j, item(i,j)+b.item(i,j)); return *S; } /** * @brief Matrix subtraction, operator -. Subtracts b (same dimensions) from this matrix and * returns the result S. Allows S = A - B. Complexity: O(rows()*cols()). * @param b * @return Matrix S */ Matrix& Matrix::operator -(Matrix & b) { Matrix *S = new Matrix(rows(), cols() ); qCDebug(lcMatrix)<< "Matrix::operator -"; for (int i=0;i< rows();i++) for (int j=0;jsetItem(i,j, item(i,j)-b.item(i,j)); return *S; } /** * @brief Matrix multiplication, operator *. Allows P = A * B, where A is this (m x n) and * B is b (n x p); returns the m x p product P. * Complexity: O(m*n*p). * @param b * @return Matrix P */ Matrix& Matrix::operator *(Matrix & b) { qCDebug(lcMatrix)<< "Matrix::operator *"; Matrix *P = new Matrix(rows(), b.cols()); if ( cols() != b.rows() ) { qCDebug(lcMatrix)<< "Matrix::product() - ERROR! Non compatible input matrices:" " this(" << rows() << "," << cols() << ") and b(" << b.rows() << ","<< b.cols(); return *P; } for (int i=0;i< rows();i++) for (int j=0;jsetItem(i,j,0); for (int k=0;k< cols();k++) { P->setItem(i,j, P->item(i,j) + item(i,k)*b.item(k,j) ); } } return *P; } /** * @brief Multiplies (right) this m x n matrix with given n x p matrix b, replacing this * matrix's own contents with the m x p product. Allows A *= B. Complexity: O(m*n*p). * @param b */ void Matrix::operator *=(Matrix & b) { qCDebug(lcMatrix)<< "Matrix::operator *"; if ( cols() != b.rows() ) { qCDebug(lcMatrix)<< "Matrix::product() - ERROR! Non compatible input matrices:" " this(" << rows() << "," << cols() << ") and b(" << b.rows() << ","<< b.cols(); return; } Matrix *P = new Matrix(rows(), b.cols()); for (int i=0;i< rows();i++) { for (int j=0;jsetItem(i,j,0); for (int k=0;k < cols();k++) { P->setItem(i,j, P->item(i,j) + item(i,k)*b.item(k,j) ); } } } *this = *P; } /** * @brief Matrix Multiplication. Given two matrices A (mxn) and B (nxp), computes their * product and stores it into the calling matrix, which becomes an m x p matrix. * Allows P.product(A, B). * @param A * @param B * @param symmetry If true, the result is assumed symmetric (P(i,j)==P(j,i)): only the * upper triangle (i<=j) is actually computed, and each computed value is mirrored * directly into its (j,i) counterpart instead of being recomputed - roughly half the * multiply-accumulate work of the general case. Used where A and B are already known to * be symmetric (e.g. cocitationMatrix(), which multiplies a matrix by its own transpose). * Complexity: O(m*n*p), or roughly half that with symmetry=true. */ void Matrix::product(Matrix &A, Matrix & B, bool symmetry) { qCDebug(lcMatrix)<< "Matrix::product() - symmetry" << symmetry; if (A.cols() != B.rows() ) { qCDebug(lcMatrix)<< "Matrix::product() - ERROR! Non compatible input matrices:" " a(" << A.rows() << "," << A.cols() << ") and b(" << B.rows() << ","<< B.cols(); return; } Matrix *P = new Matrix(A.rows(), B.cols()); qreal prod = 0; for (int i=0;i< A.rows();i++) { for (int j=0;j j ) continue; prod = 0; for (int k=0;ksetItem(i,j, prod); if (symmetry) { P->setItem(j,i, prod ); } } } *this = *P; //this->printMatrixConsole(); } /** * @brief OBSOLETE - no caller found anywhere in the codebase. Was intended to take two * (N x N) symmetric matrices a and b and write an upper-triangular product into this matrix * (the lower triangle, i>=j, left at zero). Unlike inverseByGaussJordanElimination() (also * uncalled, but explicitly kept for cross-checking matrix inversion), there is no stated * reason to keep this one - a real candidate for removal. Complexity: O(N^3). * @param a * @param b */ Matrix& Matrix::productSym( Matrix &a, Matrix & b) { for (int i=0;i=j) continue; for (int k=0;k j ) { if (a.item(i,k)!=0 && b.item(j,k)!=0) setItem(i,j, item(i,j)+a.item(i,k)*b.item(j,k)); } else //k <= j && ik ) { if (a.item(k,i)!=0 && b.item(k,j)!=0) setItem(i,j, item(i,j)+a.item(k,i)*b.item(k,j)); } else { if (a.item(i,k)!=0 && b.item(k,j)!=0) setItem(i,j, item(i,j)+a.item(i,k)*b.item(k,j)); } } return *this; } /** * @brief Returns the n-th power of this matrix (X^n), via exponentiation by squaring * (see expBySquaring2()). Used by the walks-matrix code (XM = AM.pow(length)): entry * (i,j) of AM^n counts the number of walks of length n from vertex i to vertex j. * @param n * @param symmetry Passed straight through to expBySquaring2()/product() - see product()'s * own @param symmetry for what it does. * @return This matrix, raised to the n-th power. * Complexity: O(log(n)) matrix multiplications, each O(rows()^3) - see expBySquaring2(). */ Matrix& Matrix::pow (int n, bool symmetry) { if (rows()!= cols()) { qCDebug(lcMatrix)<< "Matrix::pow() - Error. This works only for square matrix"; return *this; } qCDebug(lcMatrix)<< "Matrix::pow() "; Matrix X, Y; //auxilliary matrices qCDebug(lcMatrix)<< "Matrix::pow() - creating X = this"; X=*this; //X = this //X.printMatrixConsole(true); qCDebug(lcMatrix)<< "Matrix::pow() - creating Y = I"; Y.identityMatrix( rows() ); // y=I //Y.printMatrixConsole(true); return expBySquaring2 (Y, X, n, symmetry); } /** * @brief Recursive algorithm implementing "Exponentiation by squaring". * Also known as Fast Modulo Multiplication, this algorithm allows * fast computation of a large power n of square matrix X * @param Y must be the Identity matrix on first call * @param X the matrix to be powered * @param n the power * @param symmetry * @return Matrix& * On first call, parameters must be: Y=I, X the orginal matrix to power and n the power. * Returns the power of matrix X to this object. * For n > 4 it is more efficient than naively multiplying the base with itself repeatedly: * O(log(n)) matrix multiplications instead of O(n), each multiplication itself O(rows()^3). */ Matrix& Matrix::expBySquaring2 (Matrix &Y, Matrix &X, int n, bool symmetry) { if (n==1) { qCDebug(lcMatrix) <<"Matrix::expBySquaring2() - n = 1. Computing PM = X*Y where " "X = " ; //X.printMatrixConsole(); //Y.printMatrixConsole(); Matrix *PM = new Matrix(rows(), cols()); PM->product(X, Y, symmetry); //PM->printMatrixConsole(); return *PM; } else if ( n%2 == 0 ) { //even qCDebug(lcMatrix)<<"Matrix::expBySquaring2() - even n =" << n << "Computing PM = X * X"; Matrix PM(rows(), cols()); PM.product(X,X,symmetry); //PM.printMatrixConsole(); return expBySquaring2 ( Y, PM, n/2 ); } else { //odd qCDebug(lcMatrix)<<"Matrix::expBySquaring2() - odd n =" << n << "First compute PM = X * Y"; Matrix PM(rows(), cols()); Matrix PM2(rows(), cols()); PM.product(X,Y,symmetry); //PM.printMatrixConsole(); qCDebug(lcMatrix)<<"Matrix::expBySquaring2() - odd n =" << n << "Now compute PM2 = X * X"; PM2.product(X,X,symmetry); //PM2.printMatrixConsole(); return expBySquaring2 ( PM, PM2, (n-1)/2 ); } } /** * @brief Calculates the matrix-by-vector product Ax of this matrix (or the left product * xA, if leftMultiply is true). Used by powerIteration()'s inner loop. * Complexity: O(rows()*cols()). * @param in input array/vector, cols() elements (rows(), if leftMultiply). * @param out output array, rows() elements (cols(), if leftMultiply). * @param leftMultiply */ void Matrix::productByVector ( qreal in[], qreal out[], const bool &leftMultiply) { int n = rows(); int m = cols(); for(int i = 0; i < n; i++) { out[i] = 0; for (int j = 0; j < m; j++) { if (leftMultiply) { // dot product of row vector b with j-th column in A out[i] += item (j, i) * in[j]; } else { // dot product of i-th row in A with the column vector b out[i] += item (i, j) * in[j]; } } } } /** * @brief Helper function, takes to vectors and returns their * Manhattan distance (also known as l1 norm, Taxicab or L1 distance) * which is the sum of the absolute differences * of their coordinates. * @param x * @param y * @return */ qreal Matrix::distanceManhattan( qreal x[], qreal y[], int n) { qreal norm = 0; for(int i = 0; i < n; i++) { norm += fabs (x[i] - y[i]); } return norm; } /** * @brief Helper function, computes the Euclideian length (also known as L2 distance) * of a vector: if x = (x1 x2 ... xn), then ||x|| = square_root(x1*x1 + x2*x2 + ... + xn*xn) * @param x * @param n * @return */ qreal Matrix::distanceEuclidean( qreal x[], int n) { qreal norm = 0; for (int i = 0; i < n; i++) { norm += x[i] * x[i]; } norm = sqrt(norm); return norm; } /** * @brief Implementation of the Power method which computes the * leading eigenvector x of this matrix, that is the eigenvector * corresponding to the largest positive eigenvalue. * In the process, it also computes min and max values. * Used by Eigenvector Centrality (EVC). * * Meaning: start from any vector x, repeatedly multiply by the matrix and rescale back to * unit length - the vector converges to the eigenvector for the matrix's largest eigenvalue * (lambda_max), which is exactly the vector eigenvector centrality reports. * * We use C arrays instead of std::vectors or anything else, * as we know from start the size (n) of vectors x and tmp * This approach is faster than using std::vector when n > 1000 * @note Uses n = rows() throughout (for tmp's size, and as both the row and column count * passed to productByVector()/distanceEuclidean()/distanceManhattan()) - correct only for * square matrices. Unlike pow(), this method does not check rows()==cols() itself; it * relies on the caller (centralityEigenvector()) only ever passing a square (adjacency) * matrix. * Complexity: O(maxIter * n^2) - each iteration is one O(n^2) productByVector() call plus * a handful of O(n) passes; iterates until the vector's Manhattan distance to its previous * value drops below eps, or maxIter is reached. * @param x * @param xsum * @param xmax * @param xmaxi * @param xmin * @param xmini * @param eps * @param maxIter * @param cancelCheck Optional callback checked once per iteration; if it returns true, the * loop stops early (x/xsum/xmax/xmin reflect the last completed iteration, not a full result). * Defaults to nullptr (never cancels), so existing callers are unaffected. * @param lambdaMax Optional out param: the largest eigenvalue itself, read off from the * pre-normalization vector length on the final iteration (norm(Ax) ~= lambda_max once x has * converged to unit length). Correctly reports exactly 0 for a nilpotent matrix (any directed * acyclic graph - no cycles at all), meaning "no convergence bound, any value works" - this is * the genuine mathematical answer, not a numerical-error placeholder (a symmetric/undirected * adjacency matrix can never be nilpotent unless it's the zero matrix, so this only ever arises * for directed graphs). Callers that only need the eigenvector (e.g. centralityEigenvector()) * can leave this nullptr; callers that need to validate a convergence bound like Katz's * alpha < 1/lambda_max need it. */ void Matrix::powerIteration ( qreal x[], qreal &xsum, qreal &xmax, int &xmaxi, qreal &xmin, int &xmini, const qreal eps, const int &maxIter, std::function cancelCheck, qreal *lambdaMax) { qCDebug(lcMatrix) << "Matrix::powerIteration() - maxIter" << maxIter <<"initial x" << x; int n = rows(); qreal norm = 0, trueNorm = 0, distance=0; qreal *tmp; tmp=new (nothrow) qreal [n]; Q_CHECK_PTR( tmp ); xsum = 0; int iter = 0; do { if (cancelCheck && cancelCheck()) { qCDebug(lcMatrix) << "Matrix::powerIteration() - canceled at iteration" << iter; break; } qCDebug(lcMatrix) << "Matrix::powerIteration() - iteration" << iter ; // calculate the matrix-by-vector product Ax and // store the result to vector tmp productByVector(x, tmp, false); qCDebug(lcMatrix) << "Matrix::powerIteration() - tmp = Ax =" << tmp; // calculate the euclidean length of the resulting vector // which will be the denominator in the vector normalization norm = trueNorm = distanceEuclidean(tmp, n); qCDebug(lcMatrix) << "Matrix::powerIteration() - norm" << norm; // norm legitimately reaches exactly zero for a nilpotent matrix (any directed graph // with no cycles at all - a DAG/tree/chain: Ax eventually collapses to the zero vector, // which is the mathematically correct outcome, not a numerical error). Substitute a // safe divisor for the normalization step below, but keep trueNorm (0 in that case) as // the value reported to lambdaMax - see this method's doc comment. if (!norm) { qCDebug(lcMatrix) << "### Matrix::powerIteration() - norm = 0 !!!"; norm = 1; } // normalize vector tmp to unit vector for next iteration xsum = 0; for(int i = 0; i < n; i++) { tmp[i] = tmp[i] / norm; } qCDebug(lcMatrix) << "Matrix::powerIteration() - tmp / norm " << tmp; // calculate the manhattan distance between the new and prev vectors distance = distanceManhattan (tmp, x, n); xmax = 0 ; xmin = RAND_MAX; for(int i = 0; i < n; i++) { x[i] = tmp[i]; xsum += x[i]; if (x[i] > xmax) { xmax = x[i] ; xmaxi = i+1; } if (x[i] < xmin) { xmin = x[i] ; xmini = i+1; } } qCDebug(lcMatrix) << "Matrix::powerIteration() - end of iteration" << iter << "\n" << "x" << x << "\n" << "distance from previous x " << distance << "sum" << xsum << "xmax" << xmax << "xmin" << xmin; iter ++; if (iter > maxIter) break; } while ( distance > eps); // trueNorm is ||Ax|| from the final iteration, with x already unit-length from the // previous one - at convergence this approximates the dominant eigenvalue (lambda_max). // Deliberately trueNorm, not norm: norm may hold the divide-by-zero-safe substitute (1) // rather than the real value on a nilpotent matrix, where the true answer is exactly 0 // (any directed acyclic graph - see the comment above). if (lambdaMax) *lambdaMax = trueNorm; delete [] tmp; } /** * @brief Returns the transpose of this matrix (T(i,j) = this(j,i)). Allows T = A.transpose(). * Complexity: O(rows()*cols()). * @return Matrix T */ Matrix& Matrix::transpose() { Matrix *T = new Matrix(cols(), rows()); //T->zeroMatrix(cols(), rows()); qCDebug(lcMatrix)<< "Matrix::transpose()"; for (int i=0;i< cols();i++) { for (int j=0;jsetItem(i,j, item(j,i)); } } return *T; } /** * @brief Returns the cocitation matrix of this matrix (C = A * A^T). Allows * T = A.cocitationMatrix(). C(i,j) counts how many nodes both i and j point to (or, read the * other way with the transpose on the other side, how many nodes point to both i and j) - * the basis of bibliometric cocitation/coupling analysis. * Complexity: O(rows()^3) - transpose() is O(N^2), but the product() call that follows * dominates at O(N^3). * @return Matrix T */ Matrix& Matrix::cocitationMatrix() { Matrix *T = new Matrix(cols(), rows()); qCDebug(lcMatrix)<< "Matrix::cocitationMatrix() this transpose"; //this->transpose().printMatrixConsole(); T->product(this->transpose(),*this, true); return *T; } /** * @brief Returns the degree matrix of this matrix: a diagonal matrix where S(i,i) is the * sum of row i (i.e. vertex i's degree, if this is an adjacency matrix). Allows * S = A.degreeMatrix(). Used by laplacianMatrix(). Complexity: O(rows()*cols()). * @return Matrix S */ Matrix& Matrix::degreeMatrix() { Matrix *S = new Matrix(rows(), cols()); qCDebug(lcMatrix)<< "Matrix::degreeMatrix()"; qreal degree=0; for (int i=0;i< rows();i++) { degree = 0; for (int j=0;jsetItem(i,i, degree); } return *S; } /** * @brief Returns the Laplacian of this matrix: an N x N matrix L = D - A, where D is this * matrix's degreeMatrix(). Allows S = A.laplacianMatrix(). Complexity: O(rows()*cols()). * @return Matrix S */ Matrix& Matrix::laplacianMatrix() { Matrix *S = new Matrix(rows(), cols()); //S->zeroMatrix(rows(), cols()); qCDebug(lcMatrix)<< "Matrix::laplacianMatrix()"; *S = (this->degreeMatrix()) - *this; return *S; } /** * @brief Inverts matrix A by Gauss-Jordan elimination with partial pivoting: starts this * matrix as the identity, then applies the same row operations to both A and this that * drive A to the identity - by the time A has become the identity, this matrix has become * A's inverse. Input: matrix A. Output: A becomes the identity matrix; this matrix becomes * A's inverse and is returned. Complexity: O(n^3). * @note Unreachable in the current codebase - createMatrixAdjacencyInverse()'s only caller * always passes "lu" (Matrix::inverse()/ludcmp(), below), never "gauss". Kept rather than * removed: matrix inversion is numerically sensitive code, and a second independent * implementation is useful for cross-checking even while unused. * @param A * @return This matrix, now holding A's inverse. */ Matrix& Matrix::inverseByGaussJordanElimination(Matrix &A){ qCDebug(lcMatrix)<< "Matrix::inverseByGaussJordanElimination()"; int n=A.cols(); qCDebug(lcMatrix)<<"Matrix::inverseByGaussJordanElimination() - build I size " << n << " This will become A^-1 in the end"; identityMatrix( n ); int l=0, m_pivotLine=0; qreal m_pivot=0, temp_pivot=0, elim_coef=0; for ( int j=0; j< n; j++) { // for n, it is the last diagonal element of A l=j+1; m_pivotLine=-1; m_pivot = A.item(j,j); qCDebug(lcMatrix) << "inverseByGaussJordanElimination() at column " << j+1 << " Initial pivot " << m_pivot ; for ( int i=l; i qFabs ( m_pivot ) ) { qCDebug(lcMatrix) << " A("<< i+1 << ","<< j+1 << ") = " << temp_pivot << " absolutely larger than current pivot "<< m_pivot << ". Marking new pivot line: " << i+1; m_pivotLine=i; m_pivot = temp_pivot ; } } if ( m_pivotLine != -1 ) { A.swapRows(m_pivotLine,j); swapRows(m_pivotLine,j); } qCDebug(lcMatrix)<<" multiplyRow() "<< j+1 << " by value " << 1/m_pivot ; for ( int k=0; k< rows(); k++) { A.setItem ( j, k, (1/m_pivot) * A.item (j, k) ); setItem ( j, k, (1/m_pivot) * item (j, k) ); qCDebug(lcMatrix)<<" A.item("<< j+1 << ","<< k+1 << ") = " << A.item(j,k); qCDebug(lcMatrix)<<" item("<< j+1 << ","<< k+1 << ") = " << item(j,k); } qCDebug(lcMatrix) << "eliminate variables FromRowsBelow()" << j+1 ; for ( int i=0; i< rows(); i++) { qCDebug(lcMatrix)<<" Eliminating item("<< i+1 << ","<< j+1 << ") = " << A.item(i,j) << " while at column j="<(1,n); qreal *vv; // vv stores the implicit scaling of each row vv=new (nothrow) qreal [n]; Q_CHECK_PTR( vv ); // QTextStream stream(stdout); // stream << "a = LU = " << a ; d=1.0; // No row interchanges yet. qCDebug(lcMatrix) << "Matrix::ludcmp() - loop over row to get scaling info" ; for (i=0;i big) big=temp; } if (big == 0) // No nonzero largest element. { qCDebug(lcMatrix) << "Matrix::ludcmp() - Singular matrix in routine ludcmp"; return false; } vv[i]=1.0/big; // Save the scaling. qCDebug(lcMatrix) << "Matrix::ludcmp() - big element in row i " << i+1 << " is "<< big << " row scaling vv[i] " << vv[i]; } qCDebug(lcMatrix) << "Matrix::ludcmp() - Start Crout's LOOP over columns"; for (j=0;j big) { // Is the figure of merit for the pivot better than the best so far? big=temp; imax=i; qCDebug(lcMatrix) << "Matrix::ludcmp() - found new largest pivot at row " << imax+1 << " big " << temp; } } qCDebug(lcMatrix) << "Matrix::ludcmp() - check for row interchange "; if (j != imax) // Do we need to interchange rows? { qCDebug(lcMatrix) << "Matrix::ludcmp() - interchanging rows " << imax+1 << " and " << j+1; for ( k=0; k=0;i--) { // Now we do the backsubstitution, equation (2.3.7). sum=b[i]; qCDebug(lcMatrix) << "Matrix::lubksb() backsubstitution: "<< "i " << i << " b[i] " << b[i] << "sum " << sum ; for ( j=i+1;j cancelCheck) { int i,j, n=a.rows(); qreal d; //qreal *col = new qreal[n]; qreal *col = new (nothrow) qreal [ n ]; Q_CHECK_PTR(col); //int indx[n]; int *indx = new (nothrow) int [ n ]; Q_CHECK_PTR(indx); qCDebug(lcMatrix) << "Matrix::inverse() - inverting matrix a - size " << n; if (n==0) { delete[] col; delete[] indx; return true; } if ( ! ludcmp(a,n,indx,d,cancelCheck) ) { // Decompose the matrix just once. qCDebug(lcMatrix) << "Matrix::inverse() - matrix a singular or canceled - RETURN"; delete[] col; delete[] indx; return false; } qCDebug(lcMatrix) << "Matrix::inverse() - find inverse by columns"; bool completed = true; for ( j=0; jrows(), this->cols()); *A = *this; int n=rows(); qreal d; // int indx[n]; int *indx = new (nothrow) int [ n ]; if (!indx) { delete A; return false; } Q_CHECK_PTR(indx); qCDebug(lcMatrix) << "Matrix::solve() - solving A x - size " << n; if (n==0) { return false; } if ( ! ludcmp(*A,n,indx,d) ) { // Decompose the matrix just once. delete A; delete[] indx; qCDebug(lcMatrix) << "Matrix::solve() - matrix a singular - RETURN"; return false ; } lubksb(*A, n, indx, b); return true; } /** * @brief Computes a dissimilarities matrix T: T(i,k) is how different variable i and * variable k are, under the chosen metric, treating either this matrix's rows, its columns, * or both (concatenated) as the "variables" being compared. Backs the Distances dialog's * Euclidean/Manhattan/Jaccard/Hamming/Chebyshev options (see graph_reports.cpp's * MATRIX_DISTANCES_* cases). * @param metric One of the METRIC_* constants declared at the top of matrix.h (Jaccard, * Hamming, Euclidean, Manhattan, or Chebyshev - simple matching and Pearson are handled by * similarityMatrix()/pearsonCorrelationCoefficients() instead, not here). * @param varLocation "Rows", "Columns", or "Both" - which axis holds the variables being compared. * @param diagonal If true, i==k / k==j comparisons are included; if false, they're skipped * (a variable is never compared against itself). * @param considerWeights Currently unused (Q_UNUSED) - accepted for interface symmetry with * similarityMatrix()/pearsonCorrelationCoefficients(), which do use it. * Complexity: O(N^2 * M), where N is the number of variables being compared and M is the * length of each variable's sample (the other axis) - a triple-nested loop, effectively * O(N^3) when varLocation is "Rows" or "Columns" (M==N there). * @return Matrix T, the dissimilarities matrix. */ Matrix& Matrix::distancesMatrix(const int &metric, const QString varLocation, const bool &diagonal, const bool &considerWeights, std::function cancelCheck) { Q_UNUSED(considerWeights); Matrix *T = new Matrix(cols(), rows()); qCDebug(lcMatrix)<< "Matrix::distancesMatrix() -" <<"metric"<< metric << "varLocation"<< varLocation << "diagonal"< mean (N,0); // holds mean values qCDebug(lcMatrix)<< "Matrix::distancesMatrix() - input matrix:"; //this->printMatrixConsole(); for (int i = 0 ; i < N ; i++ ) { if (cancelCheck && cancelCheck()) { return *T; } sum = 0 ; for (int k = i ; k < N ; k++ ) { distTemp = 0; ties = 0; max = 0; for (int j = 0 ; j < N ; j++ ) { if (!diagonal && (i==j || k==j)) { continue; } switch (metric) { case METRIC_JACCARD_INDEX: if (item(i,j) == item(k,j) && (item(i,j) != 0 && item(i,j) != RAND_MAX)) { distTemp++; } if ( ( item(i,j) != 0 && item(i,j) != RAND_MAX ) || ( item(k,j) != 0 && item(k,j) != RAND_MAX )) { ties++; } break; case METRIC_HAMMING_DISTANCE: if (item(i,j) != item(k,j) ) { distTemp++; } break; case METRIC_EUCLIDEAN_DISTANCE: if (item(i,j) == RAND_MAX || item(k,j) == RAND_MAX || distTemp == RAND_MAX) { distTemp = RAND_MAX; } else { distTemp += ( item(i,j) - item(k,j) )*( item(i,j) - item(k,j) ); //compute (x - y)^2 } break; case METRIC_MANHATTAN_DISTANCE: if (item(i,j) == RAND_MAX || item(k,j) == RAND_MAX || distTemp == RAND_MAX ) { distTemp = RAND_MAX; } else { distTemp += fabs( item(i,j) - item(k,j) ); //compute |x - y| } break; case METRIC_CHEBYSHEV_MAXIMUM: if (item(i,j) == RAND_MAX || item(k,j) == RAND_MAX || distTemp == RAND_MAX) { distTemp = RAND_MAX; max = RAND_MAX; } else { distTemp = fabs( item(i,j) - item(k,j) ); max = ( distTemp > max ) ? distTemp : max; distTemp = max; } break; default: break; } } switch (metric) { case METRIC_JACCARD_INDEX: if (ties!=0) distance = 1 - distTemp/ ( ties ) ; else distance = 1; break; case METRIC_HAMMING_DISTANCE: distance = distTemp; break; case METRIC_EUCLIDEAN_DISTANCE: distance = (distTemp == RAND_MAX) ? distTemp : sqrt(distTemp); break; case METRIC_MANHATTAN_DISTANCE: distance = distTemp ; break; case METRIC_CHEBYSHEV_MAXIMUM: distance = distTemp ; break; default: break; } T->setItem(i,k, distance); T->setItem(k,i, distance); sum += distance; } //compute mean match value mean[i] = sum / ( N ) ; } } else if (varLocation=="Columns") { N = rows() ; QList mean (N,0); // holds mean values qCDebug(lcMatrix)<< "Matrix::distancesMatrix() -" <<"input matrix"; //printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { if (cancelCheck && cancelCheck()) { return *T; } sum = 0 ; for (int k = i ; k < N ; k++ ) { distTemp = 0; ties = 0; max = 0; for (int j = 0 ; j < N ; j++ ) { if (!diagonal && (i==j || k==j)) continue; switch (metric) { case METRIC_JACCARD_INDEX: if (item(j,i) == item(j,k) && (item(j,i) != 0 && item(j,i) != RAND_MAX)) { distTemp++; } if ( ( item(j,i) != 0 && item(j,i) != RAND_MAX ) || ( item(j,k) != 0 && item(j,k) != RAND_MAX )) { ties++; } break; case METRIC_HAMMING_DISTANCE: if (item(j,i) != item(j,k) ) { distTemp++; } break; case METRIC_EUCLIDEAN_DISTANCE: if (item(j,i) == RAND_MAX || item(j,k) == RAND_MAX || distTemp == RAND_MAX) { distTemp = RAND_MAX; } else { distTemp += ( item(j,i) - item(j,k) )*( item(j,i) - item(j,k) ); //compute (x - y)^2 } break; case METRIC_MANHATTAN_DISTANCE: if (item(j,i) == RAND_MAX || item(j,k) == RAND_MAX || distTemp == RAND_MAX ) { distTemp = RAND_MAX; } else { distTemp += fabs( item(j,i) - item(j,k) ); //compute |x - y| } break; case METRIC_CHEBYSHEV_MAXIMUM: if (item(j,i) == RAND_MAX || item(j,k) == RAND_MAX || distTemp == RAND_MAX) { distTemp = RAND_MAX; max = RAND_MAX; } else { distTemp = fabs( item(j,i) - item(j,k) ); max = ( distTemp > max ) ? distTemp : max; distTemp = max; } break; default: break; } } switch (metric) { case METRIC_JACCARD_INDEX: if (ties!=0) distance = 1 - distTemp/ ( ties ) ; else distance = 1; break; case METRIC_HAMMING_DISTANCE: distance = distTemp; break; case METRIC_EUCLIDEAN_DISTANCE: distance = (distTemp == RAND_MAX) ? distTemp : sqrt(distTemp); break; case METRIC_MANHATTAN_DISTANCE: distance = distTemp ; break; case METRIC_CHEBYSHEV_MAXIMUM: distance = distTemp ; break; default: break; } // << "distance("<setItem(i,k, distance); T->setItem(k,i, distance); sum += distance; } //compute mean match value mean[i] = sum / ( N ) ; } } else if (varLocation=="Both") { Matrix CM; N = rows() ; int M = N * 2; // CM will have double rows CM.zeroMatrix(M,N); QList mean (N,0); // holds mean values //create augmented matrix (concatenated rows and columns) from input matrix for (int i = 0 ; i < N ; i++ ) { for (int j = 0 ; j < N ; j++ ) { CM.setItem(j,i, item(i,j)); CM.setItem(j + N,i, item(j,i)); } } qCDebug(lcMatrix)<< "Matrix::distancesMatrix() -" <<"input matrix"; //CM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { if (cancelCheck && cancelCheck()) { return *T; } for (int k = i ; k < N ; k++ ) { distTemp = 0; ties = 0; max = 0; for (int j = 0 ; j < M ; j++ ) { if (!diagonal) { if ( (i==j || k==j )) continue; if ( j>=N && ( (i+N)==j || (k+N)==j )) continue; } switch (metric) { case METRIC_JACCARD_INDEX: if (CM.item(j,i) == CM.item(j,k) && (CM.item(j,i) != 0 && CM.item(j,i) != RAND_MAX)) { distTemp++; } if ( ( CM.item(j,i) != 0 && CM.item(j,i) != RAND_MAX ) || ( CM.item(j,k) != 0 && CM.item(j,k) != RAND_MAX )) { ties++; } break; case METRIC_HAMMING_DISTANCE: if ( CM.item(j,i) != CM.item(j,k) ) { distTemp++; } break; case METRIC_EUCLIDEAN_DISTANCE: if ( CM.item(j,i) == RAND_MAX || CM.item(j,k) == RAND_MAX || distTemp == RAND_MAX) { distTemp = RAND_MAX; } else { distTemp += ( CM.item(j,i) - CM.item(j,k) )*( CM.item(j,i) - CM.item(j,k) ); //compute (x - y)^2 } break; case METRIC_MANHATTAN_DISTANCE: if ( CM.item(j,i) == RAND_MAX || CM.item(j,k) == RAND_MAX || distTemp == RAND_MAX ) { distTemp = RAND_MAX; } else { distTemp += fabs( CM.item(j,i) - CM.item(j,k) ); //compute |x - y| } break; case METRIC_CHEBYSHEV_MAXIMUM: if ( CM.item(j,i) == RAND_MAX || CM.item(j,k) == RAND_MAX || distTemp == RAND_MAX) { distTemp = RAND_MAX; max = RAND_MAX; } else { distTemp = fabs( CM.item(j,i) - CM.item(j,k) ); max = ( distTemp > max ) ? distTemp : max; distTemp = max; } break; default: break; } } switch (metric) { case METRIC_JACCARD_INDEX: if (ties!=0) distance = 1 - distTemp/ ( ties ) ; else distance = 1; break; case METRIC_HAMMING_DISTANCE: distance = distTemp; break; case METRIC_EUCLIDEAN_DISTANCE: distance = (distTemp == RAND_MAX) ? distTemp : sqrt(distTemp); break; case METRIC_MANHATTAN_DISTANCE: distance = distTemp ; break; case METRIC_CHEBYSHEV_MAXIMUM: distance = distTemp ; break; default: break; } // << "matchRatio("<setItem(i,k, distance); T->setItem(k,i, distance); sum += distance; } //compute mean match value mean[i] = sum / ( N ) ; } } else { } qCDebug(lcMatrix) << "Matrix::distancesMatrix() - FINISHED - Returning matrix:"; //T->printMatrixConsole(); return *T; } /** * @brief Computes a pairwise similarity matrix SCM: SCM(i,k) is how alike variable i and * variable k are, under the chosen matching measure, treating either AM's rows, its columns, * or both (concatenated) as the "variables" being compared. The mirror image of * distancesMatrix() (similarity instead of dissimilarity) - backs the Similarity dialog's * simple-matching/Jaccard/Hamming/Cosine options. * @param AM Input matrix whose rows/columns/both are being compared. * @param measure One of the METRIC_* constants (simple matching, Jaccard, Hamming, Cosine - * Pearson is handled separately by pearsonCorrelationCoefficients()). * @param varLocation "Rows", "Columns", or "Both" - which axis holds the variables being compared. * @param diagonal If true, i==k comparisons are included; if false, a variable is never * compared against itself. * @param considerWeights Whether edge weights factor into the match/mismatch decision. * @return Matrix SCM, N x N (N = number of variables being compared), with a similarity * score for every pair. * Complexity: O(N^2 * M), same shape as distancesMatrix() - see its complexity note. */ Matrix& Matrix::similarityMatrix(Matrix &AM, const int &measure, const QString varLocation, const bool &diagonal, const bool &considerWeights, std::function cancelCheck){ Q_UNUSED(considerWeights); qCDebug(lcMatrix)<< "Matrix::similarityMatrix() -" <<"measure"<< measure << "varLocation"<< varLocation; int N = 0; qreal sum = 0; qreal matchRatio = 0; qreal matches = 0; qreal ties = 0; qreal magn_i=0, magn_k=0; if (varLocation=="Rows") { N = AM.rows() ; this->zeroMatrix(N,N); QList mean (N,0); // holds mean values qCDebug(lcMatrix)<< "Matrix::similarityMatrix() -" <<"input matrix"; //AM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { if (cancelCheck && cancelCheck()) { return *this; } sum = 0 ; for (int k = i ; k < N ; k++ ) { matches = 0; ties = 0; magn_i=0; magn_k=0; for (int j = 0 ; j < N ; j++ ) { if (!diagonal && (i==j || k==j)) continue; switch (measure) { case METRIC_SIMPLE_MATCHING : if (AM.item(i,j) == AM.item(k,j) ) { matches++; } ties++; break; case METRIC_JACCARD_INDEX: if (AM.item(i,j) == AM.item(k,j) && AM.item(i,j) != 0) { matches++; } if (AM.item(i,j) != 0 || AM.item(k,j) ) { ties++; } break; case METRIC_HAMMING_DISTANCE: if (AM.item(i,j) != AM.item(k,j) ) { matches++; } break; case METRIC_COSINE_SIMILARITY: matches += AM.item(i,j) * AM.item(k,j); //compute x * y magn_i += AM.item(i,j) * AM.item(i,j); //compute |x|^2 magn_k += AM.item(k,j) * AM.item(k,j); //compute |y|^2 break; case METRIC_EUCLIDEAN_DISTANCE: matches += ( AM.item(i,j) - AM.item(k,j) )*( AM.item(i,j) - AM.item(k,j) ); //compute (x - y)^2 break; default: break; } } switch (measure) { case METRIC_SIMPLE_MATCHING : matchRatio= matches/ ( ( ties ) ) ; break; case METRIC_JACCARD_INDEX: matchRatio= matches/ ( ( ties ) ) ; break; case METRIC_HAMMING_DISTANCE: matchRatio = matches; break; case METRIC_COSINE_SIMILARITY: // sigma(i,j) = cos(theta) = x * y / |x| * |y| if ( !magn_i || ! magn_k ) { // Note that cosine similarity is undefined when // one or both vertices has degree zero. By convention, // in this case we take sigma(i,j) = 0 matchRatio = 0; } else matchRatio = matches / sqrt( magn_i * magn_k ); break; case METRIC_EUCLIDEAN_DISTANCE: matchRatio = sqrt(matches); break; default: break; } qCDebug(lcMatrix) << "matches("<zeroMatrix(N,N); QList mean (N,0); // holds mean values qCDebug(lcMatrix)<< "Matrix::similarityMatrix() -" <<"input matrix"; //AM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { if (cancelCheck && cancelCheck()) { return *this; } sum = 0 ; for (int k = i ; k < N ; k++ ) { matches = 0; ties = 0; magn_i=0; magn_k=0; for (int j = 0 ; j < N ; j++ ) { if (!diagonal && (i==j || k==j)) continue; switch (measure) { case METRIC_SIMPLE_MATCHING : if (AM.item(j,i) == AM.item(j,k) ) { matches++; } ties++; break; case METRIC_JACCARD_INDEX: if (AM.item(j,i) == AM.item(j,k) && AM.item(j,i) != 0) { matches++; } if (AM.item(j,i) != 0 || AM.item(j,k) !=0 ) { ties++; } break; case METRIC_HAMMING_DISTANCE: if (AM.item(j,i) != AM.item(j,k) ) { matches++; } break; case METRIC_COSINE_SIMILARITY: matches += AM.item(j,i) * AM.item(j,k); //compute x * y magn_i += AM.item(j,i) * AM.item(j,i); //compute |x|^2 magn_k += AM.item(j,k) * AM.item(j,k); //compute |y|^2 break; case METRIC_EUCLIDEAN_DISTANCE: matches += ( AM.item(j,i) - AM.item(j,k) )*( AM.item(j,i) - AM.item(j,k) ); //compute (x - y)^2 break; default: break; } } switch (measure) { case METRIC_SIMPLE_MATCHING : matchRatio= matches/ ( ( ties ) ) ; break; case METRIC_JACCARD_INDEX: matchRatio= matches/ ( ( ties ) ) ; break; case METRIC_HAMMING_DISTANCE: matchRatio = matches; break; case METRIC_COSINE_SIMILARITY: // sigma(i,j) = cos(theta) = x * y / |x| * |y| if ( !magn_i || ! magn_k ) { // Note that cosine similarity is undefined when // one or both vertices has degree zero. By convention, // in this case we take sigma(i,j) = 0 matchRatio = 0; } else matchRatio = matches / sqrt( magn_i * magn_k ); break; case METRIC_EUCLIDEAN_DISTANCE: matchRatio = sqrt(matches); break; default: break; } qCDebug(lcMatrix) << "matches("<zeroMatrix(N,N); CM.zeroMatrix(M,N); QList mean (N,0); // holds mean values //create augmented matrix (concatenated rows and columns) from input matrix for (int i = 0 ; i < N ; i++ ) { for (int j = 0 ; j < N ; j++ ) { CM.setItem(j,i, AM.item(i,j)); CM.setItem(j + N,i, AM.item(j,i)); } } qCDebug(lcMatrix)<< "Matrix::similarityMatrix() -" <<"input matrix"; //CM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { if (cancelCheck && cancelCheck()) { return *this; } for (int k = i ; k < N ; k++ ) { matches = 0; ties = 0; magn_i=0; magn_k=0; for (int j = 0 ; j < M ; j++ ) { if (!diagonal) { if ( (i==j || k==j )) continue; if ( j>=N && ( (i+N)==j || (k+N)==j )) continue; } switch (measure) { case METRIC_SIMPLE_MATCHING : if (CM.item(j,i) == CM.item(j,k) ) { matches++; } ties++; break; case METRIC_JACCARD_INDEX: if (CM.item(j,i) == CM.item(j,k) && CM.item(j,i) != 0) { matches++; } if (CM.item(j,i) != 0 || CM.item(j,k) !=0 ) { ties++; } break; case METRIC_HAMMING_DISTANCE: if (CM.item(j,i) != CM.item(j,k) ) { matches++; } break; case METRIC_COSINE_SIMILARITY: matches += CM.item(j,i) * CM.item(j,k); //compute x * y magn_i += CM.item(j,i) * CM.item(j,i); //compute |x|^2 magn_k += CM.item(j,k) * CM.item(j,k); //compute |y|^2 break; case METRIC_EUCLIDEAN_DISTANCE: matches += ( CM.item(j,i) - CM.item(j,k) )*( CM.item(j,i) - CM.item(j,k) ); //compute (x - y)^2 break; default: break; } } switch (measure) { case METRIC_SIMPLE_MATCHING : matchRatio= matches/ ( ( ties ) ) ; break; case METRIC_JACCARD_INDEX: matchRatio= matches/ ( ( ties ) ) ; break; case METRIC_HAMMING_DISTANCE: matchRatio = matches; break; case METRIC_COSINE_SIMILARITY: // sigma(i,j) = cos(theta) = x * y / |x| * |y| if ( !magn_i || ! magn_k ) { // Note that cosine similarity is undefined when // one or both vertices has degree zero. By convention, // in this case we take sigma(i,j) = 0 matchRatio = 0; } else matchRatio = matches / sqrt( magn_i * magn_k ); break; case METRIC_EUCLIDEAN_DISTANCE: matchRatio = sqrt(matches); break; default: break; } qCDebug(lcMatrix) << "matches("< cancelCheck){ qCDebug(lcMatrix)<< "Matrix::pearsonCorrelationCoefficients() -" << "varLocation"<< varLocation; int N = 0; qreal sumi = 0; qreal sumk = 0; qreal varianceTimesNi = 0; // = sqrDeviationsFromMean qreal varianceTimesNk = 0; // = sqrDeviationsFromMean qreal covariance = 0; qreal pcc = 0; if (varLocation=="Rows") { N = AM.rows() ; this->zeroMatrix(N,N); QList mean (N,0); // holds mean values QList sigma(N,0); qCDebug(lcMatrix)<< "Matrix::pearsonCorrelationCoefficients() -" <<"input matrix"; //AM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { if (cancelCheck && cancelCheck()) { return *this; } for (int k = i ; k < N ; k++ ) { qCDebug(lcMatrix) << "comparing rows i"<zeroMatrix(N,N); QList mean (N,0); // holds mean values QList sigma(N,0); qCDebug(lcMatrix)<< "Matrix::pearsonCorrelationCoefficients() -" <<"input matrix"; //AM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { if (cancelCheck && cancelCheck()) { return *this; } for (int k = i ; k < N ; k++ ) { qCDebug(lcMatrix) << "comparing columns i"<zeroMatrix(N,N); CM.zeroMatrix(M,N); QList mean (N,0); // holds mean values QList sigma(N,0); //create augmented matrix (concatenated rows and columns) from input matrix for (int i = 0 ; i < N ; i++ ) { for (int j = 0 ; j < N ; j++ ) { CM.setItem(j,i, AM.item(i,j)); CM.setItem(j + N,i, AM.item(j,i)); } } qCDebug(lcMatrix)<< "Matrix::pearsonCorrelationCoefficients() -" <<"input matrix"; //CM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { //a column if (cancelCheck && cancelCheck()) { return *this; } for (int k = i ; k < N ; k++ ) { // next column qCDebug(lcMatrix) << "comparing augmented columns i"<=N && ( (i+N)==j || (k+N)==j )) { qCDebug(lcMatrix) << "skipping because j>=N and i"< fabs(maxVal) ) ? fabs(minVal) : fabs(maxVal) ; os << qSetFieldWidth(0) << Qt::endl ; os << "- Values: " << ( (hasRealNumbers) ? ("real numbers (printed decimals 3)") : ("integers only" ) ) << Qt::endl; os << "- Max value: "; if ( maxVal==RAND_MAX ) os << infinity << " (=not connected nodes, in distance matrix)"; else os << maxVal; os << qSetFieldWidth(0) << Qt::endl ; os << "- Min value: "; if ( minVal==RAND_MAX ) os << infinity; else os << minVal; os << qSetFieldWidth(0) << Qt::endl << Qt::endl; os << qSetFieldWidth(7) << Qt::fixed << Qt::right << "v"<< qSetFieldWidth(3) << "" ; os << ( (hasRealNumbers) ? qSetRealNumberPrecision(3) : qSetRealNumberPrecision(0) ) ; // Note: In the case of Distance Matrix, // if there is DM(i,j)=RAND_MAX (not connected), we always use fieldWidth = 13 if ( maxAbsVal > 999) fieldWidth = 13 ; else if ( maxAbsVal > 99) fieldWidth = 10 ; else if ( maxAbsVal > 9 ) fieldWidth = 9 ; else fieldWidth = 8 ; // print first/header row for (int r = 0; r < m.cols(); ++r) { actorNumber = r+1; if ( actorNumber > 999) os << qSetFieldWidth(fieldWidth-3) ; else if ( actorNumber > 99) os << qSetFieldWidth(fieldWidth-2) ; else if ( actorNumber > 9) os << qSetFieldWidth(fieldWidth-1) ; else os << qSetFieldWidth(fieldWidth) ; os << Qt::fixed << actorNumber; } os << qSetFieldWidth(0) << Qt::endl; os << qSetFieldWidth(7)<< Qt::endl; // print rows for (int r = 0; r < m.rows(); ++r) { actorNumber = r+1; if ( actorNumber > 999) os << qSetFieldWidth(4) ; else if ( actorNumber > 99) os << qSetFieldWidth(5) ; else if ( actorNumber > 9) os << qSetFieldWidth(6) ; else os << qSetFieldWidth(7) ; os << Qt::fixed << actorNumber << qSetFieldWidth(3) <<"" ; for (int c = 0; c < m.cols(); ++c) { element = m.item(r,c) ; os << qSetFieldWidth(fieldWidth) << Qt::fixed << Qt::right; if ( element == RAND_MAX) // we print inf symbol instead of RAND_MAX (distances matrix). os << Qt::fixed << Qt::right << qSetFieldWidth(fieldWidth) << infinity ; else { if ( element > 999) os << qSetFieldWidth(fieldWidth-3) ; else if ( element > 99) os << qSetFieldWidth(fieldWidth-2) ; else if ( element > 9) os << qSetFieldWidth(fieldWidth-1) ; else os << qSetFieldWidth(fieldWidth) ; os << element; } } os << qSetFieldWidth(0) << Qt::endl; } return os; } /** * @brief Writes this matrix as an HTML table to os, one row of table cells per matrix row. * @warning Do not use on a network with disabled/deleted nodes - row/column headers are * generated from a running counter, not the real vertex number, so they go out of sync * with actual actor numbers once any vertex has been deleted. * @param os Output stream to write the HTML table to. * @param markDiag If true, diagonal cells get distinct styling. * @param plain If true, skip HTML styling/highlighting (a plain table). * @param printInfinity If true, RAND_MAX cells print as the infinity symbol (unreachable/no * edge) instead of the raw number. * @return true on success. * Complexity: O(rows()*cols()). */ bool Matrix::printHTMLTable(QTextStream& os, const bool markDiag, const bool &plain, const bool &printInfinity){ qCDebug(lcMatrix) << "Matrix::printHTMLTable()"; int elementLabel=0, rowCount = 0; qreal maxVal, minVal, element; bool hasRealNumbers=false; findMinMaxValues(minVal, maxVal, hasRealNumbers); //maxAbsVal = ( fabs(minVal) > fabs(maxVal) ) ? fabs(minVal) : fabs(maxVal) ; os << ( (hasRealNumbers) ? qSetRealNumberPrecision(3) : qSetRealNumberPrecision(0) ) ; if (plain) { os << "

";

        // print first/header row
        os << "" << qSetFieldWidth(5) << Qt::right << "A/A";
        os << Qt::fixed << qSetFieldWidth(10) << Qt::right ;
        for (int r = 0; r < cols(); ++r) {
            elementLabel = r+1;
            os << elementLabel;
        }
        os << qSetFieldWidth(0) << ""<< Qt::endl;

        for (int r = 0; r < rows(); ++r) {
            elementLabel = r+1;
            rowCount++;

            os << "" << qSetFieldWidth(5) << Qt::right;
            os << elementLabel;
            os << qSetFieldWidth(0) << "";

            for (int c = 0; c < cols(); ++c) {
                element = item(r,c) ;
                os << Qt::fixed << qSetFieldWidth(10) << Qt::right;
                if (  element == RAND_MAX)  // print inf symbol instead of RAND_MAX (distances matrix).
                    os << infinity;
                else {
                    os << element ;

                }
               // os << "";
            }

            os << qSetFieldWidth(0) << Qt::endl;
        }

        os << "
"; return true; } os << "" << "" << "" << ""; // print first/header row for (int r = 0; r < cols(); ++r) { elementLabel = r+1; os << ""; } os << "" << "" << ""; // print rows rowCount = 0; for (int r = 0; r < rows(); ++r) { elementLabel = r+1; rowCount++; os << ""; os <<""; for (int c = 0; c < cols(); ++c) { element = item(r,c) ; os << Qt::fixed << Qt::right; os <<"" : ">"); if ( ( element == RAND_MAX ) && printInfinity) { // print inf symbol instead of RAND_MAX (distances matrix). os << infinity; } else { os << element ; } os << ""; } os <<""; } os << "
" << ("Actor/Actor") << "" << elementLabel << "
" << elementLabel << "
"; os << qSetFieldWidth(0) << Qt::endl ; os << "

" << "" << ("Values: ") <<"" << ( (hasRealNumbers) ? ("real numbers (printed decimals 3)") : ("integers only" ) ) << "
" << "" << ("- Max value: ") <<"" << ( ( maxVal==RAND_MAX ) ? ( (printInfinity) ? infinity : QString::number(maxVal) ) + " (=not connected nodes, in distance matrix)" : QString::number(maxVal) ) << "
" << "" << ("- Min value: ") <<"" << ( ( minVal==RAND_MAX ) ? ( (printInfinity) ? infinity : QString::number(minVal) ) + + " (usually denotes unconnected nodes, in distance matrix)" : QString::number(minVal ) ) << "

"; return true; } /** * @brief Prints this matrix as plain text, one line per row, cells right-aligned to a fixed * width. Cells >= RAND_MAX (unreachable/no edge) print as "x" instead of the raw number. * A quick way to eyeball a matrix's contents while debugging. * Complexity: O(rows()*cols()). * @param debug If true, print to stderr; if false, print to stdout. * @return true. */ bool Matrix::printMatrixConsole(bool debug){ qCDebug(lcMatrix) << "Matrix::printMatrixConsole() - debug " << debug << "matrix rows" << rows()<< "cols"<< cols(); QTextStream out ( (debug ? stderr : stdout) ); for (int r = 0; r < rows(); ++r) { for (int c = 0; c < cols(); ++c) { if ( item(r,c) < RAND_MAX ) { out << qSetFieldWidth(12) << qSetRealNumberPrecision(3) << Qt::forcepoint << Qt::fixed << Qt::right << item(r,c); } else { out << qSetFieldWidth(12) << qSetRealNumberPrecision(3) << Qt::forcepoint << Qt::fixed << Qt::right << "x"; } // QTextStream( (debug ? stderr : stdout) ) // << ( (item(r,c) < RAND_MAX ) ? item(r,c) : INFINITY )<<' '; } out << qSetFieldWidth(0)<< Qt::endl; } return true; } /** * @brief Checks whether this matrix is "ill-defined": whether any cell holds RAND_MAX, the * sentinel value used elsewhere in the codebase for "infinite"/unreachable (e.g. a distance * matrix entry for a disconnected pair). * @return true if at least one cell is RAND_MAX or greater; false otherwise. * Complexity: O(rows()*cols()) worst case, but returns as soon as one such cell is found. */ bool Matrix::illDefined(){ qCDebug(lcMatrix) << "Matrix::illDefined() " ; for (int r = 0; r < rows(); ++r) { for (int c = 0; c < cols(); ++c) { if ( item(r,c) < RAND_MAX ) { } else { qCDebug(lcMatrix) << "Matrix::illDefined() - matrix ill-defined: TRUE" ; return true; } } } return false; } SocNetV-3.7/src/matrix.h000077500000000000000000000216731524402210300151550ustar00rootroot00000000000000/** * @file matrix.h * @brief Declares the Matrix class for handling adjacency and sociomatrix data structures in network analysis. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef MATRIX_H #define MATRIX_H #include #include //for static const QString declares below #include // std::function, for the optional cancelCheck callback #include // std::pair, std::make_pair #include using namespace std; //or else compiler groans for nothrow class QTextStream; #ifdef Q_OS_WIN32 static const QString infinity = "\u221E" ; #else static const QString infinity = QString("\xE2\x88\x9E") ; #endif static const int METRIC_NONE = -1; static const int METRIC_SIMPLE_MATCHING = 0; static const int METRIC_JACCARD_INDEX = 1; static const int METRIC_HAMMING_DISTANCE = 2; static const int METRIC_COSINE_SIMILARITY = 3; static const int METRIC_EUCLIDEAN_DISTANCE = 4; static const int METRIC_MANHATTAN_DISTANCE= 5; static const int METRIC_PEARSON_COEFFICIENT = 6; static const int METRIC_CHEBYSHEV_MAXIMUM= 7; /** * @brief General-purpose dense matrix (adjacency, distance, similarity, sociomatrix, etc.), * used throughout SocNetV wherever a network needs to be represented or analyzed as an N x M * grid of numbers. * * Storage: 2D access on a 1D array, via a precomputed row-pointer index. Every cell lives in * one single contiguous allocation (`m_data`, row-major: row 0's cells, then row 1's, and so * on) - one allocation total for the whole grid, rather than one per row. A second, much * smaller array (`m_rowPtr`) records where each row starts inside `m_data` - one pointer per * row, computed once whenever the matrix is built or resized (see rebuildRowPtr()). Reading * or writing cell (r,c) is then: look up row r's starting address in `m_rowPtr` (one array * lookup), then step c cells forward from it (one pointer offset) - no multiplication needed * at access time, no matter how large the matrix is. * * `operator[]` (the `a[i][j]` syntax) returns a raw `qreal*` into `m_data` via `m_rowPtr`, * because the LU-decomposition/inversion code (`ludcmp()`, `lubksb()`, `inverse()`) needs to * modify cells in place with compound assignment (`a[i][j] -= ...`), which `item()`/ * `setItem()` (a plain value-returning getter and a separate setter) can't express. * Everything else in the codebase reads/writes exclusively through `item()`/`setItem()`. */ class Matrix { public: /**default constructor - default rows = cols = 0 */ Matrix (int rowDim=0, int colDim=0) ; Matrix(const Matrix &b) ; /* Copy constructor allows Matrix a=b */ ~Matrix(); void clear(); void resize (const int m, const int n) ; // m_rowPtr[r] is the address of row r's first cell inside m_data (see rebuildRowPtr() // below). So m_rowPtr[r][c] is: fetch that address, then step c cells forward. qreal item( int r, int c ) { return m_rowPtr[r][c]; } void setItem(const int r, const int c, const qreal elem ) { m_rowPtr[r][c] = elem; } // Returns a raw pointer to the start of row r, so a[i][j] indexing keeps working // for ludcmp()/lubksb()/inverse() without a wrapper row type. qreal* operator [] (const int &r) { return m_rowPtr[r]; } void clearItem( int r, int c ) { m_rowPtr[r][c] = 0; } int cols() {return m_cols;} int rows() {return m_rows;} int size() { return m_rows * m_cols; } void findMinMaxValues(qreal&min, qreal&max, bool &hasRealNumbers); void NeighboursNearestFarthest(qreal&min,qreal&max, int &imin, int &jmin, int &imax, int &jmax); void deleteRowColumn(int i); /* deletes row i and column i */ void identityMatrix (int dim); void zeroMatrix (const int m, const int n); void fillMatrix (qreal value ); Matrix& subtractFromI () ; Matrix& operator =(Matrix & a); void sum(Matrix &a, Matrix &b) ; void operator +=(Matrix & b); Matrix& operator +(Matrix & b); Matrix& operator -(Matrix & b); Matrix& operator *(Matrix & b); void operator *=(Matrix & b); void product( Matrix &A, Matrix & B, bool symmetry=false) ; Matrix & productSym( Matrix &a, Matrix & b) ; void swapRows(int rowA,int rowB); void multiplyScalar(const qreal &f); void multiplyRow(int row, qreal value); void productByVector ( qreal in[], qreal out[], const bool &leftMultiply=false); Matrix & pow (int n, bool symmetry=false) ; Matrix & expBySquaring2 (Matrix &Y, Matrix &X, int n, bool symmetry=false); qreal distanceManhattan( qreal x[], qreal y[], int n); qreal distanceEuclidean( qreal x[], int n); void powerIteration ( qreal x[] , qreal &xsum, qreal &xmax, int &xmaxi, qreal &xmin, int &xmini, const qreal eps, const int &maxIter, std::function cancelCheck = nullptr, qreal *lambdaMax = nullptr); Matrix& degreeMatrix(); Matrix& laplacianMatrix(); Matrix& transpose(); Matrix& cocitationMatrix(); Matrix& inverseByGaussJordanElimination(Matrix &a); bool inverse(Matrix &a, std::function cancelCheck = nullptr); bool solve(qreal b[]); bool ludcmp (Matrix &a, const int &n, int indx[], qreal &d, std::function cancelCheck = nullptr) ; void lubksb (Matrix &a, const int &n, int indx[], qreal b[]); Matrix& distancesMatrix(const int &metric, const QString varLocation, const bool &diagonal, const bool &considerWeights, std::function cancelCheck = nullptr); Matrix& similarityMatrix(Matrix &AM, const int &measure, const QString varLocation="Rows", const bool &diagonal=false, const bool &considerWeights=true, std::function cancelCheck = nullptr); Matrix& pearsonCorrelationCoefficients(Matrix &AM, const QString &varLocation="Rows", const bool &diagonal=false, std::function cancelCheck = nullptr); friend QTextStream& operator << (QTextStream& os, Matrix& m); bool printHTMLTable(QTextStream& os, const bool markDiag=false, const bool &plain=false, const bool &printInfinity=true); bool printMatrixConsole(bool debug=true); bool illDefined(); private: // Builds m_rowPtr[]: a lookup table of row-start pointers into m_data, one entry per // row, so item()/setItem()/operator[] never have to recompute a row's address from // scratch. Concretely: m_rowPtr is an array of m_rows pointers; entry r is set to // "m_data, advanced by r whole rows" (r*m_cols cells). Looking up cell (r,c) then // becomes "read m_rowPtr[r] to get row r's address, then step c cells forward from // it" - one array read plus one cheap offset, regardless of how large the matrix is, // versus recomputing r*m_cols (a real multiplication) on every single access. // // Must be called once after every place that gives m_data a new address or a new // m_cols - the constructor, the copy constructor, resize(), identityMatrix(), // zeroMatrix(), deleteRowColumn(), and the branch of operator=() that reallocates - // because every entry in the old m_rowPtr[] would otherwise point at stale memory or // use the wrong row width. Complexity: O(m_rows) - one pointer computed per row, not // per cell, which is what keeps this cheap even for large matrices. // // This function only allocates and fills; it does not free a previous m_rowPtr array. // Callers that already have one from an earlier allocation must free it themselves // first (via clear(), or a direct delete[] - see deleteRowColumn()) before calling this. void rebuildRowPtr() { m_rowPtr = new (nothrow) qreal*[m_rows]; Q_CHECK_PTR( m_rowPtr ); for (int i=0; i(i) * m_cols; } } qreal *m_data; // the actual N*M cells, one allocation, row-major. qreal **m_rowPtr; // m_rowPtr[r] == m_data + r*m_cols, precomputed for every row. int m_rows; int m_cols; }; #endif SocNetV-3.7/src/parser.cpp000077500000000000000000000215271524402210300154760ustar00rootroot00000000000000/** * @file parser.cpp * @brief Implements the Parser class for reading and interpreting various network data formats, including adjacency matrices and sociomatrices. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "parser.h" #include #include #include #include #include //used for qDebug messages #include #include #include #include #include "graph.h" //needed for setParent using namespace std; Q_LOGGING_CATEGORY(lcParser, "socnetv.parser") Parser::Parser() { qCDebug(lcParser) << "Parser constructor, on thread:" << this->thread(); } Parser::~Parser() { qCDebug(lcParser) << "**** Parser destructor on thread:" << this->thread() << " clearing hashes... "; nodeHash.clear(); keyFor.clear(); keyName.clear(); keyType.clear(); keyDefaultValue.clear(); edgesMissingNodesHash.clear(); edgeMissingNodesList.clear(); edgeMissingNodesListData.clear(); firstModeMultiMap.clear(); secondModeMultiMap.clear(); if (xml != 0) { qCDebug(lcParser) << "**** clearing xml reader object "; xml->clear(); delete xml; xml = 0; } } void Parser::setParseSink(SocNetV::IO::IGraphParseSink *sink) { m_parseSink = sink; } void Parser::setOwnedParseSink(std::unique_ptr sink) { m_ownedParseSink = std::move(sink); m_parseSink = m_ownedParseSink.get(); } /** * @brief Loads the data of the given network file, and calls the relevant method to parse it. * * @param fileName * @param codecName * @param defNodeSize * @param defNodeColor * @param defNodeShape * @param defNodeNumberColor * @param defNodeNumberSize * @param defNodeLabelColor * @param defNodeLabelSize * @param defEdgeColor * @param width * @param height * @param format * @param sm_mode * @param delim */ void Parser::load(const QString &fileName, const QString &codecName, const int &defNodeSize, const QString &defNodeColor, const QString &defNodeShape, const QString &defNodeNumberColor, const int &defNodeNumberSize, const QString &defNodeLabelColor, const int &defNodeLabelSize, const QString &defEdgeColor, const int &canvasWidth, const int &canvasHeight, const int &format, const QString &delim, const int &sm_mode, const bool &sm_has_labels) { ParseConfig cfg{ /* fileName */ fileName, /* codecName */ codecName, /* fileFormat */ format, /* delim */ delim, /* sm_mode */ sm_mode, /* sm_has_labels */ sm_has_labels, /* initNodeSize */ defNodeSize, /* initNodeColor */ defNodeColor, /* initNodeShape */ defNodeShape, /* initNodeNumberColor */ defNodeNumberColor, /* initNodeNumberSize */ defNodeNumberSize, /* initNodeLabelColor */ defNodeLabelColor, /* initNodeLabelSize */ defNodeLabelSize, /* initEdgeColor */ defEdgeColor, /* gwWidth */ canvasWidth, /* gwHeight */ canvasHeight}; qCDebug(lcParser) << "Parser::load() current thread:" << QThread::currentThread() << " affinity thread:" << this->thread() << "loading file:" << cfg.fileName << "codecName" << cfg.codecName; initNodeSize = cfg.initNodeSize; initNodeColor = cfg.initNodeColor; initNodeShape = cfg.initNodeShape; initNodeNumberColor = cfg.initNodeNumberColor; initNodeNumberSize = cfg.initNodeNumberSize; initNodeLabelColor = cfg.initNodeLabelColor; initNodeLabelSize = cfg.initNodeLabelSize; initEdgeColor = cfg.initEdgeColor; m_textCodecName = cfg.codecName; networkName = (cfg.fileName.split("/")).last(); gwWidth = cfg.gwWidth; gwHeight = cfg.gwHeight; fileFormat = cfg.fileFormat; two_sm_mode = cfg.sm_mode; edgeDirType = EdgeType::Directed; arrows = true; bezier = false; randX = 0; randY = 0; fileLoaded = false; if (!cfg.delim.isNull() && !cfg.delim.isEmpty()) { delimiter = cfg.delim; } else { delimiter = " "; } xml = 0; qCDebug(lcParser) << "Initial networkName:" << networkName << "requested fileFormat: " << fileFormat << "delim:" << cfg.delim << "delimiter" << delimiter; errorMessage = QString(); // Start a timer. QElapsedTimer computationTimer; computationTimer.start(); // Try to open the file qCDebug(lcParser) << "Opening file..."; QFile file(cfg.fileName); if (!file.open(QIODevice::ReadOnly)) { qint64 elapsedTime = computationTimer.elapsed(); qCDebug(lcParser) << "Cannot open file" << cfg.fileName; errorMessage = tr("Cannot open file: %1").arg(cfg.fileName); if (m_parseSink) { m_parseSink->fileLoaded(FileType::UNRECOGNIZED, QString(), QString(), 0, 0, false, elapsedTime, errorMessage); } return; } // Get the canonical path of the file to load (only the path) fileDirPath = QFileInfo(cfg.fileName).canonicalPath(); // Read the file into a byte array qCDebug(lcParser) << "Reading the whole file into a byte array..."; QByteArray rawData = file.readAll(); // Close the file file.close(); switch (fileFormat) { case FileType::GRAPHML: if (parseAsGraphML(rawData)) { fileLoaded = true; } break; case FileType::PAJEK: if (parseAsPajek(rawData)) { fileLoaded = true; } break; case FileType::ADJACENCY: if (parseAsAdjacency(rawData, cfg, delimiter)) { fileLoaded = true; } break; case FileType::GRAPHVIZ: if (parseAsDot(rawData)) { fileLoaded = true; } break; case FileType::UCINET: if (parseAsDL(rawData)) { fileLoaded = true; } break; case FileType::GML: if (parseAsGML(rawData)) { fileLoaded = true; } break; case FileType::EDGELIST_WEIGHTED: if (parseAsEdgeListWeighted(rawData, delimiter)) { fileLoaded = true; } break; case FileType::EDGELIST_SIMPLE: if (parseAsEdgeListSimple(rawData, delimiter)) { fileLoaded = true; } break; case FileType::TWOMODE: if (parseAsTwoModeSociomatrix(rawData)) { fileLoaded = true; } break; default: // GraphML if (parseAsGraphML(rawData)) { fileLoaded = true; } break; } // Store computation time qint64 elapsedTime = computationTimer.elapsed(); if (fileLoaded) { qCDebug(lcParser) << "[PARSER] emit signal fileLoaded:" << "fileFormat" << fileFormat << "edgeDirType" << edgeDirType << "totalLinks(parser)" << totalLinks << "totalNodes" << totalNodes; if (m_parseSink) { m_parseSink->fileLoaded(fileFormat, cfg.fileName, networkName, totalNodes, totalLinks, edgeDirType, elapsedTime); } } else if (errorMessage != QString()) { if (m_parseSink) { m_parseSink->fileLoaded(FileType::UNRECOGNIZED, QString(), QString(), 0, 0, false, elapsedTime, errorMessage); } return; } qCDebug(lcParser) << "**** Parser finished. Emitting finished() signal. "; emit finished("Parser::load() - reach end"); } SocNetV-3.7/src/parser.h000077500000000000000000000167331524402210300151460ustar00rootroot00000000000000/** * @file parser.h * @brief Declares the Parser class for reading and interpreting various network data formats, including adjacency matrices and sociomatrices. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef PARSER_H #define PARSER_H #include #include #include #include #include #include #include #include #include #include "graph/io/graph_parse_sink.h" class QXmlStreamReader; class QXmlStreamAttributes; // WS14 L3: shared across parser.cpp and all parser_*.cpp files (they all include this header) -- // one category for the whole parsing subsystem rather than one per format, since a user debugging // a load issue wants "parser debug" on regardless of which format it turns out to be. Q_DECLARE_LOGGING_CATEGORY(lcParser) /** * @brief The Actor struct * Used while parsing edge lists */ struct Actor { QString key; int value; }; /** * @brief The CompareActors class * Implements a min-priority queue * Used while parsing weighted edge lists */ class CompareActors { public: bool operator()(Actor &t1, Actor &t2) { if (t1.value == t2.value) return t1.key > t2.key; return t1.value > t2.value; // minimum priority // Returns true if t2.value smaller than t1.value } }; /** * @brief Defines a class for network file loading and parsing * * Supports GraphML, Pajek, Adjacency, Graphviz, UCINET, EdgeLists etc * */ class Parser : public QObject { Q_OBJECT private: bool validateAndInitialize(const QByteArray &rawData, const QString &delimiter, const bool &sm_has_labels, QStringList &nodeLabels); void resetCounters(); bool doParseAdjacency(QTextStream &ts, const QString &delimiter, const QStringList &nodeLabels); void createNodeWithDefaults(int nodeIndex, const QString &label); bool createEdgesForRow(const QStringList ¤tRow, int rowIndex); bool containsReservedKeywords(const QString &str) const; /** * @brief ParseConfig boundary - the immutable config object */ struct ParseConfig { QString fileName; QString codecName; int fileFormat; QString delim; int sm_mode; bool sm_has_labels; int initNodeSize; QString initNodeColor; QString initNodeShape; QString initNodeNumberColor; int initNodeNumberSize; QString initNodeLabelColor; int initNodeLabelSize; QString initEdgeColor; int gwWidth; int gwHeight; }; SocNetV::IO::IGraphParseSink *m_parseSink = nullptr; std::unique_ptr m_ownedParseSink; QHash nodeHash; QHash keyFor, keyName, keyType, keyDefaultValue; QHash edgesMissingNodesHash; QStringList edgeMissingNodesList, edgeMissingNodesListData, relationsList; QMultiMap firstModeMultiMap, secondModeMultiMap; QXmlStreamReader *xml; QString fileDirPath; QString m_textCodecName; QString networkName; QString initNodeColor, initNodeShape, initNodeCustomIcon; QString initNodeNumberColor, initNodeLabelColor; QString initEdgeColor, initEdgeLabel, delimiter; QString errorMessage; QString nodeColor, edgeColor, edgeType, nodeShape, nodeLabel, edgeLabel; QString nodeIconPath; QString nodeNumberColor, nodeLabelColor; QHash nodeCustomAttributes, initNodeCustomAttributes; QHash edgeCustomAttributes; QString key_id, key_value, key_name, key_what, key_type; QString node_id, edge_id, edge_source, edge_target, edge_weight, edge_directed; int gwWidth, gwHeight; int totalLinks, totalNodes, fileFormat, two_sm_mode, edgeDirType; int initNodeSize, initNodeNumberSize, nodeNumberSize, initNodeLabelSize; int nodeLabelSize, source, target, nodeSize; qreal initEdgeWeight, edgeWeight, arrowSize; qreal bez_p1_x, bez_p1_y, bez_p2_x, bez_p2_y; bool fileLoaded, missingNode; bool arrows, bezier, conv_OK; bool bool_key, bool_node, bool_edge, fileContainsNodeColors; bool fileContainsNodeCoords, fileContainsLinkColors; bool fileContainsLinkLabels; double randX, randY; public: Parser(); ~Parser(); void setParseSink(SocNetV::IO::IGraphParseSink *sink); void setOwnedParseSink(std::unique_ptr sink); void load(const QString &fileName, const QString &codecName, const int &defNodeSize, const QString &defNodeColor, const QString &defNodeShape, const QString &defNodeNumberColor, const int &defNodeNumberSize, const QString &defNodeLabelColor, const int &defNodeLabelSize, const QString &defEdgeColor, const int &canvasWidth, const int &canvasHeight, const int &format, const QString &delim = QString(), const int &sm_mode = 1, const bool &sm_has_labels = false); bool parseAsPajek(const QByteArray &rawData); bool parseAsAdjacency(const QByteArray &rawData, const ParseConfig &cfg, const QString &delimiter); bool parseAsDot(const QByteArray &rawData); QString preprocessDotContent(const QString &dotContent); bool parseAsGraphML(const QByteArray &rawData); bool parseAsGML(const QByteArray &rawData); bool parseAsDL(const QByteArray &rawData); bool parseAsEdgeListSimple(const QByteArray &rawData, const QString &delimiter); bool parseAsEdgeListWeighted(const QByteArray &rawData, const QString &delimiter); bool parseAsTwoModeSociomatrix(const QByteArray &rawData); bool readDLKeywords(QStringList &strList, int &N, int &NM, int &NR, int &NC, bool &fullmatrixFormat, bool &edgelist1Format, bool &diagonalPresent); void readDotProperties(QString str, qreal &nValue, QString &label, QString &shape, QString &color, QString &fontName, QString &fontColor, QHash &customAttrs); bool readGraphML(QXmlStreamReader &); void readGraphMLElementGraph(QXmlStreamReader &); void readGraphMLElementNode(QXmlStreamReader &); void endGraphMLElementNode(QXmlStreamReader &); void readGraphMLElementEdge(QXmlStreamAttributes &); void endGraphMLElementEdge(QXmlStreamReader &); void readGraphMLElementData(QXmlStreamReader &); void readGraphMLElementUnknown(QXmlStreamReader &); void readGraphMLElementKey(QXmlStreamAttributes &); void readGraphMLElementDefaultValue(QXmlStreamReader &); void readGraphMLElementNodeGraphics(QXmlStreamReader &); void readGraphMLElementEdgeGraphics(QXmlStreamReader &); void createMissingNodeEdges(); bool isComment(QString str); void createRandomNodes(const int &fixedNum = 1, const QString &label = QString(), const int &newNodes = 1); static QString normalizeQuotedIdentifier(const QString &s); signals: void finished(QString); }; #endif SocNetV-3.7/src/parser/000077500000000000000000000000001524402210300147605ustar00rootroot00000000000000SocNetV-3.7/src/parser/parser_adjacency.cpp000066400000000000000000000570371524402210300207750ustar00rootroot00000000000000/** * @file parser_adjacency.cpp * @brief Adjacency matrix parsers for SocNetV * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "parser.h" #include "global.h" SOCNETV_USE_NAMESPACE #include #include #include /** * Main function to parse adjacency-formatted data. * * Validates the format, resets internal counters, and processes the file * to create nodes and edges from an adjacency matrix representation. * * If `cfg.sm_has_labels` is true, the first comment line is treated as * node labels. * * NOTE: Parsing is aborted if any invalid data is encountered. * * Example of a supported adjacency matrix file with node labels: * * ``` * # Alice, Bob, Charlie * 0, 1, 1 * 1, 0, 0 * 1, 0, 0 * ``` * * In this example: * - The first line is a comment containing the node labels: Alice, Bob, Charlie. * - The remaining lines form a 3x3 adjacency matrix where: * - Row 1 corresponds to Alice * - Row 2 corresponds to Bob * - Row 3 corresponds to Charlie * - A "1" indicates an edge (e.g., Alice is connected to Bob and Charlie). * - A "0" indicates no edge (e.g., Bob is not connected to Charlie). * * @param rawData Raw input data as QByteArray. * @param cfg Parser configuration (contains format flags and defaults, * including `sm_has_labels`). * @param delimiter Delimiter used to split rows and columns. * @return true if parsing succeeds, false otherwise. */ bool Parser::parseAsAdjacency(const QByteArray &rawData, const ParseConfig &cfg, const QString &delimiter) { qCDebug(lcParser) << "Parsing data as adjacency formatted... delimiter: " << delimiter; // Decode the data and prepare a QTextStream for processing. QTextCodec *codec = QTextCodec::codecForName(m_textCodecName.toLatin1()); QString decodedData = codec->toUnicode(rawData); QTextStream ts(&decodedData); QStringList nodeLabels; // Stores node labels if `cfg.sm_has_labels` is true. QString str; // Validate the input data. if (!validateAndInitialize(rawData, delimiter, cfg.sm_has_labels, nodeLabels)) { return false; } // Reset the QTextStream for processing the adjacency matrix. ts.seek(0); // Reset internal counters and data structures. resetCounters(); // Process the file to create nodes and edges. if (!doParseAdjacency(ts, delimiter, nodeLabels)) { return false; // Abort if any error occurs during node or edge creation. } // If no relations are defined, add a default unnamed relation. if (relationsList.empty()) { if (m_parseSink) { m_parseSink->addNewRelation("unnamed"); } } qCDebug(lcParser) << "Finished OK. Returning."; return true; } /** * Validates the adjacency matrix file format and, optionally, gets node labels from first line (if it is a comment line). * Checks for reserved keywords, row consistency, and appropriate delimiters in the first 11 rows. * Parsing is aborted immediately if any issue is encountered. * @param rawData Raw input data as QByteArray. * @param delimiter Delimiter used to split rows and columns. * @return true if the file format is valid, false otherwise. */ bool Parser::validateAndInitialize(const QByteArray &rawData, const QString &delimiter, const bool &sm_has_labels, QStringList &nodeLabels) { QTextCodec *codec = QTextCodec::codecForName(m_textCodecName.toLatin1()); QString decodedData = codec->toUnicode(rawData); QTextStream ts(&decodedData); QString str; int fileLine = 0, actualLineNumber = 0, lastCount = 0; while (actualLineNumber < 11 && !ts.atEnd()) { fileLine++; str = ts.readLine().simplified().trimmed(); // Check for reserved keywords to ensure the file is adjacency-formatted. if (containsReservedKeywords(str)) { errorMessage = tr("Invalid adjacency-formatted file. " "Non-comment line %1 includes reserved keywords ('%2'). Parsing aborted.") .arg(fileLine) .arg(str); return false; } if (isComment(str)) { // Get node labels from first line, if `sm_has_labels` is true. if (fileLine == 1 && sm_has_labels) { str.remove(QRegularExpression("^#\\s*")); // Removes '#' and any trailing spaces nodeLabels = str.split(delimiter); if (nodeLabels.isEmpty()) { errorMessage = tr("Invalid Adjacency-formatted file. " "Node labels line is empty or improperly formatted. Parsing aborted."); return false; } qCDebug(lcParser) << "Parsed node labels:" << nodeLabels; break; } continue; } actualLineNumber++; int colCount = str.split(delimiter).size(); // Ensure that all rows have consistent column counts. if ((colCount != lastCount && actualLineNumber > 1) || (colCount < actualLineNumber)) { errorMessage = tr("Invalid Adjacency-formatted file. " "Row %1 at line %2 has a different number of elements (%3) than expected (%4). Parsing aborted.") .arg(actualLineNumber) .arg(fileLine) .arg(colCount) .arg(lastCount); return false; } lastCount = colCount; } qCDebug(lcParser) << "Validation successful. Proceeding."; return true; } /** * Resets counters and data structures used during parsing. * Clears relations and resets node and edge counters to ensure a clean state. */ void Parser::resetCounters() { relationsList.clear(); totalNodes = 0; edgeWeight = 1.0; totalLinks = 0; edgeDirType = EdgeType::Directed; } /** * Processes the adjacency matrix file to create nodes and edges. * Reads each line of the matrix, creates nodes for the first row, and creates edges for subsequent rows. * Uses `nodeLabels` to assign labels to nodes if provided. * Parsing is aborted immediately if any issue is encountered. * @param ts QTextStream of the decoded adjacency matrix file. * @param delimiter Delimiter used to split rows and columns. * @param nodeLabels List of node labels (optional). If empty, numeric labels are used. * @return true if the nodes and edges are successfully created, false otherwise. */ bool Parser::doParseAdjacency(QTextStream &ts, const QString &delimiter, const QStringList &nodeLabels) { QString str; QStringList currentRow; int fileLine = 0, actualLineNumber = 0; while (!ts.atEnd()) { fileLine++; str = ts.readLine().simplified().trimmed(); // Skip comment lines but count them for accurate line tracking. if (isComment(str)) { qCDebug(lcParser) << tr("fileLine: %1 is a comment...").arg(fileLine); continue; } actualLineNumber++; currentRow = str.split(delimiter); if (actualLineNumber == 1) { // Initialize nodes based on the first row of the adjacency matrix. totalNodes = currentRow.size(); qCDebug(lcParser) << "Nodes to be created:" << totalNodes; // Create each node, assigning random positions and labels. for (int j = 1; j <= totalNodes; ++j) { QString label = (j <= nodeLabels.size()) ? nodeLabels[j - 1] : QString::number(j); createNodeWithDefaults(j, label); } qCDebug(lcParser) << "Finished creating nodes"; } // If more rows exist than declared nodes, create additional nodes. if (actualLineNumber > totalNodes) { createNodeWithDefaults(actualLineNumber, QString::number(actualLineNumber)); } // Abort if a row contains more columns than the expected NxN matrix format. if (currentRow.size() > totalNodes) { errorMessage = tr("Invalid Adjacency-formatted file. " "Not a NxN matrix. Row %1 declares %2 edges. Expected: %3. Parsing aborted.") .arg(actualLineNumber) .arg(currentRow.size()) .arg(totalNodes); return false; } // Create edges for the current row of the matrix. if (!createEdgesForRow(currentRow, actualLineNumber)) { return false; // Abort if edge creation fails. } } return true; } /** * Emits a signal to create a node with the specified index and label, and default node properties. * Assigns a random position for the node within the graph dimensions. * @param nodeIndex Index of the node to create. * @param label Label for the node (numerical or custom). */ void Parser::createNodeWithDefaults(int nodeIndex, const QString &label) { // Assign a random position for the node within the graph dimensions. // QPointF randomPosition(rand() % gwWidth, rand() % gwHeight); QPointF randomPosition(QRandomGenerator::global()->bounded(gwWidth), QRandomGenerator::global()->bounded(gwHeight)); // Emit a signal to create the node with the given properties. if (m_parseSink) { m_parseSink->createNode(nodeIndex, initNodeSize, initNodeColor, initNodeNumberColor, initNodeNumberSize, label, initNodeLabelColor, initNodeLabelSize, randomPosition, initNodeShape, QString()); } } /** * Iterates through a row of the adjacency matrix to create edges. * Emits a signal for each non-zero weight to create an edge between nodes. * Parsing is aborted immediately if any invalid data is encountered. * @param currentRow The adjacency matrix row being processed. * @param rowIndex The index of the row (source node for edges). * @return true if edges are successfully created, false otherwise. */ bool Parser::createEdgesForRow(const QStringList ¤tRow, int rowIndex) { int colIndex = 0; bool conversionOK = false; for (const QString &edgeStr : currentRow) { colIndex++; edgeWeight = edgeStr.toDouble(&conversionOK); // Abort if a matrix element cannot be converted to a number. if (!conversionOK) { errorMessage = tr("Error reading Adjacency-formatted file. " "Element (%1, %2) contains invalid data ('%3'). Parsing aborted.") .arg(rowIndex) .arg(colIndex) .arg(edgeStr); return false; } // If the weight is greater than 0, create a directed edge. if (edgeWeight > 0) { qCDebug(lcParser) << "Signaling to create new edge:" << rowIndex << "->" << colIndex << "weight:" << edgeWeight << "TotalLinks:" << totalLinks + 1; if (m_parseSink) { m_parseSink->createEdge(rowIndex, colIndex, edgeWeight, initEdgeColor, EdgeType::Directed, true, false); } totalLinks++; } } return true; } /** * Checks if the given string contains any reserved keywords. * Reserved keywords suggest the file is not adjacency-formatted but in another graph format. * Parsing is aborted if a reserved keyword is found. * @param str The string to check for keywords. * @return true if a reserved keyword is found, false otherwise. */ bool Parser::containsReservedKeywords(const QString &str) const { // List of keywords reserved by other file formats. static const QStringList reservedKeywords = { "*Vertices", "*Arcs", "*Edges", "*Network", "graph", "digraph", "DL n", "DL", "dl", "list", "createNode( i, initNodeSize, initNodeColor, initNodeNumberColor, initNodeNumberSize, QString("p%1").arg(i), initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), initNodeShape, QString()); } } // Create Mode-2 nodes (events / groups) const QString mode2Color = "SkyBlue"; const QString mode2Shape = "diamond"; for (int j = 1; j <= NC; ++j) { randX = rand() % gwWidth; randY = rand() % gwHeight; if (m_parseSink) { m_parseSink->createNode( NR + j, initNodeSize, mode2Color, initNodeNumberColor, initNodeNumberSize, QString("e%1").arg(j), initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), mode2Shape, QString()); } } totalNodes = NR + NC; // Create cross-edges for every non-zero cell for (int i = 0; i < NR; ++i) { for (int j = 0; j < NC; ++j) { const QString &cell = matrix[i][j].trimmed(); if (cell != "0") { bool ok = false; qreal w = cell.toDouble(&ok); edgeWeight = ok ? w : 1.0; qCDebug(lcParser) << "Bipartite edge:" << (i + 1) << "->" << (NR + j + 1) << "weight" << edgeWeight; if (m_parseSink) { m_parseSink->createEdge( i + 1, NR + j + 1, edgeWeight, initEdgeColor, EdgeType::Undirected, arrows, bezier); } totalLinks++; } } } // ------------------------------------------------------------------ // // Mode 2 β€” Person projection (B Γ— Bα΅€) // // ------------------------------------------------------------------ // } else if (two_sm_mode == 2) { // Build firstModeMultiMap: person i (1-indexed) β†’ events attended for (int i = 0; i < NR; ++i) { for (int j = 0; j < NC; ++j) { if (matrix[i][j].trimmed() != "0") firstModeMultiMap.insert(i + 1, j + 1); } } // Create person nodes for (int i = 1; i <= NR; ++i) { randX = rand() % gwWidth; randY = rand() % gwHeight; if (m_parseSink) { m_parseSink->createNode( i, initNodeSize, initNodeColor, initNodeNumberColor, initNodeNumberSize, QString::number(i), initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), initNodeShape, QString()); } } totalNodes = NR; // Connect persons sharing at least one event for (int i = 1; i <= NR; ++i) { for (int k = 1; k < i; ++k) { const QList eventsI = firstModeMultiMap.values(i); bool connected = false; for (int ev : eventsI) { if (firstModeMultiMap.contains(k, ev)) { connected = true; break; } } if (connected) { qCDebug(lcParser) << "Person projection: edge" << i << "-" << k; if (m_parseSink) { m_parseSink->createEdge( i, k, 1.0, initEdgeColor, EdgeType::Undirected, arrows, bezier); } totalLinks++; } } } // ------------------------------------------------------------------ // // Mode 3 β€” Event projection (Bα΅€ Γ— B) // // ------------------------------------------------------------------ // } else if (two_sm_mode == 3) { // Build secondModeMultiMap: event j (1-indexed) β†’ persons attending for (int i = 0; i < NR; ++i) { for (int j = 0; j < NC; ++j) { if (matrix[i][j].trimmed() != "0") secondModeMultiMap.insert(j + 1, i + 1); } } // Create event nodes for (int j = 1; j <= NC; ++j) { randX = rand() % gwWidth; randY = rand() % gwHeight; if (m_parseSink) { m_parseSink->createNode( j, initNodeSize, initNodeColor, initNodeNumberColor, initNodeNumberSize, QString::number(j), initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), initNodeShape, QString()); } } totalNodes = NC; // Connect events sharing at least one person for (int j = 1; j <= NC; ++j) { for (int l = 1; l < j; ++l) { const QList personsJ = secondModeMultiMap.values(j); bool connected = false; for (int p : personsJ) { if (secondModeMultiMap.contains(l, p)) { connected = true; break; } } if (connected) { qCDebug(lcParser) << "Event projection: edge" << j << "-" << l; if (m_parseSink) { m_parseSink->createEdge( j, l, 1.0, initEdgeColor, EdgeType::Undirected, arrows, bezier); } totalLinks++; } } } } else { qWarning() << "parseAsTwoModeSociomatrix: unknown two_sm_mode" << two_sm_mode << "- falling back to bipartite (mode 1)"; two_sm_mode = 1; return parseAsTwoModeSociomatrix(rawData); } qCDebug(lcParser) << "parseAsTwoModeSociomatrix done." << "totalNodes" << totalNodes << "totalLinks" << totalLinks; return true; }SocNetV-3.7/src/parser/parser_common.cpp000066400000000000000000000022751524402210300203360ustar00rootroot00000000000000/** * @file parser_common.cpp * @brief Common parser helpers for SocNetV * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "parser.h" #include "global.h" SOCNETV_USE_NAMESPACE /** * @brief Helper. Checks if the string parameter is a comment (starts with a known char, i.e #). * * @param str * @return bool */ bool Parser::isComment(QString str) { if (str.startsWith("#", Qt::CaseInsensitive) || str.startsWith("/*", Qt::CaseInsensitive) || str.startsWith("%", Qt::CaseInsensitive) || str.startsWith("/*", Qt::CaseInsensitive) || str.startsWith("//", Qt::CaseInsensitive) || str.isEmpty()) { qCDebug(lcParser) << "Parser::isComment() - Comment or an empty line was found. " "Skipping..."; return true; } return false; } SocNetV-3.7/src/parser/parser_dl.cpp000066400000000000000000001245301524402210300174440ustar00rootroot00000000000000/** * @file parser_dl.cpp * @brief UCINET DL parsers for SocNetV * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "parser.h" #include "global.h" SOCNETV_USE_NAMESPACE #include #include /** * @brief Parses the given raw data as DL formatted (UCINET) data. * * This function reads and interprets a DL formatted file, which is a format used by UCINET. * It processes the file line by line, extracting relevant information such as node labels, * edge weights, and network properties. The function supports both fullmatrix and edgelist1 * formats, and can handle two-mode networks. * * @param rawData The raw data to be parsed, provided as a QByteArray. * @return true if the parsing is successful, false otherwise. * * The function performs the following steps: * - Reads the raw data and decodes it using the specified text codec. * - Checks if the first non-comment line starts with "DL". * - Extracts keywords such as N, NM, NR, NC, and FORMAT from the file. * - Reads row and column labels if present. * - Creates nodes based on the labels or the declared number of nodes. * - Reads and processes the data section to create edges between nodes. * - Emits signals to create nodes and edges in the network. * - Handles errors and inconsistencies in the file format. * * @note The function emits several signals during the parsing process: * - signalAddNewRelation(const QString &relation): Emitted when a new relation is found. * - signalSetRelation(int relationIndex): Emitted to set the current relation. * - signalCreateEdge(int source, int target, double weight, const QColor &color, EdgeType type, bool arrows, bool bezier): Emitted to create a new edge. * * @warning The function assumes that the input data is correctly formatted according to the DL specification. * Any deviations or errors in the format may result in parsing failures. */ bool Parser::parseAsDL(const QByteArray &rawData) { qCDebug(lcParser) << "Parsing data as DL formatted (UCINET)..."; QTextCodec *codec = QTextCodec::codecForName(m_textCodecName.toLatin1()); QString decodedData = codec->toUnicode(rawData); QTextStream ts(&decodedData); QString str = QString(); QString relation = QString(); QString prevLineStr = QString(); QString label = QString(); QString value = QString(); QString dlFormat = QString(); QString edgeStr; unsigned long int fileLineNumber = 0; unsigned long int actualLineNumber = 0; int source = 1; int target = 1; int NM = 0; int NR = 0; int NC = 0; int nodeSum = 0; int relationCounter = 0; bool rowLabels_flag = false; bool colLabels_flag = false; bool data_flag = false; bool relation_flag = false; bool nodesCreated_flag = false; bool twoMode_flag = false; bool fullmatrixFormat = false; bool edgelist1Format = false; bool diagonalPresent = false; // Flag to handle diagonal elements (self-loops) bool intOK = false; bool conversionOK = false; QStringList lineElement; QStringList tempList; QStringList rowLabels; QStringList colLabels; QRegularExpression myRegExp; relationsList.clear(); totalLinks = 0; arrows = true; bezier = false; edgeWeight = 0; edgeDirType = EdgeType::Directed; while (!ts.atEnd()) { fileLineNumber++; str = ts.readLine(); str = str.simplified(); if (isComment(str)) continue; actualLineNumber++; qCDebug(lcParser) << "actualLineNumber " << actualLineNumber << "str.simplified: \n" << str; if (actualLineNumber == 1) { if (!str.startsWith("DL", Qt::CaseInsensitive)) { qCDebug(lcParser) << "Not a DL file. Aborting!"; errorMessage = tr("Invalid UCINET-formatted file. The file does not start with DL in first non-comment line %1").arg(fileLineNumber); return false; } } // end if actualLineNumber == 1 // // This is a DL file. // Check if the line contains DL and comma // or we are still in search for N,NM, and FORMAT keywords // if (str.startsWith("DL", Qt::CaseInsensitive)) { if (str.contains(",")) { qCDebug(lcParser) << "DL starting line contains a comma"; // If it is a DL file and contains a comma in the first line, // then the line might declare some keywords (N, NM, FORMAT) // this happens in R's sna output files lineElement = str.split(",", Qt::SkipEmptyParts); readDLKeywords(lineElement, totalNodes, NM, NR, NC, fullmatrixFormat, edgelist1Format, diagonalPresent); } // end if str.contains(",") // if the line contains DL, does not contain any comma // but contains at least one "=" then we have keywords space separated. else if (str.contains("=")) { qCDebug(lcParser) << "DL starting line contains a = but not a comma"; // this is space separated lineElement = str.split(" ", Qt::SkipEmptyParts); readDLKeywords(lineElement, totalNodes, NM, NR, NC, fullmatrixFormat, edgelist1Format, diagonalPresent); } // end else if contains = } // end if startsWith("DL") // // Check if keywords are given in other lines, which do not start with DL // if (!str.contains("DL", Qt::CaseInsensitive) && (str.contains("n =", Qt::CaseInsensitive) || str.contains("n=", Qt::CaseInsensitive) || str.contains("nm=", Qt::CaseInsensitive) || str.contains("nm =", Qt::CaseInsensitive) || str.contains("nr=", Qt::CaseInsensitive) || str.contains("nr =", Qt::CaseInsensitive) || str.contains("nc=", Qt::CaseInsensitive) || str.contains("nc =", Qt::CaseInsensitive) || str.contains("format =", Qt::CaseInsensitive) || str.contains("format=", Qt::CaseInsensitive))) { // check if this line contains precisely one "=" if (str.count("=", Qt::CaseInsensitive) == 1) { qCDebug(lcParser) << "Line contains just one = "; // then one of the above keywords is declared here tempList = str.split("=", Qt::SkipEmptyParts); label = tempList[0].simplified(); value = tempList[1].simplified(); if (label == "n" || label == "N") { qCDebug(lcParser) << "N is declared to be : " << value; totalNodes = value.toInt(&intOK, 10); if (!intOK) { qCDebug(lcParser) << "N conversion error..."; // emit something here... errorMessage = tr("Problem interpreting UCINET-formatted file. Cannot convert N value to integer at line %1.").arg(fileLineNumber); return false; } } else if (label == "nm" || label == "NM") { qCDebug(lcParser) << "NM is declared to be : " << value; NM = value.toInt(&intOK, 10); if (!intOK) { qCDebug(lcParser) << "NM conversion error..."; // emit something here... errorMessage = tr("Problem interpreting UCINET-formatted file. Cannot convert NM value to integer at line %1").arg(fileLineNumber); return false; } } else if (label == "nr" || label == "NR") { qCDebug(lcParser) << "NR is declared to be : " << value; NR = value.toInt(&intOK, 10); if (!intOK) { qCDebug(lcParser) << "NR conversion error..."; // emit something here... errorMessage = tr("Problem interpreting UCINET-formatted file. Cannot convert NR value to integer at line %1").arg(fileLineNumber); return false; } } else if (label == "nc" || label == "NC") { qCDebug(lcParser) << "NC is declared to be : " << value; NC = value.toInt(&intOK, 10); if (!intOK) { qCDebug(lcParser) << "NC conversion error..."; // emit something here... errorMessage = tr("Problem interpreting UCINET-formatted file. Cannot convert NC value to integer at line %1").arg(fileLineNumber); return false; } } else if (label == "format" || label == "FORMAT") { qCDebug(lcParser) << "FORMAT is declared to be : " << value; if (value.contains("FULLMATRIX", Qt::CaseInsensitive)) { fullmatrixFormat = true; edgelist1Format = false; qCDebug(lcParser) << "βœ… FORMAT: FullMatrix detected"; } else if (value.contains("edgelist", Qt::CaseInsensitive)) { edgelist1Format = true; fullmatrixFormat = false; qCDebug(lcParser) << "βœ… FORMAT: EdgeList detected"; } else { qCDebug(lcParser) << "❌ ERROR: Unknown DL format. Expected 'FULLMATRIX' or 'edgelist'."; errorMessage = tr("Invalid UCINET format declaration. Expected 'FULLMATRIX' or 'edgelist' but found: %1").arg(value); return false; } // Check if DIAGONAL PRESENT is specified if (value.contains("DIAGONAL", Qt::CaseInsensitive)) { diagonalPresent = true; qCDebug(lcParser) << "βœ… FORMAT: Found standalone DIAGONAL token"; } } } // end if count 1 "=" in line (network properties) // check if this line contains more than one "=" else if (str.count("=", Qt::CaseInsensitive) > 1) { qCDebug(lcParser) << "Line contains multiple = "; if (str.contains(",")) { // this is comma separated lineElement = str.split(",", Qt::SkipEmptyParts); readDLKeywords(lineElement, totalNodes, NM, NR, NC, fullmatrixFormat, edgelist1Format, diagonalPresent); } // end else if contains comma // check if line contains space i.e. "NR=18 NC=14" else if (str.contains(" ")) { // this is space separated lineElement = str.split(" ", Qt::SkipEmptyParts); readDLKeywords(lineElement, totalNodes, NM, NR, NC, fullmatrixFormat, edgelist1Format, diagonalPresent); } // end else if contains space } // end if str.count("=") > 1 in line (network properties) } // end if str contains keywords // Check for standalone DIAGONAL line else if (str.compare("DIAGONAL", Qt::CaseInsensitive) == 0) { qCDebug(lcParser) << "βœ… Found standalone DIAGONAL line in main parser loop"; diagonalPresent = true; continue; } else if (str.startsWith("labels", Qt::CaseInsensitive) || str.startsWith("row labels", Qt::CaseInsensitive)) { rowLabels_flag = true; colLabels_flag = false; data_flag = false; relation_flag = false; qCDebug(lcParser) << "START LABELS RECOGNITION " "AND NODE CREATION"; continue; } else if (str.startsWith("COLUMN LABELS", Qt::CaseInsensitive) || str.startsWith("COL LABELS", Qt::CaseInsensitive)) { colLabels_flag = true; rowLabels_flag = false; data_flag = false; relation_flag = false; qCDebug(lcParser) << "START COLUMN LABELS RECOGNITION " "AND NODE CREATION"; continue; } else if (str.startsWith("data:", Qt::CaseInsensitive) || str.startsWith("data :", Qt::CaseInsensitive)) { data_flag = true; rowLabels_flag = false; colLabels_flag = false; relation_flag = false; qCDebug(lcParser) << "START DATA RECOGNITION " "AND EDGE CREATION"; continue; } else if (str.startsWith("LEVEL LABELS", Qt::CaseInsensitive)) { relation_flag = true; data_flag = false; rowLabels_flag = false; colLabels_flag = false; qCDebug(lcParser) << "START RELATIONS RECOGNITION"; continue; } else if (str.startsWith("matrix labels:", Qt::CaseInsensitive) || str.startsWith("matrix labels :", Qt::CaseInsensitive)) { data_flag = false; rowLabels_flag = false; colLabels_flag = false; relation_flag = false; qCDebug(lcParser) << "matrix labels not supported"; continue; } else if (str.isEmpty()) { qCDebug(lcParser) << "EMPTY STRING - CONTINUE"; continue; } if (rowLabels_flag) { // try to read row labels label = str.trimmed().simplified(); if (rowLabels.contains(label)) { qCDebug(lcParser) << "⚠ Warning: Duplicate row label '" << label << "' found. Ignoring."; continue; } else { qCDebug(lcParser) << "Adding label " << label << " to rowLabels, list size: " << rowLabels.size(); rowLabels << label; } } else if (colLabels_flag) { // try to read col labels label = str.trimmed().simplified(); if (colLabels.contains(label)) { qCDebug(lcParser) << "col label exists. CONTINUE"; continue; } else { qCDebug(lcParser) << "Adding col label " << label << " to colLabels"; colLabels << label; } } else if (relation_flag) { relation = str; if (relationsList.contains(relation)) { qCDebug(lcParser) << "relation exists. CONTINUE"; continue; } else { qCDebug(lcParser) << "adding new relation" << relation << "to relationsList and signaling to create new relation"; relationsList << relation; if (m_parseSink) { m_parseSink->addNewRelation(relation); } } } else if (data_flag) { // check if we haven't created any nodes... if (!nodesCreated_flag) { // Fix #63 β€” detect two-mode (NR Γ— NC affiliation matrix). // UCINET two-mode files always load as a bipartite directed // graph in SocNetV: directed edges run from each mode-1 row // node to each mode-2 column node it is affiliated with. // Projection (person-network / event-network) is not supported // for the DL format and must be done as a post-load step. qCDebug(lcParser) << "check if NR != 0 (two mode net)."; if (NR != 0 && NC != 0) { twoMode_flag = true; totalNodes = NR + NC; // Fix #63-1: set correct total qCDebug(lcParser) << "Two-mode net: NR=" << NR << "NC=" << NC << "totalNodes=" << totalNodes; } // Create row (mode-1) nodes. if (rowLabels.size() == 0) { if (!twoMode_flag) { // One-mode: create N nodes with auto-numbering. qCDebug(lcParser) << "No row labels. Creating" << totalNodes << "nodes."; createRandomNodes(1, QString(), totalNodes); nodeSum = totalNodes; } else { // Fix #63-2: two-mode, no labels β€” create NR row nodes // with explicit numbers 1..NR so col nodes can follow // at NR+1..NR+NC without overlap. qCDebug(lcParser) << "Two-mode, no row labels. Creating" << NR << "row nodes."; for (int i = 1; i <= NR; ++i) { nodeSum++; createRandomNodes(nodeSum, QString(), 1); } } } else if (rowLabels.size() == 1) { // only one label line was found // probably contains a comma to separate labels // split it qCDebug(lcParser) << "Nodes have not been created yet." << "One row for labels found." << "Splitting at a comma and calling createRandomNodes(1) for each label"; tempList = rowLabels[0].split(",", Qt::SkipEmptyParts); for (QStringList::Iterator it1 = tempList.begin(); it1 != tempList.end(); ++it1) { label = (*it1); nodeSum++; createRandomNodes(nodeSum, label, 1); } } else { // multiple label lines were found qCDebug(lcParser) << "Nodes have not been created yet." << "Multiple label lines were found: " << rowLabels.size() << "Calling createRandomNodes() for each label"; for (QStringList::Iterator it1 = rowLabels.begin(); it1 != rowLabels.end(); ++it1) { label = (*it1); nodeSum++; createRandomNodes(nodeSum, label, 1); } } if (twoMode_flag) { // Create col (mode-2) nodes numbered NR+1..NR+NC. if (colLabels.size() == 0) { // Fix #63-2: no col labels β€” loop with explicit numbers // so nodes land at NR+1..NR+NC (nodeSum is NR here). qCDebug(lcParser) << "Two-mode, no col labels. Creating" << NC << "col nodes starting at" << (nodeSum + 1); for (int j = 0; j < NC; ++j) { nodeSum++; createRandomNodes(nodeSum, QString(), 1); } } else if (colLabels.size() == 1) { // only one col label line was found // probably contains a comma to separate labels // split it qCDebug(lcParser) << "Nodes have not been created yet." << "One line for col label found." << "Splitting at a comma and calling createRandomNodes(1) for each label"; tempList = colLabels[0].split(",", Qt::SkipEmptyParts); for (QStringList::Iterator it1 = tempList.begin(); it1 != tempList.end(); ++it1) { label = (*it1); nodeSum++; createRandomNodes(nodeSum, label, 1); } } else { // multiple col label lines were found qCDebug(lcParser) << "Nodes have not been created yet." << "Multiple col label lines were found." << "Calling createRandomNodes(1) for each label"; for (QStringList::Iterator it1 = colLabels.begin(); it1 != colLabels.end(); ++it1) { label = (*it1); nodeSum++; createRandomNodes(nodeSum, label, 1); } } } // sanity check if (!twoMode_flag && nodeSum != totalNodes) { qCDebug(lcParser) << "❌ ERROR: Number of nodes processed (" << nodeSum << ") does not match declared N=" << totalNodes; errorMessage = tr("Error reading UCINET-formatted file: Number of nodes found (%1) does not match declared N=%2") .arg(nodeSum) .arg(totalNodes); return false; } nodesCreated_flag = true; } // endif nodesCreated if (fullmatrixFormat) { if (!twoMode_flag) { qCDebug(lcParser) << "reading edges in fullmatrix format"; // FIX FOR ISSUE #174: Handle wrapped matrix rows // Accumulate wrapped lines until we get totalNodes values QString accumulatedLine = str; myRegExp.setPattern("\\s+"); lineElement = accumulatedLine.split(myRegExp, Qt::SkipEmptyParts); qCDebug(lcParser) << "line elements " << lineElement.size(); while (lineElement.size() < totalNodes && !ts.atEnd()) { QString nextLine = ts.readLine().simplified(); if (!nextLine.isEmpty() && !isComment(nextLine)) { accumulatedLine += " " + nextLine; lineElement = accumulatedLine.split(myRegExp, Qt::SkipEmptyParts); fileLineNumber++; // Advance line count to keep error reporting consistent } } if (lineElement.size() != totalNodes) { qCDebug(lcParser) << "❌ ERROR: Mismatch in matrix row size at line" << fileLineNumber << ". Expected" << totalNodes << "columns but found" << lineElement.size(); qCDebug(lcParser) << "πŸ” Full row content: " << str; errorMessage = tr("Matrix row size mismatch. Expected %1 but got %2 at line %3.") .arg(totalNodes) .arg(lineElement.size()) .arg(fileLineNumber); return false; } prevLineStr.clear(); target = 1; if (source == 1 && relationCounter > 0) { qCDebug(lcParser) << "we are at source 1. " "Checking relationList"; relation = relationsList[relationCounter]; qCDebug(lcParser) << "WE ARE THE FIRST DATASET/MATRIX" << "source node counter is" << source << "and relation to:" << relation << "index:" << relationCounter << "signaling to change to that relation..."; if (m_parseSink) { m_parseSink->setRelation(relationCounter); } } else if (source > totalNodes) { source = 1; relationCounter++; relation = relationsList[relationCounter]; qCDebug(lcParser) << "LOOKS LIKE WE ENTERED A NEW DATASET/MATRIX " << " init source node counter to" << source << " and relation to" << relation << ": " << relationCounter << "signaling to change to that relation..."; if (m_parseSink) { m_parseSink->setRelation(relationCounter); } } else { qCDebug(lcParser) << "source node counter is " << source; } for (QStringList::Iterator it1 = lineElement.begin(); it1 != lineElement.end(); ++it1) { edgeStr = (*it1); edgeWeight = (*it1).toDouble(&conversionOK); if (!conversionOK) { errorMessage = tr("Problem interpreting UCINET fullmatrix-formatted file. " "In edge (%1->%2), the weight (%3) could not be converted to number, at line %4.") .arg(source) .arg(target) .arg(edgeWeight) .arg(fileLineNumber); return false; } // FIX FOR ISSUE #173: Properly handle diagonal elements based on diagonalPresent flag if (source == target) { // This is a diagonal element (self-loop) qCDebug(lcParser) << "Diagonal element at (" << source << "," << target << ") with value " << edgeWeight; if (diagonalPresent && edgeWeight > 0) { // Create self-loop only if DIAGONAL PRESENT and value is non-zero qCDebug(lcParser) << "Creating self-loop for node " << source; if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, initEdgeColor, EdgeType::Directed, arrows, bezier); } totalLinks++; } } else { // Non-diagonal element - normal edge if (edgeWeight > 0) { qCDebug(lcParser) << "relation" << relationCounter << "Adding edge from " << source << " to " << target << " with weight " << edgeWeight; if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, initEdgeColor, EdgeType::Directed, arrows, bezier); } totalLinks++; qCDebug(lcParser) << "TotalLinks= " << totalLinks; } } target++; } // end for source++; } else { // two-mode target = NR + 1; qCDebug(lcParser) << "this is a two-mode fullmatrix file. " "Splitting str to elements:"; myRegExp.setPattern("\\s+"); lineElement = str.split(myRegExp, Qt::SkipEmptyParts); qCDebug(lcParser) << "lineElement:" << lineElement; if (lineElement.size() != NC) { qCDebug(lcParser) << "Not a two-mode fullmatrix UCINET " "formatted file. Aborting!!"; // emit something... errorMessage = tr("Problem interpreting UCINET two-mode fullmatrix-formatted file. The file declared %1 columns initially, " "but I found a different number %2 of matrix columns, at line %3.") .arg(QString::number(NC)) .arg(QString::number(lineElement.size())) .arg(fileLineNumber); return false; } for (QStringList::Iterator it1 = lineElement.begin(); it1 != lineElement.end(); ++it1) { edgeStr = (*it1); edgeWeight = (*it1).toDouble(&conversionOK); if (!conversionOK) { errorMessage = tr("Problem interpreting UCINET two-mode file. " "In edge (%1->%2), the weight (%3) cannot be converted to number, at line %4.") .arg(source) .arg(target) .arg(edgeWeight) .arg(fileLineNumber); return false; } if (edgeWeight) { qCDebug(lcParser) << "relation " << relationCounter << "found edge from " << source << " to " << target << "weight " << edgeWeight << "signaling to create new edge"; if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, initEdgeColor, EdgeType::Directed, arrows, bezier); } totalLinks++; qCDebug(lcParser) << "TotalLinks= " << totalLinks; } target++; } // end for source++; } } // END FULLMATRIX FORMAT READING if (edgelist1Format) { // read edges in edgelist1 format myRegExp.setPattern("\\s+"); lineElement = str.split(myRegExp, Qt::SkipEmptyParts); qCDebug(lcParser) << "edgelist str line:" << str; qCDebug(lcParser) << "edgelist data element:" << lineElement; if (lineElement.size() != 3) { qCDebug(lcParser) << "Not an edgelist1 UCINET " "formatted file. Aborting!!"; // emit something... errorMessage = tr("Problem interpreting UCINET-formatted file. " "The file was declared as edgelist but I found " "a line which did not have 3 elements (source, target, weight), at line %1") .arg(fileLineNumber); return false; } source = (lineElement[0]).toInt(&intOK); target = (lineElement[1]).toInt(&intOK); qCDebug(lcParser) << "source node " << source << " target node " << target; edgeWeight = (lineElement[2]).toDouble(&conversionOK); if (conversionOK) { qCDebug(lcParser) << "list file declares edge weight: " << edgeWeight; } else { edgeWeight = 1.0; qCDebug(lcParser) << " list file NOT declaring edge weight. Setting default: " << edgeWeight; } qCDebug(lcParser) << "Signaling to create new edge" << source << "->" << target << " weight= " << edgeWeight << " TotalLinks= " << totalLinks + 1; if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, initEdgeColor, EdgeType::Directed, arrows, bezier); } totalLinks++; } // END edgelist1 format reading. } // end if data_flag } // end while there are more lines if (relationsList.isEmpty()) { if (m_parseSink) { m_parseSink->addNewRelation("unnamed"); } } // The network has been loaded. Change to the first relation if (m_parseSink) { m_parseSink->setRelation(0); } // Clear temp arrays lineElement.clear(); tempList.clear(); rowLabels.clear(); colLabels.clear(); relationsList.clear(); qCDebug(lcParser) << "Finished OK. Returning."; return true; } /** * @brief Reads and parses DL keywords from a given QStringList. * * This function processes a list of strings to extract and interpret DL keywords. * It updates the provided references with the parsed values. * * @param strList A reference to a QStringList containing the DL keywords. * @param N A reference to an integer to store the parsed value of 'N'. * @param NM A reference to an integer to store the parsed value of 'NM'. * @param NR A reference to an integer to store the parsed value of 'NR'. * @param NC A reference to an integer to store the parsed value of 'NC'. * @param fullmatrixFormat A reference to a boolean to indicate if the format is 'FULLMATRIX'. * @param edgelist1Format A reference to a boolean to indicate if the format is 'edgelist'. * @return true if all keywords are successfully parsed and valid, false otherwise. */ bool Parser::readDLKeywords(QStringList &strList, int &N, int &NM, int &NR, int &NC, bool &fullmatrixFormat, bool &edgelist1Format, bool &diagonalPresent) { QStringList tempList; QString tempStr = QString(); QString label = QString(); QString value = QString(); bool intOK = false; for (QStringList::Iterator it1 = strList.begin(); it1 != strList.end(); ++it1) { tempStr = (*it1); qCDebug(lcParser) << "element:" << tempStr.toLatin1(); // Check for standalone DIAGONAL token if (tempStr.compare("DIAGONAL", Qt::CaseInsensitive) == 0) { qCDebug(lcParser) << "βœ… Found standalone DIAGONAL token"; diagonalPresent = true; continue; } if (tempStr.startsWith("DL", Qt::CaseInsensitive)) { // remove DL tempStr.remove("DL", Qt::CaseInsensitive); tempStr = tempStr.simplified(); qCDebug(lcParser) << "element contained DL. Removed it:" << tempStr; } // check if this element contains a "=" if (tempStr.size() > 0) { if (tempStr.contains("=", Qt::CaseInsensitive)) { qCDebug(lcParser) << "splitting element at = sign"; tempList = tempStr.split("=", Qt::SkipEmptyParts); label = tempList[0].simplified(); value = tempList[1].simplified(); if (label == "n" || label == "N") { qCDebug(lcParser) << "N is declared to be : " << value; N = value.toInt(&intOK, 10); if (!intOK) { qCDebug(lcParser) << "N conversion error..."; // emit something here... errorMessage = tr("Error while reading UCINET-formatted file. Cannot convert N value to integer. "); return false; } } else if (label == "nm" || label == "NM") { qCDebug(lcParser) << "NM is declared to be : " << value; NM = value.toInt(&intOK, 10); if (!intOK) { qCDebug(lcParser) << "NM conversion error..."; // emit something here... errorMessage = tr("Problem interpreting UCINET file. Cannot convert NM value to integer. "); return false; } } else if (label == "nr" || label == "NR") { qCDebug(lcParser) << "NR is declared to be : " << value; NR = value.toInt(&intOK, 10); if (!intOK) { qCDebug(lcParser) << "NR conversion error..."; // emit something here... errorMessage = tr("Error while reading UCINET-formatted file. Cannot convert NR value to integer."); return false; } } else if (label == "nc" || label == "NC") { qCDebug(lcParser) << "NC is declared to be : " << value; NC = value.toInt(&intOK, 10); if (!intOK) { qCDebug(lcParser) << "NC conversion error..."; // emit something here... errorMessage = tr("Error while reading UCINET-formatted file. Cannot convert NC value to integer. "); return false; } } else if (label == "format" || label == "FORMAT") { qCDebug(lcParser) << "FORMAT is declared to be : " << value; // Check if DIAGONAL PRESENT is specified in the format if (value.contains("DIAGONAL", Qt::CaseInsensitive)) { diagonalPresent = true; qCDebug(lcParser) << "βœ… FORMAT: DIAGONAL token found in format value"; } if (value.contains("FULLMATRIX", Qt::CaseInsensitive)) { fullmatrixFormat = true; edgelist1Format = false; qCDebug(lcParser) << "βœ… FORMAT: FullMatrix detected"; } else if (value.contains("edgelist", Qt::CaseInsensitive)) { edgelist1Format = true; fullmatrixFormat = false; qCDebug(lcParser) << "βœ… FORMAT: EdgeList detected"; } else { qCDebug(lcParser) << "❌ ERROR: Unknown DL format. Expected 'FULLMATRIX' or 'edgelist'."; errorMessage = tr("Invalid UCINET format declaration. Expected 'FULLMATRIX' or 'edgelist' but found: %1").arg(value); return false; } } // end format } // end if contains = else { // We'll be more lenient here - if we encounter unknown tokens without = // we'll just ignore them rather than returning false qCDebug(lcParser) << "Ignoring unknown token without = sign:" << tempStr; } } // end if > 0 } // end for lineElement return true; } /** * @brief Signals to create either a single new node (numbered fixedNum) or multiple new nodes (numbered from 1 to to newNodes) * @param fixedNum * @param label * @param newNodes */ void Parser::createRandomNodes(const int &fixedNum, const QString &label, const int &newNodes) { if (newNodes != 1) { for (int i = 0; i < newNodes; i++) { qCDebug(lcParser) << "Signaling to create multiple nodes. Now signaling for node:" << i + 1; if (m_parseSink) { m_parseSink->createNodeAtPosRandom(false); } } } else { qCDebug(lcParser) << "Signaling to create a single node:" << fixedNum << "with label:" << label; if (m_parseSink) { m_parseSink->createNodeAtPosRandomWithLabel(fixedNum, label, false); } } }SocNetV-3.7/src/parser/parser_dot.cpp000066400000000000000000000712521524402210300176350ustar00rootroot00000000000000/** * @file parser_dot.cpp * @brief GraphViz DOT parsers for SocNetV * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "parser.h" #include "global.h" SOCNETV_USE_NAMESPACE #include #include /** * @brief Parses the data as GraphViz (DOT) formatted network. * * IMPORTANT CONTRACTS / INVARIANTS * - DOT has two graph β€œkinds”: * 1) digraph => default directed, edges use "->" * 2) graph => default undirected, edges use "--" * * - SocNetV must report the *graph* directedness reliably via Parser::signalFileLoaded * (the Graph::graphFileLoaded slot uses this to set directedness before calling * edgesEnabled()). * * - Therefore we must NOT use a single variable for two meanings: * (a) the overall graph type (graph/digraph), AND * (b) the last edge’s operator ("--"/"->"). * * Previous bug: * - The parser used edgeDirType as both (a) and (b) and also treated any '-' as * an edge indicator. In some cases, this left edgeDirType as Directed even for * undirected files, so Graph::graphFileLoaded computed ties while the graph was * still considered directed, doubling counts in the CLI (issue #187). * * Fix: * - Track graph-level directedness separately (graphDirType) and never overwrite it. * - Parse edges only when the line contains "--" or "->" (not merely '-'). * * @param rawData Raw file bytes. * @return true on successful parse; false on format errors. */ bool Parser::parseAsDot(const QByteArray &rawData) { qCDebug(lcParser) << "Parsing data as dot (Graphviz) formatted..."; int fileLine = 0; int actualLineNumber = 0; int aNum = -1; int start = 0; int end = 0; int next = 0; QString label, node, nodeLabel, fontName, fontColor; QString edgeShape, edgeColor, edgeLabel, networkLabel; QString str, temp, prop, value; QStringList lineElement; nodeColor = "red"; edgeColor = "black"; nodeShape = ""; edgeWeight = 1.0; initEdgeWeight = 1.0; qreal nodeValue = 1.0; bool netProperties = false; QHash edgeCustomAttrs; QList nodeSequence; // holds nodes in an edge chain: a -- b -- c ... QList nodesDiscovered; // holds *raw* node identifiers encountered relationsList.clear(); // Graph-level directedness. Set ONCE from header (graph/digraph) and never overwritten. int graphDirType = EdgeType::Directed; // Per-edge directedness (derived from operator in each edge statement). int edgeTypeThisLine = EdgeType::Directed; arrows = true; bezier = false; source = 0; target = 0; QTextCodec *codec = QTextCodec::codecForName(m_textCodecName.toLatin1()); QString decodedData = codec->toUnicode(rawData).trimmed(); // Abort early if file does not even contain dot keywords. if (!decodedData.contains("digraph", Qt::CaseInsensitive) && !decodedData.contains("graph", Qt::CaseInsensitive)) { qCDebug(lcParser) << "Not a valid GraphViz (dot) file. Aborting!"; errorMessage = tr("Invalid GraphViz (dot) file. The file does not contain 'digraph' or 'graph'."); return false; } // Normalize to line-based format (single-line DOT etc). decodedData = preprocessDotContent(decodedData); QTextStream ts(&decodedData); totalNodes = 0; totalLinks = 0; while (!ts.atEnd()) { fileLine++; qCDebug(lcParser) << "πŸ”Ž Reading fileLine " << fileLine; str = ts.readLine().simplified().trimmed(); qCDebug(lcParser) << str; if (isComment(str)) continue; actualLineNumber++; // Header: determine graph kind and optional graph name. if (actualLineNumber == 1) { // Reject obvious non-DOT formats if (str.contains("vertices", Qt::CaseInsensitive) || // Pajek str.contains("network", Qt::CaseInsensitive) || // Pajek? str.contains("[", Qt::CaseInsensitive) || // GML str.contains("DL n", Qt::CaseInsensitive) || // UCINET DL str == "DL" || str == "dl" || str.contains("list", Qt::CaseInsensitive) || // list str.startsWith(" QString { if (s.size() >= 2 && s.startsWith('"') && s.endsWith('"')) return s.mid(1, s.size() - 2); return s; }; if (str.contains("digraph", Qt::CaseInsensitive)) { graphDirType = EdgeType::Directed; if (lineElement.size() > 1 && lineElement[1] != "{") networkName = stripQuotes(lineElement[1]); qCDebug(lcParser) << "This is a DOT DIGRAPH named " << networkName; continue; } else if (str.contains("graph", Qt::CaseInsensitive)) { graphDirType = EdgeType::Undirected; if (lineElement.size() > 1 && lineElement[1] != "{") networkName = stripQuotes(lineElement[1]); qCDebug(lcParser) << "This is a DOT GRAPH named " << networkName; continue; } else { qCDebug(lcParser) << "*** Not a GraphViz file. " "Abort: dot format can only start with \"(di)graph netname {\""; errorMessage = tr("Not properly GraphViz-formatted file. " "First non-comment line should start with \"(di)graph netname {\""); return false; } } // Global graph settings block starts. // If the closing ']' is on the same line, skip without entering multi-line mode. if (str.contains("graph [", Qt::CaseInsensitive)) { qCDebug(lcParser) << "πŸ”΅ Detected global graph settings. Skipping..."; if (!str.contains(']')) netProperties = true; continue; } // Global graph settings (simple key=value) if (str.startsWith("label", Qt::CaseInsensitive) || str.startsWith("mincross", Qt::CaseInsensitive) || str.startsWith("ratio", Qt::CaseInsensitive) || str.startsWith("name", Qt::CaseInsensitive) || str.startsWith("type", Qt::CaseInsensitive) || str.startsWith("loops", Qt::CaseInsensitive) || str.startsWith("rankdir", Qt::CaseInsensitive) || str.startsWith("splines", Qt::CaseInsensitive) || str.startsWith("overlap", Qt::CaseInsensitive) || str.startsWith("nodesep", Qt::CaseInsensitive) || str.startsWith("ranksep", Qt::CaseInsensitive) || str.startsWith("size", Qt::CaseInsensitive)) { qCDebug(lcParser) << "πŸ”΅ Detected global graph settings. Parsing..."; next = str.indexOf('=', 1); prop = str.mid(0, next).simplified(); value = str.right(str.size() - next - 1).simplified(); if (prop == "label" || prop == "name") networkLabel = value; else if (prop == "size") qCDebug(lcParser) << "⚠️ Ignoring 'size' attribute:" << value; continue; } // End of global graph settings block if (netProperties && str.contains("]", Qt::CaseInsensitive)) { netProperties = false; continue; } // Global node defaults if (str.startsWith("node [", Qt::CaseInsensitive)) { qCDebug(lcParser) << "πŸ”΅ Detected global node settings..."; QHash ignored; readDotProperties( str.mid(str.indexOf('[') + 1, str.indexOf(']') - str.indexOf('[') - 1), nodeValue, nodeLabel, initNodeShape, initNodeColor, fontName, fontColor, ignored); qCDebug(lcParser) << "βœ… Default node color set to:" << initNodeColor; continue; } // Global edge defaults if (str.startsWith("edge [", Qt::CaseInsensitive)) { qCDebug(lcParser) << "πŸ”΅ Detected global edge settings..."; QHash ignored; readDotProperties( str.mid(str.indexOf('[') + 1, str.indexOf(']') - str.indexOf('[') - 1), edgeWeight, edgeLabel, edgeShape, initEdgeColor, fontName, fontColor, ignored); qCDebug(lcParser) << "βœ… Default edge color set to:" << initEdgeColor; continue; } // Node with properties: nodeName [ ... ] if (!str.startsWith('[', Qt::CaseInsensitive) && !str.contains("--", Qt::CaseInsensitive) && !str.contains("->", Qt::CaseInsensitive) && str.contains("[", Qt::CaseInsensitive) && !netProperties) { qCDebug(lcParser) << "πŸ”΅ A single node definition must be here:\n" << str; start = str.indexOf('['); if (start == -1) { errorMessage = tr("Not properly GraphViz-formatted file. Node definition without opening ["); return false; } node = str.left(start).simplified().remove('\"'); qCDebug(lcParser) << "node named" << node; end = str.lastIndexOf(']'); if (end == -1) { errorMessage = tr("Not properly GraphViz-formatted file. Node definition without closing ]"); return false; } temp = str.mid(start + 1, end - start - 1); nodeLabel = node; // default label QHash nodeCustomAttrs; readDotProperties(temp, nodeValue, nodeLabel, initNodeShape, initNodeColor, fontName, fontColor, nodeCustomAttrs); if (nodeLabel.isEmpty()) nodeLabel = node; totalNodes++; randX = rand() % gwWidth; randY = rand() % gwHeight; QPointF nodePos(randX, randY); if (nodeCustomAttrs.contains("pos")) { const QStringList xy = nodeCustomAttrs.take("pos").split(','); if (xy.size() == 2) { bool okX, okY; const qreal px = xy[0].toDouble(&okX); const qreal py = xy[1].toDouble(&okY); if (okX && okY) nodePos = QPointF(px, py); } } if (m_parseSink) { m_parseSink->createNode( totalNodes, initNodeSize, initNodeColor, initNodeNumberColor, initNodeNumberSize, nodeLabel, initNodeLabelColor, initNodeLabelSize, nodePos, initNodeShape, QString(), false, nodeCustomAttrs); } nodesDiscovered.push_back(node); target = totalNodes; continue; } // Node without properties: nodeName; if (!str.contains('[', Qt::CaseInsensitive) && !str.contains("node", Qt::CaseInsensitive) && !str.contains(']', Qt::CaseInsensitive) && !str.contains("--", Qt::CaseInsensitive) && !str.contains("->", Qt::CaseInsensitive) && !str.contains("=", Qt::CaseInsensitive) && !netProperties) { qCDebug(lcParser) << "πŸ”΅ A node definition without properties must be here ..." << str; end = str.indexOf(';'); if (end == -1) continue; node = str.left(end).remove(']').remove(';').remove('\"').trimmed(); nodeLabel = node; totalNodes++; randX = rand() % gwWidth; randY = rand() % gwHeight; if (m_parseSink) { m_parseSink->createNode( totalNodes, initNodeSize, initNodeColor, initNodeNumberColor, initNodeNumberSize, nodeLabel, initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), initNodeShape, QString()); } nodesDiscovered.push_back(node); target = totalNodes; continue; } // Edge statement: MUST contain either "--" (undirected) or "->" (directed). // We do NOT treat any '-' as an edge indicator. if (str.contains("--", Qt::CaseInsensitive) || str.contains("->", Qt::CaseInsensitive)) { netProperties = false; qCDebug(lcParser) << "πŸ”΅ Edge definition found ..."; // Parse optional attribute block [...] end = str.indexOf('['); if (end != -1) { temp = str.right(str.size() - end - 1); temp = temp.remove(']').remove(';'); edgeCustomAttrs.clear(); readDotProperties(temp, edgeWeight, edgeLabel, edgeShape, edgeColor, fontName, fontColor, edgeCustomAttrs); initEdgeColor = edgeColor; } else { edgeLabel = ""; edgeColor = initEdgeColor; edgeWeight = initEdgeWeight; end = str.indexOf(';'); if (end == -1) end = str.size(); } // Keep only the edge chain part ("a -- b -- c" or "a -> b -> c") // NOTE: Cannot parse nodes named with '-' character (existing FIXME) QString edgeChain = str.mid(0, end).remove('\"').trimmed(); const bool isDirectedEdge = edgeChain.contains("->", Qt::CaseInsensitive); const bool isUndirectedEdge = edgeChain.contains("--", Qt::CaseInsensitive); if (isDirectedEdge && isUndirectedEdge) { // Mixed operator on same line is unusual; treat as parse error rather than guessing. errorMessage = tr("Invalid DOT edge statement: mixed '->' and '--' operators on the same line."); return false; } if (isUndirectedEdge) { nodeSequence = edgeChain.split("--", Qt::SkipEmptyParts); edgeTypeThisLine = EdgeType::Undirected; } else { nodeSequence = edgeChain.split("->", Qt::SkipEmptyParts); edgeTypeThisLine = EdgeType::Directed; } // Create all nodes defined in nodeSequence (and edges between consecutive nodes) for (auto it = nodeSequence.begin(); it != nodeSequence.end(); ++it) { node = (*it).simplified(); // Create node if unseen if ((aNum = nodesDiscovered.indexOf(node)) == -1) { totalNodes++; randX = rand() % gwWidth; randY = rand() % gwHeight; if (m_parseSink) { m_parseSink->createNode( totalNodes, initNodeSize, nodeColor, initNodeNumberColor, initNodeNumberSize, node, initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), initNodeShape, QString()); } nodesDiscovered.push_back(node); target = totalNodes; } else { target = aNum + 1; } // Emit edge between previous node (source) and current node (target) if (it != nodeSequence.begin()) { totalLinks++; if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, edgeColor, edgeTypeThisLine, arrows, bezier, edgeLabel, false, edgeCustomAttrs); } } source = target; } nodeSequence.clear(); continue; } // Default node properties - no node keyword if (str.contains("[", Qt::CaseInsensitive) && str.contains("=") && !netProperties) { start = str.indexOf('['); end = str.indexOf(']'); if (start == -1 || end == -1 || end <= start) continue; temp = str.mid(start + 1, end - start - 1).simplified(); QHash nodeCustomAttrs2; readDotProperties(temp, nodeValue, label, nodeShape, nodeColor, fontName, fontColor, nodeCustomAttrs2); if (start > 2) { node = str.left(start).remove('\"').simplified(); if (!nodesDiscovered.contains(node)) { totalNodes++; randX = rand() % gwWidth; randY = rand() % gwHeight; QPointF nodePos2(randX, randY); if (nodeCustomAttrs2.contains("pos")) { const QStringList xy = nodeCustomAttrs2.take("pos").split(','); if (xy.size() == 2) { bool okX, okY; const qreal px = xy[0].toDouble(&okX); const qreal py = xy[1].toDouble(&okY); if (okX && okY) nodePos2 = QPointF(px, py); } } if (m_parseSink) { m_parseSink->createNode( totalNodes, initNodeSize, nodeColor, initNodeNumberColor, initNodeNumberSize, label, initNodeLabelColor, initNodeLabelSize, nodePos2, nodeShape, QString(), false, nodeCustomAttrs2); } nodesDiscovered.push_back(node); } } continue; } qCDebug(lcParser) << " Redundant data:" << str; } // Ensure at least one relation exists. if (relationsList.size() == 0) { QString relName = (!networkName.isEmpty()) ? networkName : "unnamed"; if (m_parseSink) { m_parseSink->addNewRelation(relName); } } // IMPORTANT: Preserve graph-level directedness for the file. // Do NOT leak "last edge operator" into the graph kind. edgeDirType = graphDirType; qCDebug(lcParser) << "Parser::parseAsDot() - Finished OK. Returning."; return true; } /** * @brief Preprocesses the content of a DOT file to normalize its formatting, improve parsing and readability. * * This function performs several transformations on the input DOT content: * - Converts escaped newlines (\n) into actual newlines. * - Adds newlines after opening braces `{` and before closing braces `}`. * - Adds newlines after each closing bracket `]` and semicolon `;`. * - Normalizes brackets by adding spaces around them. * - Ensures there is a semicolon `;` between consecutive node definitions. * - Adds spaces around edge definitions (`->` and `--`). * - Handles node and edge attribute blocks more carefully by adding newlines before them. * - Merges edge attributes written on separate lines with their respective edge definitions. * * @param dotContent The original content of the DOT file as a QString. * @return A QString containing the preprocessed DOT content. */ QString Parser::preprocessDotContent(const QString &dotContent) { QString processedData = dotContent; // Convert escaped newlines (\n) into actual newlines processedData.replace("\n", "\\n"); // Add newline after opening brace `{` processedData.replace(QRegularExpression("\\{\\s*"), "{\n "); // Add newline after each closing bracket `]` (but don't add semicolon if one already exists) // This effectively splits node definitions into separate lines and ensures they end with a semicolon processedData.replace(QRegularExpression("\\](\\s*)(?!;)"), "];\n "); processedData.replace(QRegularExpression("\\];(\\s*)"), "];\n "); // Add newline after each semicolon `;` processedData.replace(";", ";\n "); // Add newline before closing brace `}` processedData.replace(QRegularExpression("\\s*\\}"), "\n}"); // Normalize brackets - add spaces around them for better parsing processedData.replace("[", " [ "); processedData.replace("]", " ] "); // Ensure there is a semicolon `;` between consecutive node definitions processedData.replace(QRegularExpression("(\\]\\s*)(struct\\d+)"), "];\n \\2"); // Add spaces around edge definitions processedData.replace("->", " -> "); processedData.replace("--", " -- "); // Handle node and edge attribute blocks more carefully processedData.replace(QRegularExpression("\\bnode\\s*\\["), "\nnode ["); processedData.replace(QRegularExpression("\\bedge\\s*\\["), "\nedge ["); // Handle cases where an edge attribute is written on a separate line QStringList lines = processedData.split('\n'); QStringList processedLines; bool previousLineWasEdge = false; QString previousLine; for (const QString &line : lines) { QString currentLine = line.trimmed(); if (currentLine.isEmpty()) { continue; // Skip empty lines } // Check if this line contains only attributes (starts with '[' but not a node/edge definition) if (currentLine.startsWith('[') && !currentLine.contains("->") && !currentLine.contains("--") && !currentLine.startsWith("node") && !currentLine.startsWith("edge") && previousLineWasEdge) { // Merge edge attributes with previous edge definition QString combinedLine = previousLine; if (combinedLine.endsWith(';')) { combinedLine.chop(1); } combinedLine += " " + currentLine; // Replace the previous line with the combined line processedLines.removeLast(); processedLines.append(combinedLine); previousLineWasEdge = false; } else { // Check if this is an edge definition line previousLineWasEdge = (currentLine.contains("->") || currentLine.contains("--")); previousLine = currentLine; // Add the current line as-is processedLines.append(currentLine); } } // Return the processed data return processedLines.join('\n'); } /** * @brief Reads the properties of a dot element with improved handling of quoted values * @param str String containing properties (format: "prop1=value1, prop2=value2, ...") * @param nValue Output variable for numeric value property * @param label Output variable for label property * @param shape Output variable for shape property * @param color Output variable for color property * @param fontName Output variable for font name property * @param fontColor Output variable for font color property */ void Parser::readDotProperties(QString str, qreal &nValue, QString &label, QString &shape, QString &color, QString &fontName, QString &fontColor, QHash &customAttrs) { qCDebug(lcParser) << "Reading DOT properties from:" << str; str = str.simplified(); // Process properties one by one while (!str.isEmpty()) { int equalPos = str.indexOf('='); if (equalPos == -1) { break; // No more properties } // Extract property name QString prop = str.left(equalPos).simplified(); str = str.mid(equalPos + 1).simplified(); // Check for quoted value QString value; if (str.startsWith('\"')) { // Handle quoted value (which might contain commas) int endQuotePos = -1; bool escaped = false; // Find the matching end quote, accounting for escaped quotes for (int i = 1; i < str.length(); i++) { if (str.at(i) == '\\') { escaped = !escaped; } else if (str.at(i) == '\"' && !escaped) { endQuotePos = i; break; } else { escaped = false; } } if (endQuotePos != -1) { value = str.mid(1, endQuotePos - 1); // Extract without quotes str = str.mid(endQuotePos + 1).simplified(); } else { // Malformed - no closing quote qCDebug(lcParser) << "Warning: No closing quote found in property value"; value = str.mid(1); str.clear(); } // Handle unescape if needed value = value.replace("\\\"", "\""); } else { // Handle unquoted value (which ends at next comma or end of string) int commaPos = str.indexOf(','); if (commaPos != -1) { value = str.left(commaPos).simplified(); str = str.mid(commaPos + 1).simplified(); } else { value = str.simplified(); str.clear(); } } // Skip any leading comma in remaining string if (str.startsWith(',')) { str = str.mid(1).simplified(); } qCDebug(lcParser) << "Parsed property:" << prop << "=" << value; // Process the property bool ok = false; if (prop == "label") { label = value; qCDebug(lcParser) << "Set label to:" << label; } else if (prop == "fontname") { fontName = value; qCDebug(lcParser) << "Set fontName to:" << fontName; } else if (prop == "value") { nValue = value.toFloat(&ok); if (ok) { qCDebug(lcParser) << "Set value to:" << nValue; } else { qCDebug(lcParser) << "Error converting value:" << value; } } else if (prop == "color" || prop == "fillcolor") { color = value; qCDebug(lcParser) << "Set color to:" << color; } else if (prop == "fontcolor") { fontColor = value; qCDebug(lcParser) << "Set fontColor to:" << fontColor; } else if (prop == "shape") { shape = value; qCDebug(lcParser) << "Set shape to:" << shape; } else if (prop == "weight") { nValue = value.toFloat(&ok); if (ok) { qCDebug(lcParser) << "Set weight to:" << nValue; } else { qCDebug(lcParser) << "Error converting weight:" << value; } } else if (prop == "style") { qCDebug(lcParser) << "Style property:" << value << "(currently not used)"; } else { qCDebug(lcParser) << "Storing unknown property as custom attribute:" << prop << "=" << value; customAttrs[prop] = value; } } } SocNetV-3.7/src/parser/parser_edgelist.cpp000066400000000000000000000552161524402210300206510ustar00rootroot00000000000000/** * @file parser_edgelist.cpp * @brief Edge-list parsers for SocNetV * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "parser.h" #include "global.h" SOCNETV_USE_NAMESPACE #include #include #include #include using namespace std; /** * @brief Parses the data as weighted edgelist formatted network * * This method can read and parse edgelist formated files * where edge source and target are either named with numbers or with labels * That is the following formats can be parsed: * # edgelist with node numbers * 1 2 1 * 1 3 2 * 1 6 2 * 1 8 2 * ... * * # edgelist with node labels * actor1 actor2 1 * actor2 actor4 2 * actor1 actor3 1 * actorX actorY 3 * name othername 1 * othername somename 2 * ... * @param rawData * @param delimiter * @return */ bool Parser::parseAsEdgeListWeighted(const QByteArray &rawData, const QString &delimiter) { qCDebug(lcParser) << "Parsing data as weighted edgelist formatted..." << "column delimiter" << delimiter; QTextCodec *codec = QTextCodec::codecForName(m_textCodecName.toLatin1()); QString decodedData = codec->toUnicode(rawData); QTextStream ts(&decodedData); QMap nodeMap; // use a minimum priority queue to order Actors by their value // so that we can create the discovered nodes by either their increasing nodeNumber // (if nodesWithLabels == true) or by their actual number in the file (if nodesWithLabels == false). priority_queue, CompareActors> nodeQ; QHash edgeList; QString str, edgeKey, edgeKeyDelimiter = "====>"; QStringList lineElement, edgeElement; // one or more digits QRegularExpression onlyDigitsExp("^\\d+$"); bool nodesWithLabels = false; bool conversionOK = false; int fileLine = 0, actualLineNumber = 0; totalNodes = 0; edgeWeight = 1.0; edgeDirType = EdgeType::Directed; arrows = true; bezier = false; relationsList.clear(); qCDebug(lcParser) << "*** Initial file parsing " "to test integrity and edge naming scheme"; while (!ts.atEnd()) { fileLine++; str = ts.readLine().simplified().trimmed(); qCDebug(lcParser) << " simplified str" << str; if (isComment(str)) continue; actualLineNumber++; if ( actualLineNumber == 1 && (str.contains("vertices", Qt::CaseInsensitive) || str.contains("network", Qt::CaseInsensitive) || str.contains("graph", Qt::CaseInsensitive) || str.contains("digraph", Qt::CaseInsensitive) || str.contains("DL n", Qt::CaseInsensitive) // DL format || str == "DL" || str == "dl" || str.contains("list", Qt::CaseInsensitive) || str.contains("graphml", Qt::CaseInsensitive) || str.contains("xml", Qt::CaseInsensitive))) { qCDebug(lcParser) << "Not a Weighted list-formatted file. Aborting!!"; errorMessage = tr("Not an EdgeList-formatted file. " "A non-comment line includes keywords reserved by other file formats (i.e vertices, graphml, network, graph, digraph, DL, xml)"); return false; } lineElement = str.split(delimiter); if (lineElement.size() != 3) { qCDebug(lcParser) << "*** Not a Weighted list-formatted file. Aborting!!"; errorMessage = tr("Not a properly EdgeList-formatted file. " "Row %1 has not 3 elements as expected (i.e. source, target, weight)") .arg(fileLine); return false; } edge_source = lineElement[0]; edge_target = lineElement[1]; edge_weight = lineElement[2]; qCDebug(lcParser) << " Dissecting line - " "source:" << edge_source << "target:" << edge_target << "weight:" << edge_weight; if (!edge_source.contains(onlyDigitsExp)) { qCDebug(lcParser) << " node named by non-digit only string. " "nodesWithLabels = true"; nodesWithLabels = true; } if (!edge_target.contains(onlyDigitsExp)) { qCDebug(lcParser) << " node named by non-digit only string. " "nodesWithLabels = true"; nodesWithLabels = true; } } ts.seek(0); fileLine = 0; qCDebug(lcParser) << "*** Initial file parsing finished. " "This is really a weighted edge list. Proceed to main parsing"; while (!ts.atEnd()) { str = ts.readLine(); qCDebug(lcParser) << " str" << str; str = str.simplified(); qCDebug(lcParser) << " simplified str" << str; if (isComment(str)) continue; lineElement = str.split(delimiter); edge_source = lineElement[0]; edge_target = lineElement[1]; edge_weight = lineElement[2]; qCDebug(lcParser) << " Dissecting line - " "source:" << edge_source << "target:" << edge_target << "weight:" << edge_weight; if (!nodeMap.contains(edge_source)) { totalNodes++; Actor sourceActor; sourceActor.key = edge_source; if (nodesWithLabels) { sourceActor.value = totalNodes; // order by an increasing totalNodes index nodeQ.push(sourceActor); nodeMap.insert(edge_source, totalNodes); } else { sourceActor.value = edge_source.toInt(); // order by the actual actor number in the file nodeQ.push(sourceActor); nodeMap.insert(edge_source, edge_source.toInt()); } qCDebug(lcParser) << " source, new node named" << edge_source << "totalNodes" << totalNodes << "nodeMap.count" << nodeMap.size(); } else { qCDebug(lcParser) << " source already found, continue"; } if (!nodeMap.contains(edge_target)) { totalNodes++; Actor targetActor; targetActor.key = edge_target; if (nodesWithLabels) { targetActor.value = totalNodes; // order by an increasing totalNodes index nodeQ.push(targetActor); nodeMap.insert(edge_target, totalNodes); } else { targetActor.value = edge_target.toInt(); // order by the actual actor number in the file nodeQ.push(targetActor); nodeMap.insert(edge_target, edge_target.toInt()); } qCDebug(lcParser) << " target, new node named" << edge_target << "totalNodes" << totalNodes << "nodeMap.count" << nodeMap.size(); } else { qCDebug(lcParser) << " target already found, continue"; } edgeWeight = edge_weight.toDouble(&conversionOK); if (conversionOK) { qCDebug(lcParser) << " read edge weight" << edgeWeight; } else { edgeWeight = 1.0; qCDebug(lcParser) << " error reading edge weight." "Using default edgeWeight" << edgeWeight; } edgeKey = edge_source + edgeKeyDelimiter + edge_target; if (!edgeList.contains(edgeKey)) { qCDebug(lcParser) << " inserting edgeKey" << edgeKey << "in edgeList with weight" << edgeWeight; edgeList.insert(edgeKey, edgeWeight); totalLinks++; } } // end ts.stream while here qCDebug(lcParser) << " finished reading file, " "start creating nodes and edges"; // print_queue(nodeQ); // create nodes one by one while (!nodeQ.empty()) { Actor node = nodeQ.top(); nodeQ.pop(); randX = rand() % gwWidth; randY = rand() % gwHeight; if (nodesWithLabels) { qCDebug(lcParser) << "signaling to create new node" << node.value << "label" << node.key << "at pos" << QPointF(randX, randY); if (m_parseSink) { m_parseSink->createNode(node.value, initNodeSize, initNodeColor, initNodeNumberColor, initNodeNumberSize, node.key, initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), initNodeShape, QString()); } } else { qCDebug(lcParser) << "signaling to create new node" << node.key.toInt() << "label" << node.key << "at pos" << QPointF(randX, randY); if (m_parseSink) { m_parseSink->createNode(node.key.toInt(), initNodeSize, initNodeColor, initNodeNumberColor, initNodeNumberSize, node.key, initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), initNodeShape, QString()); } } } // create edges one by one QHash::const_iterator edge = edgeList.constBegin(); while (edge != edgeList.constEnd()) { qCDebug(lcParser) << " creating edge named" << edge.key() << " weight " << edge.value(); edgeElement = edge.key().split(edgeKeyDelimiter); if (nodesWithLabels) { source = nodeMap.value(edgeElement[0]); target = nodeMap.value(edgeElement[1]); } else { source = edgeElement[0].toInt(); target = edgeElement[1].toInt(); } edgeWeight = edge.value(); if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, initEdgeColor, edgeDirType, arrows, bezier); } ++edge; } if (relationsList.size() == 0) { if (m_parseSink) { m_parseSink->addNewRelation("unnamed"); } } qCDebug(lcParser) << " END. Returning."; return true; } /** * @brief Parses the data as simple edgelist formatted * * @param rawData * @param delimiter * @return bool */ bool Parser::parseAsEdgeListSimple(const QByteArray &rawData, const QString &delimiter) { qCDebug(lcParser) << "Parsing data as simple edgelist formatted..." << "column delimiter" << delimiter; QTextCodec *codec = QTextCodec::codecForName(m_textCodecName.toLatin1()); QString decodedData = codec->toUnicode(rawData); QTextStream ts(&decodedData); QString str, edgeKey, edgeKeyDelimiter = "====>"; QStringList lineElement, edgeElement; int columnCount = 0; int fileLine = 0, actualLineNumber = 0; bool nodesWithLabels = false; //@TODO Always use nodesWithLabels= true QMap nodeMap; // use a minimum priority queue to order Actors by their value // so that we can create the discovered nodes by either their increasing nodeNumber // (if nodesWithLabels == true) or by their actual number in the file (if nodesWithLabels == false). priority_queue, CompareActors> nodeQ; QHash edgeList; QRegularExpression onlyDigitsExp("^\\d+$"); totalNodes = 0; initEdgeWeight = 1.0; edgeDirType = EdgeType::Directed; arrows = true; bezier = false; relationsList.clear(); qCDebug(lcParser) << "*** Initial file parsing " "to test integrity and edge naming scheme"; while (!ts.atEnd()) { fileLine++; str = ts.readLine().simplified().trimmed(); qCDebug(lcParser) << " line " << fileLine << "\n" << str; if (isComment(str)) { continue; } actualLineNumber++; if (actualLineNumber == 1 && (str.contains("vertices", Qt::CaseInsensitive) || str.contains("network", Qt::CaseInsensitive) || str.contains("graph", Qt::CaseInsensitive) || str.contains("digraph", Qt::CaseInsensitive) || str.contains("DL n", Qt::CaseInsensitive) || str == "DL" || str == "dl" || str.contains("list", Qt::CaseInsensitive) || str.contains("graphml", Qt::CaseInsensitive) || str.contains("xml", Qt::CaseInsensitive))) { qCDebug(lcParser) << "*** Not an EdgeList-formatted file. Aborting!!"; errorMessage = tr("Not an EdgeList-formatted file. " "Non-comment line %1 includes keywords reserved by other file formats (i.e vertices, graphml, network, graph, digraph, DL, xml)") .arg(fileLine); return false; } lineElement = str.split(delimiter); for (QStringList::Iterator it1 = lineElement.begin(); it1 != lineElement.end(); ++it1) { edge_source = (*it1); if (!edge_source.contains(onlyDigitsExp)) { qCDebug(lcParser) << " node named by non-digit only string. " "nodesWithLabels = true"; nodesWithLabels = true; } if (edge_source == "0") { nodesWithLabels = true; } } } ts.seek(0); fileLine = 0; qCDebug(lcParser) << " Reset and read lines. nodesWithLabels" << nodesWithLabels; while (!ts.atEnd()) { fileLine++; str = ts.readLine(); str = str.simplified(); qCDebug(lcParser) << " line" << fileLine << "\n" << str; if (isComment(str)) continue; lineElement = str.split(delimiter); columnCount = 0; for (QStringList::Iterator it1 = lineElement.begin(); it1 != lineElement.end(); ++it1) { columnCount++; if (columnCount == 1) { // source node edge_source = (*it1); qCDebug(lcParser) << " Dissecting line - " "source node:" << edge_source; if (!nodeMap.contains(edge_source)) { totalNodes++; Actor sourceActor; sourceActor.key = edge_source; if (nodesWithLabels) { sourceActor.value = totalNodes; // order by an increasing totalNodes index nodeQ.push(sourceActor); nodeMap.insert(edge_source, totalNodes); } else { sourceActor.value = edge_source.toInt(); // order by the actual actor number in the file nodeQ.push(sourceActor); nodeMap.insert(edge_source, edge_source.toInt()); } qCDebug(lcParser) << " source, new node named" << edge_source << "totalNodes" << totalNodes << "nodeMap.count" << nodeMap.size(); } else { qCDebug(lcParser) << " source already found, continue"; } } else { // target nodes edge_target = (*it1); qCDebug(lcParser) << " Dissecting line - " "target node:" << edge_target; if (!nodeMap.contains(edge_target)) { totalNodes++; Actor targetActor; targetActor.key = edge_target; if (nodesWithLabels) { targetActor.value = totalNodes; // order by an increasing totalNodes index nodeQ.push(targetActor); nodeMap.insert(edge_target, totalNodes); } else { targetActor.value = edge_target.toInt(); // order by the actual actor number in the file nodeQ.push(targetActor); nodeMap.insert(edge_target, edge_target.toInt()); } qCDebug(lcParser) << " target, new node named" << edge_target << "totalNodes" << totalNodes << "nodeMap.count" << nodeMap.size(); } else { qCDebug(lcParser) << " target already found, continue"; } } if (columnCount > 1) { edgeKey = edge_source + edgeKeyDelimiter + edge_target; if (!edgeList.contains(edgeKey)) { qCDebug(lcParser) << " inserting edgeKey" << edgeKey << "in edgeList with initial weight" << initEdgeWeight; edgeList.insert(edgeKey, initEdgeWeight); totalLinks++; } else { // if edge already discovered, then increase its weight by 1 edgeWeight = edgeList.value(edgeKey); edgeWeight = edgeWeight + 1; qCDebug(lcParser) << " edgeKey" << edgeKey << "found before, adding in edgeList with increased weight" << edgeWeight; edgeList.insert(edgeKey, edgeWeight); } } } // end for QStringList::Iterator } // end ts.stream while here // create nodes one by one while (!nodeQ.empty()) { Actor node = nodeQ.top(); nodeQ.pop(); randX = rand() % gwWidth; randY = rand() % gwHeight; if (nodesWithLabels) { qCDebug(lcParser) << "signaling to create new node" << node.value << "label" << node.key << "at pos" << QPointF(randX, randY); if (m_parseSink) { m_parseSink->createNode(node.value, initNodeSize, initNodeColor, initNodeNumberColor, initNodeNumberSize, node.key, initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), initNodeShape, QString()); } } else { qCDebug(lcParser) << "signaling to create new node" << node.key.toInt() << "label" << node.key << "at pos" << QPointF(randX, randY); if (m_parseSink) { m_parseSink->createNode(node.key.toInt(), initNodeSize, initNodeColor, initNodeNumberColor, initNodeNumberSize, node.key, initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), initNodeShape, QString()); } } } // create edges one by one QHash::const_iterator edge = edgeList.constBegin(); while (edge != edgeList.constEnd()) { qCDebug(lcParser) << " creating edge named" << edge.key() << " weight " << edge.value(); edgeElement = edge.key().split(edgeKeyDelimiter); if (nodesWithLabels) { source = nodeMap.value(edgeElement[0]); target = nodeMap.value(edgeElement[1]); } else { source = edgeElement[0].toInt(); target = edgeElement[1].toInt(); } edgeWeight = edge.value(); if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, initEdgeColor, edgeDirType, arrows, bezier); } ++edge; } if (relationsList.size() == 0) { if (m_parseSink) { m_parseSink->addNewRelation("unnamed"); } } qCDebug(lcParser) << " Finished OK. Returning."; return true; } /** * Debugging only - Used while parsing weighted edge lists */ template void print_queue(T &q) { qCDebug(lcParser) << "print_queue() "; while (!q.empty()) { qCDebug(lcParser) << q.top().key << " value: " << q.top().value << " "; q.pop(); } qCDebug(lcParser) << "\n"; } SocNetV-3.7/src/parser/parser_gml.cpp000066400000000000000000000511661524402210300176300ustar00rootroot00000000000000/** * @file parser_gml.cpp * @brief GML parsers for SocNetV * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "parser.h" #include "global.h" SOCNETV_USE_NAMESPACE #include #include /** * @brief Parses the data as GML formatted network. * * This parser is line/state based. Many GML files are "compact" and place * multiple attributes on the same line, e.g.: * * node [ id 1 label "1" ] * * while others use the expanded form: * * node [ * id 1 * label "1" * ] * * To support both forms, we preprocess the decoded input into a normalized * stream where: * - '[' and ']' become standalone lines (outside quoted strings) * - each attribute becomes its own "key value" line (outside quoted strings), * e.g. 'id 1 label "1"' -> 'id 1' + 'label "1"' * * We then run the existing state machine unchanged (but now it receives * one attribute per line). * * Also accepts both "weight" and "value" as edge weight keys. * * @param rawData Raw file bytes. * @return bool True on success, false on parse error. */ bool Parser::parseAsGML(const QByteArray &rawData) { qCDebug(lcParser) << "Parsing data as GML formatted..."; QTextCodec *codec = QTextCodec::codecForName(m_textCodecName.toLatin1()); QString decodedData = codec->toUnicode(rawData); /** * @brief Returns true if ch is whitespace (space/tab/etc). */ auto isWS = [](QChar ch) -> bool { return ch.isSpace(); }; /** * @brief Tokenizes a line into tokens while respecting quoted strings. * * Example: * id 1 label "John Doe" * becomes tokens: * ["id","1","label","\"John Doe\""] * * Quotes are preserved as part of the token. */ auto tokenizeRespectQuotes = [&](const QString &line) -> QStringList { QStringList tokens; QString cur; bool inQuotes = false; auto flush = [&]() { const QString t = cur.trimmed(); if (!t.isEmpty()) tokens << t; cur.clear(); }; for (int i = 0; i < line.size(); ++i) { const QChar ch = line.at(i); if (ch == '"') { inQuotes = !inQuotes; cur.append(ch); continue; } if (!inQuotes && isWS(ch)) { flush(); continue; } cur.append(ch); } flush(); return tokens; }; /** * @brief Normalizes GML text for a line-based parser. * * 1) Makes '[' and ']' standalone lines (outside quoted strings). * 2) Splits compact attribute runs into "key value" lines using a known-key set. * * This is intentionally conservative: we only split on keys we know how * to parse. Unknown tokens are left as-is. */ auto normalizeGmlForLineParser = [&](const QString &in) -> QString { // Keys we want to split into separate "key value" lines. const QSet keys = { "directed", "isplanar", "id", "label", "source", "target", "weight", "value", "graphics", "center", "type", "fill", "graph", "node", "edge"}; QString out; out.reserve(in.size() + 256); // Step 1: split brackets into their own lines (outside quotes). { bool inQuotes = false; for (int i = 0; i < in.size(); ++i) { const QChar ch = in.at(i); if (ch == '"') { inQuotes = !inQuotes; out.append(ch); continue; } if (!inQuotes && (ch == '[' || ch == ']')) { out.append('\n'); out.append(ch); out.append('\n'); continue; } out.append(ch); } } // Step 2: for each line, split "id 1 label "1"" into "id 1" + "label "1"" etc. QString normalized; normalized.reserve(out.size() + 256); QTextStream ts(&out); while (!ts.atEnd()) { QString line = ts.readLine().trimmed(); if (line.isEmpty()) { normalized.append('\n'); continue; } // Keep standalone bracket/section lines intact. if (line == "[" || line == "]") { normalized.append(line); normalized.append('\n'); continue; } // Tokenize with quote awareness. const QStringList toks = tokenizeRespectQuotes(line); if (toks.size() <= 2) { // already "key value" or single keyword normalized.append(line); normalized.append('\n'); continue; } // If the line is a known section keyword (graph/node/edge/graphics), keep it alone. const QString firstLower = toks.first().toLower(); if (firstLower == "graph" || firstLower == "node" || firstLower == "edge" || firstLower == "graphics") { normalized.append(toks.first()); normalized.append('\n'); // Remaining tokens may contain attributes on same line: split them. int i = 1; while (i < toks.size()) { const QString k = toks.at(i); const QString kLower = k.toLower(); if (!keys.contains(kLower)) { // Unknown token: keep as raw remainder and stop splitting. QString rest; for (int j = i; j < toks.size(); ++j) { if (!rest.isEmpty()) rest += " "; rest += toks.at(j); } normalized.append(rest); normalized.append('\n'); break; } // We expect "key value" pairs for attributes. if (i + 1 < toks.size()) { normalized.append(k); normalized.append(' '); normalized.append(toks.at(i + 1)); normalized.append('\n'); i += 2; } else { // Dangling key with no value: keep as-is. normalized.append(k); normalized.append('\n'); break; } } continue; } // Otherwise interpret as a sequence of key/value pairs: k v k v ... int i = 0; while (i < toks.size()) { const QString k = toks.at(i); const QString kLower = k.toLower(); if (!keys.contains(kLower)) { // Unknown token: keep remainder on one line. QString rest; for (int j = i; j < toks.size(); ++j) { if (!rest.isEmpty()) rest += " "; rest += toks.at(j); } normalized.append(rest); normalized.append('\n'); break; } if (i + 1 < toks.size()) { normalized.append(k); normalized.append(' '); normalized.append(toks.at(i + 1)); normalized.append('\n'); i += 2; } else { normalized.append(k); normalized.append('\n'); break; } } } return normalized; }; decodedData = normalizeGmlForLineParser(decodedData); QTextStream ts(&decodedData); QRegularExpression onlyDigitsExp("^\\d+$"); QStringList tempList; QString str; int fileLine = 0, actualLineNumber = 0; bool floatOK = false; bool isPlanar = false, graphKey = false, graphicsKey = false, edgeKey = false, nodeKey = false, graphicsCenterKey = false; Q_UNUSED(isPlanar); relationsList.clear(); node_id = QString(); arrows = true; bezier = false; edgeDirType = EdgeType::Undirected; totalNodes = 0; while (!ts.atEnd()) { floatOK = false; fileContainsNodeCoords = false; nodeShape = initNodeShape; nodeColor = initNodeColor; fileLine++; str = ts.readLine().simplified(); qCDebug(lcParser) << "line" << fileLine << ":" << str; if (isComment(str)) continue; actualLineNumber++; if (actualLineNumber == 1 && (str.contains("vertices", Qt::CaseInsensitive) || str.contains("network", Qt::CaseInsensitive) || str.contains("digraph", Qt::CaseInsensitive) || str.contains("DL n", Qt::CaseInsensitive) || str == "DL" || str == "dl" || str.contains("list", Qt::CaseInsensitive) || str.contains("graphml", Qt::CaseInsensitive) || str.contains("xml", Qt::CaseInsensitive))) { qCDebug(lcParser) << "*** Not a GML-formatted file. Aborting!!"; errorMessage = tr("Not an GML-formatted file. " "Non-comment line %1 includes keywords reserved by other file formats " "(i.e vertices, graphml, network, digraph, DL, xml)") .arg(fileLine); return false; } if (str.startsWith("comment", Qt::CaseInsensitive)) continue; if (str.startsWith("creator", Qt::CaseInsensitive)) continue; if (str.startsWith("graph", Qt::CaseInsensitive)) { qCDebug(lcParser) << "graph description list start"; graphKey = true; continue; } if (str.startsWith("directed", Qt::CaseInsensitive)) { if (graphKey) { if (str.contains("1")) { qCDebug(lcParser) << "graph directed 1. A directed graph."; edgeDirType = EdgeType::Directed; } else { qCDebug(lcParser) << "graph directed 0. An undirected graph."; } } continue; } if (str.startsWith("isPlanar", Qt::CaseInsensitive)) { if (graphKey) isPlanar = str.contains("1"); continue; } if (str.startsWith("node", Qt::CaseInsensitive)) { qCDebug(lcParser) << "node description list starts"; nodeKey = true; continue; } if (str.startsWith("id", Qt::CaseInsensitive)) { if (nodeKey) { totalNodes++; node_id = str.split(" ", Qt::SkipEmptyParts).last(); if (!node_id.contains(onlyDigitsExp)) { errorMessage = tr("Not a proper GML-formatted file. " "Node id tag at line %1 has non-arithmetic value.") .arg(fileLine); return false; } qCDebug(lcParser) << "id description " << "This node" << totalNodes << "id" << node_id; } continue; } if (str.startsWith("label ", Qt::CaseInsensitive)) { if (nodeKey) { nodeLabel = str.split(" ", Qt::SkipEmptyParts).last().remove("\""); qCDebug(lcParser) << "node label definition" << "node" << totalNodes << "id" << node_id << "label" << nodeLabel; } else if (edgeKey) { edgeLabel = str.split(" ", Qt::SkipEmptyParts).last(); qCDebug(lcParser) << "edge label definition" << "edge" << totalLinks << "label" << edgeLabel; } continue; } if (str.startsWith("edge ", Qt::CaseInsensitive) || str == "edge") { qCDebug(lcParser) << "edge description list start"; edgeKey = true; totalLinks++; edgeWeight = 1.0; edgeColor = "black"; edgeLabel.clear(); continue; } if (str.startsWith("source ", Qt::CaseInsensitive)) { if (edgeKey) { edge_source = str.split(" ", Qt::SkipEmptyParts).last(); if (!edge_source.contains(onlyDigitsExp)) { errorMessage = tr("Not a proper GML-formatted file. " "Edge source tag at line %1 has non-arithmetic value.") .arg(fileLine); return false; } source = edge_source.toInt(nullptr, 10); qCDebug(lcParser) << "edge source definition" << "edge source" << edge_source; } continue; } if (str.startsWith("target ", Qt::CaseInsensitive)) { if (edgeKey) { edge_target = str.split(" ", Qt::SkipEmptyParts).last(); // BUGFIX: validate edge_target, not edge_source if (!edge_target.contains(onlyDigitsExp)) { errorMessage = tr("Not a proper GML-formatted file. " "Edge target tag at line %1 has non-arithmetic value.") .arg(fileLine); return false; } target = edge_target.toInt(nullptr, 10); qCDebug(lcParser) << "edge target definition" << "edge target" << edge_target; } continue; } if (str.startsWith("weight ", Qt::CaseInsensitive) || str.startsWith("value ", Qt::CaseInsensitive)) { if (edgeKey) { edgeWeight = str.split(" ", Qt::SkipEmptyParts).last().toDouble(&floatOK); if (!floatOK) { errorMessage = tr("Not a proper GML-formatted file. " "Edge weight tag at line %1 has an invalid value.") .arg(fileLine); return false; } qCDebug(lcParser) << "edge weight definition" << "edge weight" << edgeWeight; } continue; } if (str.startsWith("graphics", Qt::CaseInsensitive)) { graphicsKey = true; continue; } if (str.startsWith("center", Qt::CaseInsensitive)) { if (graphicsKey && nodeKey) { if (str.contains("[", Qt::CaseInsensitive)) { if (str.contains("]", Qt::CaseInsensitive) && str.contains("x", Qt::CaseInsensitive) && str.contains("y", Qt::CaseInsensitive)) { str.remove("center"); str.remove("["); str.remove("]"); str = str.simplified(); tempList = str.split(" ", Qt::SkipEmptyParts); randX = (tempList.at(1)).toFloat(&floatOK); if (!floatOK) { errorMessage = tr("Not a proper GML-formatted file. " "Node center tag at line %1 cannot be converted to qreal.") .arg(fileLine); return false; } randY = tempList.at(3).toFloat(&floatOK); if (!floatOK) { errorMessage = tr("Not a proper GML-formatted file. " "Node center tag at line %1 cannot be converted to qreal.") .arg(fileLine); return false; } fileContainsNodeCoords = true; } else { graphicsCenterKey = true; } } } continue; } if (str.startsWith("type", Qt::CaseInsensitive)) { if (graphicsKey && nodeKey) { nodeShape = str.split(" ", Qt::SkipEmptyParts).last(); if (nodeShape.isNull() || nodeShape.isEmpty()) { errorMessage = tr("Not a proper GML-formatted file. " "Node type tag at line %1 has no value.") .arg(fileLine); return false; } nodeShape.remove("\""); } continue; } if (str.startsWith("fill", Qt::CaseInsensitive)) { if (graphicsKey && nodeKey) { nodeColor = str.split(" ", Qt::SkipEmptyParts).last(); if (nodeColor.isNull() || nodeColor.isEmpty()) { errorMessage = tr("Not a proper GML-formatted file. " "Node fill tag at line %1 has no value.") .arg(fileLine); return false; } } continue; } if (str.startsWith("]", Qt::CaseInsensitive)) { if (nodeKey && graphicsKey && graphicsCenterKey) { graphicsCenterKey = false; continue; } else if (graphicsKey) { graphicsKey = false; continue; } else if (nodeKey && !graphicsKey) { nodeKey = false; if (!fileContainsNodeCoords) { randX = rand() % gwWidth; randY = rand() % gwHeight; } if (m_parseSink) { m_parseSink->createNode( node_id.toInt(nullptr, 10), initNodeSize, nodeColor, initNodeNumberColor, initNodeNumberSize, nodeLabel, initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), nodeShape, QString()); } continue; } else if (edgeKey && !graphicsKey) { edgeKey = false; if (edgeLabel == QString()) edgeLabel = edge_source + "->" + edge_target; if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, edgeColor, edgeDirType, arrows, bezier, edgeLabel); } continue; } else if (graphKey) { graphKey = false; continue; } } } if (relationsList.size() == 0) { if (m_parseSink) { m_parseSink->addNewRelation("unnamed"); } } qCDebug(lcParser) << "Finished OK. Returning."; return true; } SocNetV-3.7/src/parser/parser_graphml.cpp000066400000000000000000001072601524402210300205000ustar00rootroot00000000000000/** * @file parser_graphml.cpp * @brief GraphML parsers for SocNetV * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "parser.h" #include "global.h" SOCNETV_USE_NAMESPACE #include #include #include #include /** * @brief Parses the data as GraphML (not GML) formatted network. * * @param rawData * @return bool */ bool Parser::parseAsGraphML(const QByteArray &rawData) { qCDebug(lcParser) << "Parsing data as GraphML formatted..."; totalNodes = 0; totalLinks = 0; nodeHash.clear(); relationsList.clear(); bool_key = false; bool_node = false; bool_edge = false; key_id = ""; key_name = ""; key_type = ""; key_value = ""; initNodeCustomIcon = ""; initEdgeWeight = 1; edgeWeight = 1; edgeColor = "black"; arrows = true; edgeDirType = EdgeType::Directed; // Create a xml parser QXmlStreamReader xml; // Prepare the user selected codec, if needed QByteArray userSelectedCodec = m_textCodecName.toLatin1(); // Add raw data into xml parser xml.addData(rawData); qCDebug(lcParser) << "Testing if XML document encoding is the same as the userSelectedCodec:" << userSelectedCodec; xml.readNext(); if (xml.isStartDocument()) { qCDebug(lcParser) << "XML document version" << xml.documentVersion() << "encoding" << xml.documentEncoding() << "userSelectedCodec" << m_textCodecName; if (xml.documentEncoding().toString() != m_textCodecName) { qCDebug(lcParser) << "Conflicting encodings. " << " Re-reading data with userSelectedCodec" << userSelectedCodec; xml.clear(); QTextCodec *codec = QTextCodec::codecForName(userSelectedCodec); QString decodedData = codec->toUnicode(rawData); xml.addData(decodedData); // QTextStream in(&rawData); // in.setAutoDetectUnicode(false); // QString decodedData = in.readAll(); // // QTextStream no longer supports setCodec // in.setEncoding() // QTextStream in(&rawData); } else { qCDebug(lcParser) << "Testing XML: OK"; xml.clear(); xml.addData(rawData); } } while (!xml.atEnd()) { xml.readNext(); qCDebug(lcParser) << "xml.token " << xml.tokenString(); if (xml.isStartDocument()) { qCDebug(lcParser) << "xml startDocument" << " version " << xml.documentVersion() << " encoding " << xml.documentEncoding(); } if (xml.isStartElement()) { qCDebug(lcParser) << "element name " << xml.name().toString(); if (xml.name().toString() == "graphml") { qCDebug(lcParser) << "GraphML start. NamespaceUri is " << xml.namespaceUri().toString() << "Calling readGraphML()"; if (!readGraphML(xml)) { // return false; break; } } else { // not a GraphML doc, return false. xml.raiseError( QObject::tr("not a GraphML file.")); qCDebug(lcParser) << "### Error in startElement " << " The file is not an GraphML version 1.0 file "; errorMessage = tr("Invalid GraphML file. " "XML at startElement but element name not graphml."); break; } } else if (xml.tokenString() == "Invalid") { xml.raiseError( QObject::tr("invalid GraphML or encoding.")); qCDebug(lcParser) << "### Cannot find startElement" << " The file is not valid GraphML or has invalid encoding"; errorMessage = tr("Invalid GraphML file. " "XML tokenString at line %1 invalid.") .arg(xml.lineNumber()); break; } } // end while // clear our mess - remove every hash element... keyFor.clear(); keyName.clear(); keyType.clear(); keyDefaultValue.clear(); nodeHash.clear(); edgeMissingNodesList.clear(); // if there was an error return false with error string if (xml.hasError()) { qCDebug(lcParser) << "### xmls has error! " "Returning false with errorString" << xml.errorString(); errorMessage = tr("Invalid GraphML file. " "XML has error at line %1, token name %2:\n\n%3") .arg(xml.lineNumber()) .arg(xml.name().toString()) .arg(xml.errorString()); xml.clear(); return false; } xml.clear(); qCDebug(lcParser) << "signaling to change to the first relation..."; if (m_parseSink) { m_parseSink->setRelation(0); } qCDebug(lcParser) << "Finished OK. Returning."; return true; } /** * @brief Checks the xml token name and calls the appropriate function. * * @param xml * @return bool */ bool Parser::readGraphML(QXmlStreamReader &xml) { qCDebug(lcParser) << "Reading graphml token/element..."; bool_node = false; bool_edge = false; bool_key = false; // Q_ASSERT(xml.isStartElement() && xml.name().toString() == "graph"); while (!xml.atEnd()) { // start reading until QXmlStreamReader end(). xml.readNext(); // read next token qCDebug(lcParser) << "line:" << xml.lineNumber(); if (xml.isStartElement()) { // new token (graph, node, or edge) here qCDebug(lcParser) << "isStartElement() : " << xml.name().toString(); if (xml.name().toString() == "graph") // graph definition token readGraphMLElementGraph(xml); else if (xml.name().toString() == "key") { // key definition token QXmlStreamAttributes xmlStreamAttr = xml.attributes(); readGraphMLElementKey(xmlStreamAttr); } else if (xml.name().toString() == "default") // default key value token readGraphMLElementDefaultValue(xml); else if (xml.name().toString() == "node") // graph definition token readGraphMLElementNode(xml); else if (xml.name().toString() == "data") // data definition token readGraphMLElementData(xml); else if (xml.name().toString() == "ShapeNode") { bool_node = true; } else if ((xml.name().toString() == "Geometry" || xml.name().toString() == "Fill" || xml.name().toString() == "BorderStyle" || xml.name().toString() == "NodeLabel" || xml.name().toString() == "Shape") && bool_node) { readGraphMLElementNodeGraphics(xml); } else if (xml.name().toString() == "edge") { // edge definition token QXmlStreamAttributes xmlStreamAttr = xml.attributes(); readGraphMLElementEdge(xmlStreamAttr); } else if (xml.name().toString() == "BezierEdge") { bool_edge = true; } else if ((xml.name().toString() == "Path" || xml.name().toString() == "LineStyle" || xml.name().toString() == "Arrows" || xml.name().toString() == "EdgeLabel") && bool_edge) { readGraphMLElementEdgeGraphics(xml); } else readGraphMLElementUnknown(xml); } if (xml.isEndElement()) { // token ends here qCDebug(lcParser) << " element ends here: " << xml.name().toString(); if (xml.name().toString() == "node") // node definition end endGraphMLElementNode(xml); else if (xml.name().toString() == "edge") // edge definition end endGraphMLElementEdge(xml); } if (xml.hasError()) { qCDebug(lcParser) << "xml has error:" << xml.errorString(); return false; } } // Check if we need to create any edges with missing nodes createMissingNodeEdges(); return true; } /** * @brief Reads a graph definition * * Called at Graph element * * @param xml */ void Parser::readGraphMLElementGraph(QXmlStreamReader &xml) { QXmlStreamAttributes xmlStreamAttr = xml.attributes(); QString defaultDirection = xmlStreamAttr.value("edgedefault").toString(); qCDebug(lcParser) << "Parsing graph element - edgedefault " << defaultDirection; if (defaultDirection == "undirected") { qCDebug(lcParser) << "this is an undirected graph "; edgeDirType = EdgeType::Undirected; arrows = false; } else { qCDebug(lcParser) << "this is a directed graph "; edgeDirType = EdgeType::Directed; arrows = true; } // store graph id networkName = xmlStreamAttr.value("id").toString(); // add it as relation relationsList << networkName; qCDebug(lcParser) << "Signaling to add new relation:" << networkName; if (m_parseSink) { m_parseSink->addNewRelation(networkName); } int lastRelationIndex = relationsList.size() - 1; if (lastRelationIndex > 0) { totalNodes = 0; qCDebug(lcParser) << "last relation index:" << lastRelationIndex << "signaling to change to the new relation"; if (m_parseSink) { m_parseSink->setRelation(lastRelationIndex); } } qCDebug(lcParser) << "graph id:" << networkName; } /** * @brief Reads a key definition * * called at key element * * @param xmlStreamAttr */ void Parser::readGraphMLElementKey(QXmlStreamAttributes &xmlStreamAttr) { key_id = xmlStreamAttr.value("id").toString(); qCDebug(lcParser) << "Reading key element - key id" << key_id; key_what = xmlStreamAttr.value("for").toString(); keyFor[key_id] = key_what; qCDebug(lcParser) << "key for " << key_what; if (xmlStreamAttr.hasAttribute("attr.name")) { // to be enabled in later versions.. key_name = xmlStreamAttr.value("attr.name").toString(); keyName[key_id] = key_name; qCDebug(lcParser) << "key attr.name" << key_name; } if (xmlStreamAttr.hasAttribute("attr.type")) { key_type = xmlStreamAttr.value("attr.type").toString(); keyType[key_id] = key_type; qCDebug(lcParser) << "key attr.type" << key_type; } else if (xmlStreamAttr.hasAttribute("yfiles.type")) { key_type = xmlStreamAttr.value("yfiles.type").toString(); keyType[key_id] = key_type; qCDebug(lcParser) << "key yfiles.type" << key_type; } } /** * @brief Reads default key values * * Called at a default element (usually nested inside key element) * * @param xml */ void Parser::readGraphMLElementDefaultValue(QXmlStreamReader &xml) { key_value = xml.readElementText(); key_name = keyName.value(key_id); keyDefaultValue[key_id] = key_value; // key_id is already stored qCDebug(lcParser) << "Reading default key values - key default value is" << key_value; if (keyFor.value(key_id) == "node") { if (key_name == "size") { qCDebug(lcParser) << "key default value" << key_value << "is for node size"; conv_OK = false; initNodeSize = key_value.toInt(&conv_OK); if (!conv_OK) initNodeSize = 8; } if (key_name == "shape") { qCDebug(lcParser) << "key default value" << key_value << "is for nodes shape"; initNodeShape = key_value; } if (key_name == "custom-icon") { qCDebug(lcParser) << "key default value" << key_value << "is for node custom-icon path"; initNodeCustomIcon = key_value; initNodeCustomIcon = fileDirPath + "/" + initNodeCustomIcon; qCDebug(lcParser) << "initNodeCustomIcon full path:" << initNodeCustomIcon; if (QFileInfo::exists(initNodeCustomIcon)) { qCDebug(lcParser) << "custom icon file exists!"; } else { qCDebug(lcParser) << "custom icon file does not exists!"; xml.raiseError( QObject::tr(" Default custom icon for nodes does not exist in the filesystem. \nThe declared icon file was: \n%1").arg(initNodeCustomIcon)); } } if (key_name == "color") { qCDebug(lcParser) << "key default value" << key_value << "is for nodes color"; initNodeColor = key_value; } if (key_name == "label.color") { qCDebug(lcParser) << "key default value" << key_value << "is for node labels color"; initNodeLabelColor = key_value; } if (key_name == "label.size") { qCDebug(lcParser) << "key default value" << key_value << "is for node labels size"; conv_OK = false; initNodeLabelSize = key_value.toInt(&conv_OK); if (!conv_OK) initNodeLabelSize = 8; } } else if (keyFor.value(key_id) == "edge") { if (key_name == "weight") { qCDebug(lcParser) << "key default value" << key_value << "is for edges weight"; conv_OK = false; initEdgeWeight = key_value.toDouble(&conv_OK); if (!conv_OK) initEdgeWeight = 1; } if (key_name == "color") { qCDebug(lcParser) << "key default value" << key_value << "is for edges color"; initEdgeColor = key_value; } } } /** * @brief Reads basic node attributes and sets the nodeNumber. * * called at the start of a node element * * @param xml */ void Parser::readGraphMLElementNode(QXmlStreamReader &xml) { QXmlStreamAttributes xmlStreamAttr = xml.attributes(); node_id = (xmlStreamAttr.value("id")).toString(); totalNodes++; nodeHash[node_id] = totalNodes; // copy default node attribute values. // Some might change when reading element data, some will stay the same... nodeColor = initNodeColor; nodeShape = initNodeShape; nodeIconPath = initNodeCustomIcon; nodeSize = initNodeSize; nodeNumberSize = initNodeNumberSize; nodeNumberColor = initNodeNumberColor; nodeLabel = node_id; nodeLabelSize = initNodeLabelSize; nodeLabelColor = initNodeLabelColor; nodeCustomAttributes = initNodeCustomAttributes; bool_node = true; randX = rand() % gwWidth; randY = rand() % gwHeight; } /** * @brief Signals to create a new node * * called at the end of a node element * * @param xml */ void Parser::endGraphMLElementNode(QXmlStreamReader &xml) { Q_UNUSED(xml); //@todo this check means we cannot have different nodes between relations. if (relationsList.size() > 1) { qCDebug(lcParser) << "multirelational data" "skipping node creation. Node should have been created in earlier relation"; bool_node = false; return; } qCDebug(lcParser) << "signaling to create a new node" << totalNodes << "id " << node_id << " label " << nodeLabel << "at pos:" << QPointF(randX, randY); if (nodeShape == "custom") { if (m_parseSink) { m_parseSink->createNode(totalNodes, nodeSize, nodeColor, nodeNumberColor, nodeNumberSize, nodeLabel, nodeLabelColor, nodeLabelSize, QPointF(randX, randY), nodeShape, (nodeIconPath.isEmpty() ? initNodeCustomIcon : nodeIconPath), false, nodeCustomAttributes); } } else { if (m_parseSink) { m_parseSink->createNode(totalNodes, nodeSize, nodeColor, nodeNumberColor, nodeNumberSize, nodeLabel, nodeLabelColor, nodeLabelSize, QPointF(randX, randY), nodeShape, QString(), false, nodeCustomAttributes); } } bool_node = false; } /** * @brief Reads basic edge creation properties. * * called at the start of an edge element * * @param xmlStreamAttr */ void Parser::readGraphMLElementEdge(QXmlStreamAttributes &xmlStreamAttr) { edge_source = xmlStreamAttr.value("source").toString(); edge_target = xmlStreamAttr.value("target").toString(); edge_directed = xmlStreamAttr.value("directed").toString(); missingNode = false; edgeWeight = initEdgeWeight; edgeColor = initEdgeColor; edgeLabel = ""; edgeCustomAttributes.clear(); bool_edge = true; if (edge_directed == "false" || edge_directed.contains("false", Qt::CaseInsensitive)) { edgeDirType = EdgeType::Undirected; qCDebug(lcParser) << "Edge is UNDIRECTED"; } else { edgeDirType = EdgeType::Directed; qCDebug(lcParser) << "Edge is DIRECTED"; } if (!nodeHash.contains(edge_source)) { qCDebug(lcParser) << "source node id " << edge_source << "for edge from " << edge_source << " to " << edge_target << "DOES NOT EXIST!" << "Inserting into edgesMissingNodesHash"; edgesMissingNodesHash.insert(edge_source + "===>" + edge_target, QString::number(edgeWeight) + "|" + edgeColor + "|" + QString::number(edgeDirType)); missingNode = true; } if (!nodeHash.contains(edge_target)) { qCDebug(lcParser) << "target node id " << edge_target << "for edge from " << edge_source << " to " << edge_target << "DOES NOT EXIST!" << "Inserting into edgesMissingNodesHash"; edgesMissingNodesHash.insert(edge_source + "===>" + edge_target, QString::number(edgeWeight) + "|" + edgeColor + "|" + QString::number(edgeDirType)); missingNode = true; } if (missingNode) { return; } source = nodeHash[edge_source]; target = nodeHash[edge_target]; qCDebug(lcParser) << "source " << edge_source << " num " << source << " - target " << edge_target << " num " << target << " edgeDirType " << edgeDirType; } /** * @brief Signals for a new edge to be created/added * * Called at the end of edge element * * @param xml */ void Parser::endGraphMLElementEdge(QXmlStreamReader &xml) { Q_UNUSED(xml); if (missingNode) { qCDebug(lcParser) << "missingNode true " << " postponing edge creation signal"; return; } qCDebug(lcParser) << "signaling to create new edge" << source << "->" << target << " edgeDirType value " << edgeDirType; if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, edgeColor, edgeDirType, arrows, bezier, edgeLabel, false, edgeCustomAttributes); } totalLinks++; bool_edge = false; } /** * @brief Reads data for edges and nodes * * called at a data element (usually nested inside a node or an edge element) * * @param xml */ void Parser::readGraphMLElementData(QXmlStreamReader &xml) { QXmlStreamAttributes xmlStreamAttr = xml.attributes(); key_id = xmlStreamAttr.value("key").toString(); key_name = keyName.value(key_id); key_value = xml.text().toString(); qCDebug(lcParser) << "parding data for key_id: " << key_id << "key_value " << key_value; if (key_value.trimmed() == "") { qCDebug(lcParser) << "empty key_value: " << key_value << "reading more xml.text()..."; xml.readNext(); key_value = xml.text().toString(); qCDebug(lcParser) << "now key_value: " << key_value; if (key_value.trimmed() != "") { // if there's simple text after the StartElement, qCDebug(lcParser) << "key_id " << key_id << " value is simple text " << key_value; } else { // no text, probably more tags. Return... qCDebug(lcParser) << "key_id " << key_id << " for " << keyFor.value(key_id) << ". More elements nested here. Returning"; return; } } if (keyFor.value(key_id) == "node") { if (key_name == "color") { qCDebug(lcParser) << "Data found. Node color: " << key_value << " for this node"; nodeColor = key_value; } else if (key_name == "label") { qCDebug(lcParser) << "Data found. Node label: " "" << key_value << " for this node"; nodeLabel = key_value; } else if (key_name == "x_coordinate") { qCDebug(lcParser) << "Data found. Node x: " << key_value << " for this node"; conv_OK = false; randX = key_value.toFloat(&conv_OK); if (!conv_OK) randX = 0; else randX = randX * gwWidth; qCDebug(lcParser) << "Using: " << randX; } else if (key_name == "y_coordinate") { qCDebug(lcParser) << "Data found. Node y: " << key_value << " for this node"; conv_OK = false; randY = key_value.toFloat(&conv_OK); if (!conv_OK) randY = 0; else randY = randY * gwHeight; qCDebug(lcParser) << "Using: " << randY; } else if (key_name == "size") { qCDebug(lcParser) << "Data found. Node size: " << key_value << " for this node"; conv_OK = false; nodeSize = key_value.toInt(&conv_OK); if (!conv_OK) nodeSize = initNodeSize; qCDebug(lcParser) << "Using: " << nodeSize; } else if (key_name == "label.size") { qCDebug(lcParser) << "Data found. Node label size: " << key_value << " for this node"; conv_OK = false; nodeLabelSize = key_value.toInt(&conv_OK); if (!conv_OK) nodeLabelSize = initNodeLabelSize; qCDebug(lcParser) << "Using: " << nodeSize; } else if (key_name == "label.color") { qCDebug(lcParser) << "Data found. Node label Color: " << key_value << " for this node"; nodeLabelColor = key_value; } else if (key_name == "shape") { qCDebug(lcParser) << "Data found. Node shape: " << key_value << " for this node"; nodeShape = key_value; } else if (key_name == "custom-icon") { qCDebug(lcParser) << "Data found. Node custom-icon path: " << key_value << " for this node"; nodeIconPath = key_value; nodeIconPath = fileDirPath + ("/") + nodeIconPath; qCDebug(lcParser) << "full node custom-icon path: " << nodeIconPath; } else { qCDebug(lcParser) << "Data found for custom attribute: " << key_name << " of this node. Data: " << key_value; nodeCustomAttributes.insert(key_name, key_value); } } else if (keyFor.value(key_id) == "edge") { if (key_name == "color") { qCDebug(lcParser) << "Data found. Edge color: " << key_value << " for this edge"; edgeColor = key_value; if (missingNode) { edgesMissingNodesHash.insert(edge_source + "===>" + edge_target, QString::number(edgeWeight) + "|" + edgeColor + "|" + QString::number(edgeDirType)); } } else if ((key_name == "value" || key_name == "weight")) { conv_OK = false; edgeWeight = key_value.toDouble(&conv_OK); if (!conv_OK) edgeWeight = 1.0; if (missingNode) { edgesMissingNodesHash.insert(edge_source + "===>" + edge_target, QString::number(edgeWeight) + "|" + edgeColor + "|" + QString::number(edgeDirType)); } qCDebug(lcParser) << "Data found. Edge value: " << key_value << " Using " << edgeWeight << " for this edge"; } else if (key_name == "size of arrow") { conv_OK = false; qreal temp = key_value.toFloat(&conv_OK); if (!conv_OK) arrowSize = 1; else arrowSize = temp; qCDebug(lcParser) << "Data found. Edge arrow size: " << key_value << " Using " << arrowSize << " for this edge"; } else if (key_name == "label") { edgeLabel = key_value; if (missingNode) { edgesMissingNodesHash.insert(edge_source + "===>" + edge_target, QString::number(edgeWeight) + "|" + edgeColor + "|" + QString::number(edgeDirType)); } qCDebug(lcParser) << "Data found. Edge label: " << edgeLabel << " for this edge"; } else { qCDebug(lcParser) << "Data found for custom edge attribute:" << key_name << "value:" << key_value; edgeCustomAttributes.insert(key_name, key_value); } } } /** * @brief Reads node graphics data and properties: label, color, shape, size, coordinates, etc. * @param xml */ void Parser::readGraphMLElementNodeGraphics(QXmlStreamReader &xml) { qCDebug(lcParser) << "reading node graphics/properties, element name" << xml.name().toString(); qreal tempX = -1, tempY = -1, temp = -1; QString color; QXmlStreamAttributes xmlStreamAttr = xml.attributes(); if (xml.name().toString() == "Geometry") { if (xmlStreamAttr.hasAttribute("x")) { conv_OK = false; tempX = xml.attributes().value("x").toString().toFloat(&conv_OK); if (conv_OK) randX = tempX; } if (xmlStreamAttr.hasAttribute("y")) { conv_OK = false; tempY = xml.attributes().value("y").toString().toFloat(&conv_OK); if (conv_OK) randY = tempY; } qCDebug(lcParser) << "Node Coordinates: " << tempX << " " << tempY << " Using coordinates" << randX << " " << randY; if (xmlStreamAttr.hasAttribute("width")) { conv_OK = false; temp = xmlStreamAttr.value("width").toString().toFloat(&conv_OK); if (conv_OK) nodeSize = temp; qCDebug(lcParser) << "Node Size: " << temp << " Using nodesize" << nodeSize; } if (xmlStreamAttr.hasAttribute("shape")) { nodeShape = xmlStreamAttr.value("shape").toString(); qCDebug(lcParser) << "Node Shape: " << nodeShape; } } else if (xml.name().toString() == "Fill") { if (xmlStreamAttr.hasAttribute("color")) { nodeColor = xmlStreamAttr.value("color").toString(); qCDebug(lcParser) << "Node color: " << nodeColor; } } else if (xml.name().toString() == "BorderStyle") { } else if (xml.name().toString() == "NodeLabel") { key_value = xml.readElementText(); // see if there's simple text after the StartElement if (!xml.hasError()) { qCDebug(lcParser) << "Node Label " << key_value; nodeLabel = key_value; } else { qCDebug(lcParser) << "Cannot read Node Label. There must be more elements nested here, continuing"; } } else if (xml.name().toString() == "Shape") { if (xmlStreamAttr.hasAttribute("type")) { nodeShape = xmlStreamAttr.value("type").toString(); qCDebug(lcParser) << "Node shape: " << nodeShape; } } } /** * @brief Reads edge graphics data and properties: path, linestyle,width, arrows, etc * @param xml */ void Parser::readGraphMLElementEdgeGraphics(QXmlStreamReader &xml) { qCDebug(lcParser) << "reading edge graphics/props, element name" << xml.name().toString(); qreal tempX = -1, tempY = -1, temp = -1; QString color, tempString; QXmlStreamAttributes xmlStreamAttr = xml.attributes(); if (xml.name().toString() == "Path") { if (xmlStreamAttr.hasAttribute("sx")) { conv_OK = false; tempX = xmlStreamAttr.value("sx").toString().toFloat(&conv_OK); if (conv_OK) bez_p1_x = tempX; else bez_p1_x = 0; } if (xmlStreamAttr.hasAttribute("sy")) { conv_OK = false; tempY = xmlStreamAttr.value("sy").toString().toFloat(&conv_OK); if (conv_OK) bez_p1_y = tempY; else bez_p1_y = 0; } if (xmlStreamAttr.hasAttribute("tx")) { conv_OK = false; tempX = xmlStreamAttr.value("tx").toString().toFloat(&conv_OK); if (conv_OK) bez_p2_x = tempX; else bez_p2_x = 0; } if (xmlStreamAttr.hasAttribute("ty")) { conv_OK = false; tempY = xmlStreamAttr.value("ty").toString().toFloat(&conv_OK); if (conv_OK) bez_p2_y = tempY; else bez_p2_y = 0; } qCDebug(lcParser) << "Edge Path control points: " << bez_p1_x << " " << bez_p1_y << " " << bez_p2_x << " " << bez_p2_y; } else if (xml.name().toString() == "LineStyle") { if (xmlStreamAttr.hasAttribute("color")) { edgeColor = xmlStreamAttr.value("color").toString(); qCDebug(lcParser) << "Edge color: " << edgeColor; } if (xmlStreamAttr.hasAttribute("type")) { edgeType = xmlStreamAttr.value("type").toString(); qCDebug(lcParser) << "Edge type: " << edgeType; } if (xmlStreamAttr.hasAttribute("width")) { temp = xmlStreamAttr.value("width").toString().toFloat(&conv_OK); if (conv_OK) edgeWeight = temp; else edgeWeight = 1.0; qCDebug(lcParser) << "Edge width: " << edgeWeight; } } else if (xml.name().toString() == "Arrows") { if (xmlStreamAttr.hasAttribute("source")) { tempString = xmlStreamAttr.value("source").toString(); qCDebug(lcParser) << "Edge source arrow type: " << tempString; } if (xmlStreamAttr.hasAttribute("target")) { tempString = xmlStreamAttr.value("target").toString(); qCDebug(lcParser) << "Edge target arrow type: " << tempString; } } else if (xml.name().toString() == "EdgeLabel") { key_value = xml.readElementText(); // see if there's simple text after the StartElement if (!xml.hasError()) { qCDebug(lcParser) << "Edge Label " << key_value; // probably there's more than simple text after StartElement edgeLabel = key_value; } else { qCDebug(lcParser) << "Can't read Edge Label. More elements nested ? Continuing with blank edge label...."; edgeLabel = ""; } } } /** * @brief Trivial call for unknown elements * @param xml */ void Parser::readGraphMLElementUnknown(QXmlStreamReader &xml) { Q_ASSERT(xml.isStartElement()); qCDebug(lcParser) << "unknown element found:" << xml.name().toString(); } /** * @brief Creates any missing node edges */ void Parser::createMissingNodeEdges() { qCDebug(lcParser) << "Creating missing node edges... "; int count = 0; if ((count = edgesMissingNodesHash.size()) > 0) { bool ok; edgeWeight = initEdgeWeight; edgeColor = initEdgeColor; edgeDirType = EdgeType::Directed; qCDebug(lcParser) << "edges to create " << count; QHash::const_iterator it = edgesMissingNodesHash.constBegin(); while (it != edgesMissingNodesHash.constEnd()) { qCDebug(lcParser) << "creating missing edge " << it.key() << " data " << it.value(); edgeMissingNodesList = (it.key()).split("===>"); if (!((edgeMissingNodesList[0]).isEmpty()) && !((edgeMissingNodesList[1]).isEmpty())) { source = nodeHash.value(edgeMissingNodesList[0], -666); target = nodeHash.value(edgeMissingNodesList[1], -666); if (source == -666 || target == -666) { // emit something that this node has not been declared continue; } edgeMissingNodesListData = (it.value()).split("|"); if (!edgeMissingNodesListData[0].isEmpty()) { edgeWeight = edgeMissingNodesListData[0].toInt(&ok, 10); } if (!edgeMissingNodesListData[1].isEmpty()) { edgeColor = edgeMissingNodesListData[1]; } if (!edgeMissingNodesListData[2].isEmpty()) { if ((edgeMissingNodesListData[2]).contains("2")) edgeDirType = EdgeType::Undirected; } qCDebug(lcParser) << "signaling to create new edge:" << source << "->" << target << " edgeDirType value " << edgeDirType; if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, edgeColor, edgeDirType, arrows, bezier, edgeLabel); } } ++it; } } else { qCDebug(lcParser) << "nothing to do"; } } SocNetV-3.7/src/parser/parser_pajek.cpp000066400000000000000000001010501524402210300201270ustar00rootroot00000000000000/** * @file parser_pajek.cpp * @brief Pajek parsers for SocNetV * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "parser.h" #include "global.h" SOCNETV_USE_NAMESPACE #include #include #include using namespace std; /** * @brief Parse a Pajek-formatted network from raw bytes. * * Supported constructs include (depending on the file contents): * - `*Network` header * - `*Vertices N` (node definitions with optional attributes) * - Tie sections such as `*Arcs`, `*Edges` * - Matrix-based relations: * - `*Matrix :k` * - `*Matrix :k "Label"` * - `*Matrix k: "Label"` * - empty-label cases like `*Matrix :k` / `*Matrix k:` * * Behavior: * - Creates nodes/edges by emitting the standard Parser signals. * - Detects directedness and relation structure according to Pajek sections. * - Populates totals (nodes/links) used by the loader and CLI harness. * - On failure, sets @c errorMessage and returns false. * * @param rawData Entire file contents (as read from disk). * @return true if parsing succeeds, false otherwise. */ bool Parser::parseAsPajek(const QByteArray &rawData) { qCDebug(lcParser) << "Parsing data as pajek formatted..."; QTextCodec *codec = QTextCodec::codecForName(m_textCodecName.toLatin1()); QString decodedData = codec->toUnicode(rawData); QTextStream ts(&decodedData); QString str, label, temp; nodeColor = ""; edgeColor = ""; nodeShape = ""; initEdgeLabel = QString(); QStringList lineElement; bool ok = false, intOk = false, check1 = false, check2 = false; bool has_arcs = false; bool nodes_flag = false, edges_flag = false, arcs_flag = false, arcslist_flag = false, matrix_flag = false; fileContainsNodeColors = false; fileContainsNodeCoords = false; fileContainsLinkColors = false; fileContainsLinkLabels = false; bool zero_flag = false; int i = 0, j = 0, miss = 0, source = -1, target = -1, nodeNum, colorIndex = -1; int coordIndex = -1, labelIndex = -1; unsigned long int fileLineNumber = 0; unsigned long int actualLineNumber = 0; int pos = -1, lastRelationIndex = 0; qreal weight = 1; QString relation; list listDummiesPajek; totalLinks = 0; totalNodes = 0; j = 0; // counts how many real nodes exist in the file miss = 0; // counts missing nodeNumbers. // if j + miss < nodeNum, it creates (nodeNum-miss) dummy nodes which are deleted in the end. relationsList.clear(); QRegularExpression myRegExp; while (!ts.atEnd()) { fileLineNumber++; str = ts.readLine(); str = str.simplified(); if (isComment(str)) { continue; } actualLineNumber++; qCDebug(lcParser) << "*** str:" << str; if (actualLineNumber == 1) { if (str.startsWith("graph", Qt::CaseInsensitive) || str.startsWith("digraph", Qt::CaseInsensitive) || str.startsWith("DL", Qt::CaseInsensitive) || str.startsWith("list", Qt::CaseInsensitive) || str.startsWith("graphml", Qt::CaseInsensitive) || str.startsWith("addNewRelation(relation); } lastRelationIndex = relationsList.size() - 1; if (lastRelationIndex > 0) { qCDebug(lcParser) << "last relation index:" << lastRelationIndex << "signaling to change to the last relation..."; if (m_parseSink) { m_parseSink->setRelation(lastRelationIndex); } i = 0; // reset the source node index } } continue; } else if (str.contains("*arcslist", Qt::CaseInsensitive)) { arcs_flag = false; edges_flag = false; arcslist_flag = true; matrix_flag = false; continue; } else if (str.contains("*matrix", Qt::CaseInsensitive)) { /* * Pajek *Matrix Header Import Policy * * IMPORT IS TOLERANT. * * The parser accepts common real-world variants, including: * * *Matrix :1 * *Matrix :1 "Label" * *Matrix 1: * *Matrix 1: "Label" * * The parser normalizes the relation name so that: * - Leading ":k", "k", or "k:" tokens do not pollute the label. * - Wrapping quotes are removed. * * Export canonicalization is handled in Graph::saveToPajekFormat(). * * Do NOT make the importer stricter to enforce canonical form. * Canonicalization belongs to export, not import. */ auto parsePajekMatrixHeader = [](const QString &line, int *outK, QString *outLabel) -> bool { // Accept: // *Matrix :1 // *Matrix :1 "Label" // *Matrix 1: // *Matrix 1: "Label" // // Output: // outK = 1-based matrix index // outLabel = cleaned label (no surrounding quotes), may be empty QString s = line.trimmed(); // Remove leading "*Matrix" (case-insensitive) if (!s.toLower().startsWith("*matrix")) return false; s = s.mid(QString("*matrix").size()).trimmed(); if (s.isEmpty()) return false; // Optional ":" before k if (s.startsWith(':')) s = s.mid(1).trimmed(); // Read integer k int pos = 0; while (pos < s.size() && s.at(pos).isDigit()) ++pos; if (pos == 0) return false; bool ok = false; const int k = s.left(pos).toInt(&ok); if (!ok || k <= 0) return false; s = s.mid(pos).trimmed(); // Optional ":" after k if (s.startsWith(':')) s = s.mid(1).trimmed(); // Whatever remains is the optional label QString label; if (!s.isEmpty()) label = normalizeQuotedIdentifier(s); // strip wrapping quotes etc *outK = k; *outLabel = label.trimmed(); return true; }; qCDebug(lcParser) << str; arcs_flag = false; edges_flag = false; arcslist_flag = false; matrix_flag = true; // check if row has label for matrix data, // and use it as relation name int k = 0; QString label; if (parsePajekMatrixHeader(str, &k, &label)) { // If label missing, use temporary name = index (for UI) relation = label.isEmpty() ? QString::number(k) : label; relationsList << relation; qCDebug(lcParser) << "added new relation" << relation << "to relationsList - signaling to add new relation"; if (m_parseSink) { m_parseSink->addNewRelation(relation); } lastRelationIndex = relationsList.size() - 1; if (lastRelationIndex > 0) { qCDebug(lcParser) << "last relation index:" << lastRelationIndex << "signaling to change to the last relation..."; if (m_parseSink) { m_parseSink->setRelation(lastRelationIndex); } } i = 0; // reset the source node index } continue; } /** READING NODES, THEN EDGES/ARCS */ if (!edges_flag && !arcs_flag && !arcslist_flag && !matrix_flag) { nodes_flag = true; nodeNum = lineElement[0].toInt(&intOk, 10); if (nodeNum == 0) { qCDebug(lcParser, "Node is zero numbered! Raising zero-start-flag - increasing nodenum"); zero_flag = true; } if (zero_flag) { nodeNum++; } if (lineElement.size() < 2) { label = lineElement[0]; randX = rand() % gwWidth; randY = rand() % gwHeight; nodeColor = initNodeColor; nodeShape = initNodeShape; } else { /** NODELABEL */ // If the label is quoted, extract the full string between the // quotes so that labels containing spaces or commas are preserved. int quoteStart = str.indexOf('"'); if (quoteStart != -1) { int quoteEnd = str.indexOf('"', quoteStart + 1); if (quoteEnd != -1) { label = str.mid(quoteStart + 1, quoteEnd - quoteStart - 1); str.remove(0, quoteEnd + 1); } else { label = str.mid(quoteStart + 1).trimmed(); str.clear(); } } else { label = lineElement[1]; str.remove(0, str.lastIndexOf(label) + label.size()); } /** NODESHAPE: There are five possible . */ if (str.contains("Ellipse", Qt::CaseInsensitive)) nodeShape = "ellipse"; else if (str.contains("circle", Qt::CaseInsensitive)) nodeShape = "circle"; else if (str.contains("box", Qt::CaseInsensitive)) nodeShape = "box"; else if (str.contains("star", Qt::CaseInsensitive)) nodeShape = "star"; else if (str.contains("triangle", Qt::CaseInsensitive)) nodeShape = "triangle"; else if (str.contains("diamond", Qt::CaseInsensitive)) nodeShape = "diamond"; else nodeShape = initNodeShape; /** NODECOLORS */ // if there is an "ic" tag, a specific NodeColor for this node follows... if (str.contains("ic", Qt::CaseInsensitive)) { for (int c = 0; c < lineElement.size(); c++) { if (lineElement[c] == "ic") { // the colourname is at c+1 position. nodeColor = lineElement[c + 1]; fileContainsNodeColors = true; break; } } if (nodeColor.contains(".")) nodeColor = initNodeColor; if (nodeColor.startsWith("RGB")) nodeColor.replace(0, 3, "#"); qCDebug(lcParser) << " \n\n PAJEK color " << nodeColor; } else { // there is no nodeColor. Use the default fileContainsNodeColors = false; nodeColor = initNodeColor; } /**READ NODE COORDINATES */ if (str.contains(".", Qt::CaseInsensitive)) { for (int c = 0; c < lineElement.size(); c++) { temp = lineElement.at(c); if ((coordIndex = temp.indexOf(".", Qt::CaseInsensitive)) != -1) { if (lineElement.at(c - 1) == "ic") continue; // pajek declares colors with numbers! if (!temp[coordIndex - 1].isDigit()) continue; // needs 0.XX if (c + 1 == lineElement.size()) { // first coord zero, i.e: 0 0.455 randX = lineElement.at(c - 1).toDouble(&check1); randY = temp.toDouble(&check2); } else { randX = temp.toDouble(&check1); randY = lineElement[c + 1].toDouble(&check2); } if (check1 && check2) { randX = randX * gwWidth; randY = randY * gwHeight; fileContainsNodeCoords = true; } if (randX <= 0.0 || randY <= 0.0) { randX = rand() % gwWidth; randY = rand() % gwHeight; } break; } } } else { fileContainsNodeCoords = false; randX = rand() % gwWidth; randY = rand() % gwHeight; } } // START NODE CREATION qCDebug(lcParser) << "Creating node numbered " << nodeNum << " Real nodes count (j)= " << j + 1; j++; // Controls the real number of nodes. // If the file misses some nodenumbers then we create dummies and delete them afterwards! if (j + miss < nodeNum) { qCDebug(lcParser) << "There are " << j << " nodes but this node has number" << nodeNum; for (int num = j; num < nodeNum; num++) { qCDebug(lcParser) << "Signaling to create new dummy node" << num << "at" << QPointF(randX, randY); if (m_parseSink) { m_parseSink->createNode(num, initNodeSize, nodeColor, initNodeNumberColor, initNodeNumberSize, label, lineElement[3], initNodeLabelSize, QPointF(randX, randY), nodeShape, QString()); } listDummiesPajek.push_back(num); miss++; } } else if (j > nodeNum) { qCDebug(lcParser, "Error: This Pajek net declares this node with nodeNumber smaller than previous nodes. Aborting"); errorMessage = tr("Invalid Pajek-formatted file. It declares a node with " "nodeNumber smaller than previous nodes."); return false; } qCDebug(lcParser) << "Signaling to create new node" << nodeNum << "at" << QPointF(randX, randY); if (m_parseSink) { m_parseSink->createNode( nodeNum, initNodeSize, nodeColor, initNodeNumberColor, initNodeNumberSize, label, initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), nodeShape, QString()); } initNodeColor = nodeColor; } else { // NODES CREATED. CREATE EDGES/ARCS NOW. // first check that all nodes are already created if (j && j != totalNodes) { // if there were more or less nodes than the file declared qCDebug(lcParser) << "*** WARNING ***: The Pajek file declares " << totalNodes << " nodes, but I found " << j << " nodes...."; totalNodes = j; } else if (j == 0) { // if there were no nodes at all, we need to create them now. qCDebug(lcParser) << "The Pajek file declares " << totalNodes << " but I didn't found any nodes. I will create them...."; for (int num = j + 1; num <= totalNodes; num++) { randX = rand() % gwWidth; randY = rand() % gwHeight; qCDebug(lcParser) << "Signaling to create new node" << num << "at random pos:" << QPointF(randX, randY); if (m_parseSink) { m_parseSink->createNode( num, initNodeSize, initNodeColor, initNodeNumberColor, initNodeNumberSize, QString::number(i), initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), initNodeShape, QString(), false); } } j = totalNodes; } if (edges_flag && !arcs_flag) { /**EDGES */ qCDebug(lcParser, "==== Reading edges ===="); qCDebug(lcParser) << lineElement; source = lineElement[0].toInt(&ok, 10); target = lineElement[1].toInt(&ok, 10); if (source == 0 || target == 0) { errorMessage = tr("Invalid Pajek-formatted file. The file declares an edge " "with a zero source or target nodeNumber. " "However, each node should have a nodeNumber > 0."); return false; } else if (source < 0 && target > 0) { // weights come first... edgeWeight = lineElement[0].toDouble(&ok); source = lineElement[1].toInt(&ok, 10); if (lineElement.size() > 2) { target = lineElement[2].toInt(&ok, 10); } else { target = lineElement[1].toInt(&ok, 10); // self link } } else if (lineElement.size() > 2) { edgeWeight = lineElement[2].toDouble(&ok); } else { edgeWeight = 1.0; } if (lineElement.contains("c", Qt::CaseSensitive)) { fileContainsLinkColors = true; myRegExp.setPattern("[c]"); colorIndex = lineElement.indexOf(myRegExp, 0) + 1; if (colorIndex >= lineElement.size()) edgeColor = initEdgeColor; else edgeColor = lineElement[colorIndex]; if (edgeColor.contains(".")) edgeColor = initEdgeColor; } else { edgeColor = initEdgeColor; } if (lineElement.contains("l", Qt::CaseSensitive)) { qCDebug(lcParser, "file with link labels"); fileContainsLinkLabels = true; myRegExp.setPattern("[l]"); labelIndex = lineElement.indexOf(myRegExp, 0) + 1; if (labelIndex >= lineElement.size()) edgeLabel = initEdgeLabel; else edgeLabel = lineElement[labelIndex]; if (edgeLabel.contains(".")) edgeLabel = initEdgeLabel; qCDebug(lcParser) << " edge label " << edgeLabel; } else { edgeLabel = initEdgeLabel; } arrows = false; bezier = false; qCDebug(lcParser) << "EDGES: signaling to create new edge:" << source << " - " << target; if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, edgeColor, EdgeType::Undirected, arrows, bezier, edgeLabel); } totalLinks = totalLinks + 2; } // end if EDGES else if (!edges_flag && arcs_flag) { /** ARCS */ source = lineElement[0].toInt(&ok, 10); target = lineElement[1].toInt(&ok, 10); if (source == 0 || target == 0) { errorMessage = tr("Invalid Pajek-formatted file. The file declares arc " "with a zero source or target nodeNumber. " "However, each node should have a nodeNumber > 0."); return false; // i -->(i-1) internally } else if (source < 0 && target > 0) { // weights come first... edgeWeight = lineElement[0].toDouble(&ok); source = lineElement[1].toInt(&ok, 10); if (lineElement.size() > 2) { target = lineElement[2].toInt(&ok, 10); } else { target = lineElement[1].toInt(&ok, 10); // self link } } else if (lineElement.size() > 2) { edgeWeight = lineElement[2].toDouble(&ok); } else { edgeWeight = 1.0; } if (lineElement.contains("c", Qt::CaseSensitive)) { myRegExp.setPattern("[c]"); edgeColor = lineElement.at(lineElement.indexOf(myRegExp, 0) + 1); fileContainsLinkColors = true; } else { edgeColor = initEdgeColor; } if (lineElement.contains("l", Qt::CaseSensitive)) { qCDebug(lcParser, "file with link labels"); fileContainsLinkLabels = true; myRegExp.setPattern("[l]"); labelIndex = lineElement.indexOf(myRegExp, 0) + 1; if (labelIndex >= lineElement.size()) edgeLabel = initEdgeLabel; else edgeLabel = lineElement.at(labelIndex); // if (edgeLabel.contains (".") ) edgeLabel=initEdgeLabel; qCDebug(lcParser) << " edge label " << edgeLabel; } else { edgeLabel = initEdgeLabel; } arrows = true; bezier = false; has_arcs = true; qCDebug(lcParser) << "ARCS: signaling to create new arc:" << source << "->" << target << "with weight " << weight; if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, edgeColor, EdgeType::Directed, arrows, bezier, edgeLabel); } totalLinks++; } // else if ARCS else if (arcslist_flag) { /** ARCSlist */ if (lineElement[0].startsWith("-")) lineElement[0].remove(0, 1); source = lineElement[0].toInt(&ok, 10); fileContainsLinkColors = false; edgeColor = initEdgeColor; has_arcs = true; arrows = true; bezier = false; for (int index = 1; index < lineElement.size(); index++) { target = lineElement.at(index).toInt(&ok, 10); qCDebug(lcParser) << "ARCS LIST: signaling to create new arc:" << source << "->" << target << "with weight " << weight; if (m_parseSink) { m_parseSink->createEdge(source, target, edgeWeight, edgeColor, EdgeType::Directed, arrows, bezier); } totalLinks++; } } // else if ARCSLIST else if (matrix_flag) { /** matrix */ i++; source = i; fileContainsLinkColors = false; edgeColor = initEdgeColor; has_arcs = true; arrows = true; bezier = false; for (target = 0; target < lineElement.size(); target++) { if (lineElement.at(target) != "0") { edgeWeight = lineElement.at(target).toFloat(&ok); qCDebug(lcParser) << " MATRIX: signaling to create new arc" << source << "->" << target + 1 << "with weight" << weight; if (m_parseSink) { m_parseSink->createEdge(source, target + 1, edgeWeight, edgeColor, EdgeType::Directed, arrows, bezier); } totalLinks++; } } } // else if matrix } // end if BOTH ARCS AND EDGES } // end WHILE if (j == 0) { errorMessage = tr("Invalid Pajek-formatted file. Could not find node declarations in this file."); return false; } qCDebug(lcParser, "Removing all dummy nodes, if any"); if (listDummiesPajek.size() > 0) { qCDebug(lcParser, "Trying to delete the dummies now"); for (list::iterator it = listDummiesPajek.begin(); it != listDummiesPajek.end(); it++) { if (m_parseSink) { m_parseSink->removeDummyNode(*it); } } } if (relationsList.size() == 0) { if (m_parseSink) { m_parseSink->addNewRelation(networkName); } } qCDebug(lcParser) << "Clearing temporary dummies and relations list"; listDummiesPajek.clear(); relationsList.clear(); qCDebug(lcParser) << "signaling to change to the first relation..."; if (m_parseSink) { m_parseSink->setRelation(0); } if (has_arcs) { edgeDirType = EdgeType::Directed; } else { edgeDirType = EdgeType::Undirected; } qCDebug(lcParser) << "Finished OK. Returning."; return true; } /** * @brief Normalizes a quoted identifier from external network formats. * * Some formats (e.g. Pajek) use quotes in headers as syntactic delimiters, such as: * ``` * *Matrix 9: "star" * *Matrix :9 "star" * *Matrix :9 'star' * ``` * * Quotes are part of the file syntax and must not become part of the internal * relation name. This function: * * - trims surrounding whitespace * - removes a single pair of wrapping double quotes OR single quotes, if present * - normalizes doubled quotes inside quoted strings: * "" -> " * '' -> ' * * It does NOT collapse internal whitespace. * * @param s Raw identifier substring extracted from the file. * @return Clean identifier suitable for internal storage. */ QString Parser::normalizeQuotedIdentifier(const QString &s) { QString out = s.trimmed(); // Strip one pair of wrapping quotes (format delimiters) if (out.size() >= 2) { const QChar first = out.front(); const QChar last = out.back(); if ((first == '"' && last == '"') || (first == '\'' && last == '\'')) out = out.mid(1, out.size() - 2); } // Normalize doubled quotes inside quoted strings out.replace("\"\"", "\""); out.replace("''", "'"); return out.trimmed(); } SocNetV-3.7/src/qss/000077500000000000000000000000001524402210300142725ustar00rootroot00000000000000SocNetV-3.7/src/qss/default.qss000066400000000000000000000364321524402210300164560ustar00rootroot00000000000000/* * SocNetV Style Sheet * See: https://doc.qt.io/qt-6/stylesheet-syntax.html */ /* * General Styles * Apply to all widgets, including the main window, dialogs, labels, buttons, etc. */ QWidget { font-size: 100%; } QMainWindow, QWidget, QMenuBar, QMenu, QDialog, QToolBar, QGroupBox, QLabel, QPushButton, QStatusBar { background-color: #edecec; color: #222; font-size: 100%; } QMainWindow::separator { background: #d4d4d4; width: 3px; height: 3px; } QMainWindow::separator:hover { background: #03a9f4; } QGridLayout { margin:0; padding:0; } /* * Menu Bar and Menus */ QMenuBar { background-color: #edecec; padding: 5px 0; border: 0px none; border-bottom: 1px solid #ddd; } QMenuBar::item { spacing: 25px; /* spacing between menu bar items */ padding: 5px 10px; background: transparent; border-radius: 0px; color: #000; } QMenuBar::item:selected { /* when selected using mouse or keyboard */ background: #03a9f4; color: #000; } QMenu { background-color: #edecec; color: #222; margin: 0; /* no spacing around the menu */ padding: 0px 10px ; border: 1px solid rgba(0, 0, 0, 0.2); } QMenu::item { padding: 5px 25px 5px 25px; border: 1px solid transparent; /* reserve space for selection border */ } QMenu::item:selected { border-color: rgba(0, 0, 0, 0.2); background: #03a9f4; color: #000; } QMenu::item:disabled { background-color: #eee; color: #787878; } QMenu::separator { height: 1px; background: rgba(0, 0, 0, 0.1); } /* * Tool Bar and Status Bar */ QToolBar { background-color: #edecec; opacity: 200; border: 0 none; border-bottom: 1px solid rgba(0, 0, 0, 0.1); color: #666; padding: 0px 5px; } QToolBar::separator{ height: 5px; width: 15px; } QStatusBar { background-color: #edecec; padding: 0; border: 1px solid #ddd; } /** * Frames and Labels */ QFrame { } QFrame#graphicsWidget { border: 1px solid #babdbe; } QGroupBox#rightPanel QFrame { border: 0 none; } /* A QLabel is a QFrame ... */ QLabel { border: none; padding: 0; background: none; } /* A QToolTip is a QLabel ... */ QToolTip { border: 1px solid rgba(0, 0, 0, 0.1); padding: 5px; border-radius: 2px; opacity: 300; min-width: 300px; max-width: 600px; font-size: 100%; background: #ffff65; color: #333; } /* * Progress Dialog and Progress Bar */ QProgressDialog { min-width: 400px; } QProgressBar { background-color: transparent; color: #333; border: 1px solid #babdbe; text-align: center; } QProgressBar::chunk { background-color: #007ac1;; width: 20px; } /* * Charts */ QChart, QChartView { background-color:transparent; margin:0; padding:0; } /* * Group Boxes */ QGroupBox { border: 0 none; border-top: 1px solid rgba(0, 0, 0, 0.1); margin:0; margin-top: 18px; /* leave space at the top for the title */ padding:0px; /* border: 1px solid #ddd; */ } QGroupBox::title { subcontrol-position: top left; subcontrol-origin: margin; left: 0px; padding: 0px 0px 0px 0px; font-style:italic; color: #444; font-size: 100%; /* line-height: 16px; */ margin: 2px 0 0 0; } QGroupBox#leftPanel { padding:0; } /* * Buttons */ QPushButton { background: #fff; color: #222; border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 2px; padding: 5px 16px; min-width: 100px; min-height: 2.5ex; text-align: center; } QPushButton:hover { background-color: #eaeaea; } QPushButton:checked, QPushButton:pressed { background-color: #007ac1; color: #fff; } /* make the default button prominent */ QPushButton:default { background-color: #007ac1; color:#fff; border: 1px solid rgba(0, 0, 0, 0.2); } QProgressDialog QPushButton, QProgressDialog QPushButton:default { margin-top: 20px; background-color: #fff; color: #222; } QPushButton:disabled { background-color: #32414B; border: 1px solid #32414B; color: #787878; border-radius: 4px; padding: 3px; } QPushButton#removePropertyBtn { background-color:#de0000; color: #fff; min-height: 1.5ex; padding: 3px 0px; margin:0; } QPushButton#removePropertyBtn:disabled { background-color: #aaa; border: 1px solid #aaa; color: #eee; } QPushButton#removePropertyBtn:hover { background-color:#ff0000; } QPushButton#addPropertyBtn { background-color: #fff; color: #222; min-width: 60px; min-height: 1.5ex; padding: 3px 8px; margin: 0; } QToolButton { border:1px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(255, 255, 255, 0.3); border-left:1px solid rgba(255, 255, 255, 0.3); min-width: 20px; } QToolBar QToolButton { border:0 none; min-width: 20px; padding: 5px 10px; border-radius:5px; } QToolButton:hover { background-color: #e2e2e2; } QToolButton:pressed { background-color: #d2d2d2; border: 1px solid rgba(255, 255, 255, 0.3); border-top: 1px solid rgba(0, 0, 0, 0.1); border-left:1px solid rgba(0, 0, 0, 0.1); } QToolButton:checked{ background-color: #d2d2d2; } QToolButton:disabled{ background-color: #eee; color: #787878; } /** * Combo Boxes */ QComboBox { background-color: #fafafa; border: 1px solid #ddd; border-radius: 4px; selection-background-color: #67daff; selection-color: #000; padding-top: 2px; padding-bottom: 2px; padding-left: 4px; padding-right: 4px; min-height: 22px; } QComboBox:focus, QComboBox:hover { border: 1px solid #babdbe; } QComboBox:hover { background-color: #eaeaea; } /* shift the text when the popup opens */ QComboBox:on { } QComboBox:disabled { background-color: #eee; color: #787878; } QGroupBox#leftPanel QComboBox { min-width: 120px; max-width: 120px; min-height: 20px; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 15px; border-left-width: 0px; border-left-color: transparent; border-left-style: solid; /* just a single line */ } QComboBox::down-arrow { image: url(:images/arrow_down_48px.svg); width: 16px; height: 16px; color: #ddd; } QComboBox QAbstractItemView { border: 0 none; padding: 0; margin:0; min-width:200px; background-color: #fff; } /* * Scroll Bars */ QScrollBar { } QScrollBar:horizontal { height: 10px; margin: 0; border: 1px solid #d2d2d2; border-radius: 0px; background-color: #e2e2e2; } QScrollBar:vertical { width: 10px; margin: 0; border: 1px solid #d2d2d2; border-radius: 0px; background-color: #e2e2e2; } QScrollBar::handle:horizontal { background-color: #787878; border: 1px solid #32414B; border-radius: 4px; min-width: 5px; } QScrollBar::handle:vertical { background-color: #787878; border: 1px solid #32414B; border-radius: 4px; min-width: 5px; } QScrollBar::handle:horizontal:hover { background-color: #03a9f4; border: 1px solid #03a9f4; } QScrollBar::handle:vertical:hover { background-color: #03a9f4; border: 1px solid #03a9f4; } QScrollBar::add-line:horizontal { margin: 0; border-image: none; background: transparent; border: 0 none; width: 0; height: 0; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { margin: 0; border-image: none; background: transparent; border: 0 none; height: 0; width: 0; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar::sub-line:vertical { margin: 0; border-image: none; background: transparent; border: 0 none; height: 0px; width: 0px; subcontrol-position: top; subcontrol-origin: margin; } QScrollBar::add-line:vertical { margin: 0; border-image: none; background: transparent; border: 0 none; height: 0px; width: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } /* * Sliders */ QSlider::groove:horizontal { border: 1px solid #999999; height: 2px; /* auto expands to the size of the slider by default */ background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #e4e4e4, stop:0.5 #b4b4b4, stop:1 #e4e4e4); margin: 1px 0; } QSlider::handle:horizontal { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f); border: 1px solid #5c5c5c; width: 18px; height: 10px; margin: -3px 0; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */ border-radius: 2px; } QSlider::groove:vertical{ border: 1px solid #babdbe; width: 2px; /* auto expands to the size of the slider by default */ background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B1B1B1, stop:1 #e4e4e4); margin: 3px 0; } QSlider::handle:vertical { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f); border: 1px solid #5c5c5c; width : 2px; height: 20px; margin: 0 -3px; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */ border-radius: 2px; } QSlider::handle:vertical:hover, QSlider::handle:horizontal:hover{ border: 1px solid #5c5c5c; background: #03a9f4; } /* * Tab Widgets */ QTabWidget::pane { /* The tab widget frame */ border:0 none; border-top: 1px solid #babdbe; } QTabWidget::tab-bar { left: 5px; /* move to the right by 5px */ } /* Style the tab using the tab sub-control. Note that it reads QTabBar _not_ QTabWidget */ QTabBar::tab { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #E1E1E1, stop: 0.4 #DDDDDD, stop: 0.5 #D8D8D8, stop: 1.0 #babdbe); border: 1px solid #babdbe; border-bottom-color: transparent; /* same as the pane color */ border-bottom: 0 none; border-top-left-radius: 4px; border-top-right-radius: 4px; min-width: 8ex; padding: 2px; } QTabBar::tab:selected, QTabBar::tab:hover { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #fafafa, stop: 0.4 #f4f4f4, stop: 0.5 #e7e7e7, stop: 1.0 #fafafa); } QTabBar::tab:selected { border-color: #9B9B9B; border-bottom-color: transparent; /* same as pane color */ } QTabBar::tab:!selected { margin-top: 3px; /* make non-selected tabs look smaller */ } /* === Checkboxes / RadioButtons (SVG indicators) === */ QCheckBox, QRadioButton { spacing: 6px; } QCheckBox::indicator, QRadioButton::indicator { width: 16px; height: 16px; } /* CheckBox states */ QCheckBox::indicator:unchecked { image: url(:/images/controls/checkbox_unchecked.svg); } QCheckBox::indicator:unchecked:hover { image: url(:/images/controls/checkbox_unchecked_hover.svg); } QCheckBox::indicator:unchecked:pressed { image: url(:/images/controls/checkbox_unchecked_pressed.svg); } QCheckBox::indicator:checked { image: url(:/images/controls/checkbox_checked.svg); } QCheckBox::indicator:checked:hover { image: url(:/images/controls/checkbox_checked_hover.svg); } QCheckBox::indicator:checked:pressed { image: url(:/images/controls/checkbox_checked_pressed.svg); } QCheckBox:disabled { color: #787878; } QCheckBox::indicator:disabled { opacity: 0.6; } /* RadioButton states */ QRadioButton::indicator:unchecked { image: url(:/images/controls/radio_unchecked.svg); } QRadioButton::indicator:unchecked:hover { image: url(:/images/controls/radio_unchecked_hover.svg); } QRadioButton::indicator:unchecked:pressed { image: url(:/images/controls/radio_unchecked_pressed.svg); } QRadioButton::indicator:checked { image: url(:/images/controls/radio_checked.svg); } QRadioButton::indicator:checked:hover { image: url(:/images/controls/radio_checked_hover.svg); } QRadioButton::indicator:checked:pressed { image: url(:/images/controls/radio_checked_pressed.svg); } QRadioButton:disabled { color: #787878; } QRadioButton::indicator:disabled { opacity: 0.6; } /* * Line Edits, Spin Boxes, Text Edits, List Views, Plain Text Edits, Double Spin Boxes, and Table Widgets */ QLineEdit, QSpinBox, QTextEdit, QListView, QPlainTextEdit, QDoubleSpinBox, QTableWidget { background-color: #fafafa; color: #333; selection-color: #fff; selection-background-color: #0277BD; border: 0 none; border: 1px solid #ddd; border-radius: 4px; } QLineEdit, QTextEdit { min-height: 20px; } QTableWidget:focus,QToolButton:focus, QDoubleSpinBox:focus, QDoubleSpinBox:hover, QSpinBox:focus, QSpinBox:hover, QLineEdit:focus, QLineEdit:hover, QPlainTextEdit:focus, QPlainTextEdit:hover, QTextEdit:focus, QTextEdit:hover{ border: 1px solid #babdbe; } /* Nice to have the background color change when hovered. */ QPlainTextEdit:hover, QDoubleSpinBox:hover,QSpinBox:hover, QCheckBox:hover { background-color: #e2e2e2; } QSpinBox:disabled, QDoubleSpinBox:disabled, QTextEdit:disabled, QPlainTextEdit:disabled, QLineEdit:disabled { background-color: #eee; color: #787878; } /* ----------------------------------------------------------------------- * Filter Bar (#219) * ----------------------------------------------------------------------- */ FilterBarWidget { background-color: #e8eaf6; border-bottom: 1px solid #c5cae9; min-height: 28px; max-height: 36px; } FilterBarWidget QFrame#FilterChip { background-color: #ffffff; border: 1px solid #9fa8da; border-radius: 11px; } FilterBarWidget QLabel#ChipLabel { background: transparent; color: #1a237e; font-size: 90%; padding: 0 2px; } FilterBarWidget QToolButton#ChipCloseButton { background: transparent; border: none; border-radius: 7px; padding: 1px; } FilterBarWidget QToolButton#ChipCloseButton:hover { background-color: #c5cae9; } FilterBarWidget QToolButton#ChipCloseButton:disabled { opacity: 0.3; } FilterBarWidget QPushButton#ClearAllButton { background: transparent; color: #3949ab; border: 1px solid #9fa8da; border-radius: 4px; padding: 2px 8px; font-size: 90%; min-height: 0; } FilterBarWidget QPushButton#ClearAllButton:hover { background-color: #c5cae9; border-color: #5c6bc0; } /* ----------------------------------------------------------------------- * Data Table toolbar (#228) * Compact button and search-box appearance inside GraphTableWidget so the * toolbar row stays slim while the global QPushButton style (padding 5px 16px, * min-width 100px) is left untouched everywhere else. * ----------------------------------------------------------------------- */ GraphTableWidget QPushButton { padding: 1px 5px; min-width: 0; font-size: 11px; } GraphTableWidget QLineEdit { font-size: 11px; min-height: 0; } /* ----------------------------------------------------------------------- * Statistics Panel β€” section headers and chart toggle (#234) * ----------------------------------------------------------------------- */ QPushButton#sectionToggleBtn { font-size: 10px; font-weight: bold; color: #666; text-align: left; border: none; border-bottom: 1px solid #ddd; padding: 2px 0; background: transparent; min-width: 0; } QPushButton#sectionToggleBtn:hover { color: #03a9f4; border-bottom-color: #03a9f4; } SocNetV-3.7/src/texteditor.cpp000077500000000000000000000250451524402210300163740ustar00rootroot00000000000000/** * @file texteditor.cpp * @brief Implements the TextEditor class for editing and managing text-based network data files. * @details This file contains the logic for displaying, editing, and saving raw network data in text format within the SocNetV application. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include #include #include "texteditor.h" Q_LOGGING_CATEGORY(lcTextEditor, "socnetv.texteditor") TextEditor::TextEditor(const QString &fileName, QWidget *parent, const bool &format) : QMainWindow(parent), formatHTML(format) { qCDebug(lcTextEditor, "TextEditor()"); textEdit = new QTextEdit; setCentralWidget(textEdit); createActions(); createMenus(); createToolBars(); createStatusBar(); readSettings(); connect(textEdit->document(), SIGNAL(contentsChanged()), this, SLOT(documentWasModified())); resize( 1024,768 ); setWindowState(Qt::WindowMaximized|Qt::WindowActive); if (!fileName.isEmpty()) loadFile(fileName); else setCurrentFile(""); } void TextEditor::closeEvent(QCloseEvent *event) { if (maybeSave()) { writeSettings(); event->accept(); } else { event->ignore(); } } void TextEditor::newFile() { if (maybeSave()) { textEdit->clear(); setCurrentFile(""); } } void TextEditor::open() { if (maybeSave()) { QString fileName = QFileDialog::getOpenFileName(this); if (!fileName.isEmpty()) loadFile(fileName); } } bool TextEditor::save() { if (curFile.isEmpty()) { return saveAs(); } else { return saveFile(curFile); } } bool TextEditor::saveAs() { QString fileName = QFileDialog::getSaveFileName(this, tr("Save file"), curFile); if (fileName.isEmpty()) return false; return saveFile(fileName); } void TextEditor::documentWasModified() { setWindowModified(textEdit->document()->isModified()); } void TextEditor::createActions() { newAct = new QAction(QIcon(":/images/new.png"), tr("&New"), this); newAct->setShortcut(tr("Ctrl+N")); newAct->setStatusTip(tr("Create a new file")); connect(newAct, SIGNAL(triggered()), this, SLOT(newFile())); openAct = new QAction(QIcon(":/images/open.png"), tr("&Open..."), this); openAct->setShortcut(tr("Ctrl+O")); openAct->setStatusTip(tr("Open an existing file")); connect(openAct, SIGNAL(triggered()), this, SLOT(open())); saveAct = new QAction(QIcon(":/images/save.png"), tr("&Save"), this); saveAct->setShortcut(tr("Ctrl+S")); saveAct->setStatusTip(tr("Save the document to disk")); connect(saveAct, SIGNAL(triggered()), this, SLOT(save())); saveAsAct = new QAction(tr("Save &As..."), this); saveAsAct->setStatusTip(tr("Save the document under a new name")); connect(saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs())); exitAct = new QAction(tr("E&xit"), this); exitAct->setShortcut(tr("Ctrl+Q")); exitAct->setStatusTip(tr("Exit the application")); connect(exitAct, SIGNAL(triggered()), this, SLOT(close())); cutAct = new QAction(QIcon(":/images/cut.png"), tr("Cu&t"), this); cutAct->setShortcut(tr("Ctrl+X")); cutAct->setStatusTip(tr("Cut the current selection's contents to the " "clipboard")); connect(cutAct, SIGNAL(triggered()), textEdit, SLOT(cut())); copyAct = new QAction(QIcon(":/images/copy.png"), tr("&Copy"), this); copyAct->setShortcut(tr("Ctrl+C")); copyAct->setStatusTip(tr("Copy the current selection's contents to the " "clipboard")); connect(copyAct, SIGNAL(triggered()), textEdit, SLOT(copy())); pasteAct = new QAction(QIcon(":/images/paste.png"), tr("&Paste"), this); pasteAct->setShortcut(tr("Ctrl+V")); pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current " "selection")); connect(pasteAct, SIGNAL(triggered()), textEdit, SLOT(paste())); aboutAct = new QAction(tr("&About"), this); aboutAct->setStatusTip(tr("Show the application's About box")); connect(aboutAct, SIGNAL(triggered()), this, SLOT(about())); aboutQtAct = new QAction(tr("About &Qt"), this); aboutQtAct->setStatusTip(tr("Show the Qt library's About box")); connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt())); cutAct->setEnabled(false); copyAct->setEnabled(false); connect(textEdit, SIGNAL(copyAvailable(bool)), cutAct, SLOT(setEnabled(bool))); connect(textEdit, SIGNAL(copyAvailable(bool)), copyAct, SLOT(setEnabled(bool))); } void TextEditor::createMenus() { fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(newAct); fileMenu->addAction(openAct); fileMenu->addAction(saveAct); fileMenu->addAction(saveAsAct); fileMenu->addSeparator(); fileMenu->addAction(exitAct); editMenu = menuBar()->addMenu(tr("&Edit")); editMenu->addAction(cutAct); editMenu->addAction(copyAct); editMenu->addAction(pasteAct); menuBar()->addSeparator(); helpMenu = menuBar()->addMenu(tr("&Help")); helpMenu->addAction(aboutAct); helpMenu->addAction(aboutQtAct); } void TextEditor::createToolBars() { fileToolBar = addToolBar(tr("File")); fileToolBar->addAction(newAct); fileToolBar->addAction(openAct); fileToolBar->addAction(saveAct); editToolBar = addToolBar(tr("Edit")); editToolBar->addAction(cutAct); editToolBar->addAction(copyAct); editToolBar->addAction(pasteAct); } void TextEditor::createStatusBar() { statusBar()->showMessage(tr("Ready")); } void TextEditor::readSettings() { QSettings settings("SocNetV", "TextEditor"); QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint(); QSize size = settings.value("size", QSize(400, 400)).toSize(); resize(size); move(pos); } void TextEditor::writeSettings() { QSettings settings("SocNetV ", "TextEditor"); settings.setValue("pos", pos()); settings.setValue("size", size()); } bool TextEditor::maybeSave() { if (textEdit->document()->isModified()) { QMessageBox::StandardButton ret; ret = QMessageBox::warning(this, tr("TextEditor"), tr("The document has been modified.\n" "Do you want to save your changes?"), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); if (ret == QMessageBox::Save) return save(); else if (ret == QMessageBox::Cancel) return false; } return true; } void TextEditor::loadFile(const QString &fileName) { QFile file(fileName); if (!file.open(QFile::ReadOnly | QFile::Text)) { QMessageBox::warning(this, tr("SocNetV Editor"), tr("Cannot read file %1:\n%2.") .arg(fileName) .arg(file.errorString())); return; } QTextStream in(&file); QApplication::setOverrideCursor(Qt::WaitCursor); if (formatHTML) textEdit->setHtml(in.readAll()); else textEdit->setPlainText(in.readAll()); QApplication::restoreOverrideCursor(); setCurrentFile(fileName); statusBar()->showMessage(tr("File loaded"), 2000); file.close(); } bool TextEditor::saveFile(const QString &fileName) { QFile file(fileName); if (!file.open(QFile::WriteOnly | QFile::Text)) { QMessageBox::warning(this, tr("SocNetV Editor"), tr("Cannot write file %1:\n%2.") .arg(fileName) .arg(file.errorString())); return false; } QTextStream outText(&file); QApplication::setOverrideCursor(Qt::WaitCursor); if (formatHTML) outText << textEdit->toHtml(); else outText << textEdit->toPlainText(); QApplication::restoreOverrideCursor(); setCurrentFile(fileName); statusBar()->showMessage(tr("File saved"), 2000); file.close(); return true; } void TextEditor::setCurrentFile(const QString &fileName) { curFile = fileName; textEdit->document()->setModified(false); setWindowModified(false); QString shownName; if (curFile.isEmpty()) shownName = "untitled.txt"; else shownName = strippedName(curFile); setWindowTitle(tr("%1[*] - %2").arg(shownName).arg(tr("SocNetV Editor"))); } QString TextEditor::strippedName(const QString &fullFileName) { return QFileInfo(fullFileName).fileName(); } //bool TextEditor::canInsertFromMimeData( const QMimeData *source ) const // { // if (source->hasImage()) // return true; // else // return QTextEdit::canInsertFromMimeData(source); // } //void TextEditor::insertFromMimeData( const QMimeData *source ) //{ // if (source->hasImage()) // { // QImage image = qvariant_cast(source->imageData()); // QTextCursor cursor = this->textCursor(); // QTextDocument *document = this->document(); // document->addResource(QTextDocument::ImageResource, QUrl("image"), image); // cursor.insertImage("image"); // } //} void TextEditor::about() { QMessageBox::about( this, "SocNetV Editor", " Part of Social Network Visualizer" "

Developer:
Dimitris B. Kalamaras
" "
email: dimitris.kalamaras@gmail.com" "

Note: This text editor was adapted from Trolltech's application example." "

This program is free software; you can redistribute it and/or modify" "
it under the terms of the GNU General Public License as published by" "
the Free Software Foundation; either version 3 of the License, or" "
(at your option) any later version.

" "

This program is distributed in the hope that it will be useful," "
but WITHOUT ANY WARRANTY; without even the implied warranty of" "
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the" "
GNU General Public License for more details.

" "

You should have received a copy of the GNU General Public License" "
along with this program; if not, write to the Free Software" "
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"); } SocNetV-3.7/src/texteditor.h000077500000000000000000000036121524402210300160350ustar00rootroot00000000000000/** * @file texteditor.h * @brief Declares the TextEditor class for editing and managing text-based network data files. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef TEXTEDITOR_H #define TEXTEDITOR_H #include //#include class QAction; class QMenu; class QTextEdit; class TextEditor : public QMainWindow { Q_OBJECT public: TextEditor(const QString &fileName , QWidget *parent= Q_NULLPTR , const bool &format=false); protected: void closeEvent(QCloseEvent *event); private slots: void newFile(); void open(); bool save(); bool saveAs(); void about(); void documentWasModified(); //protected: // bool canInsertFromMimeData(const QMimeData *source) const; // void insertFromMimeData(const QMimeData *source) ; private: void createActions(); void createMenus(); void createToolBars(); void createStatusBar(); void readSettings(); void writeSettings(); bool maybeSave(); void loadFile(const QString &fileName); bool saveFile(const QString &fileName); void setCurrentFile(const QString &fileName); QString strippedName(const QString &fullFileName); QTextEdit *textEdit; QString curFile; bool formatHTML; QMenu *fileMenu; QMenu *editMenu; QMenu *helpMenu; QToolBar *fileToolBar; QToolBar *editToolBar; QAction *newAct; QAction *openAct; QAction *saveAct; QAction *saveAsAct; QAction *exitAct; QAction *cutAct; QAction *copyAct; QAction *pasteAct; QAction *aboutAct; QAction *aboutQtAct; }; #endif SocNetV-3.7/src/tools/000077500000000000000000000000001524402210300146245ustar00rootroot00000000000000SocNetV-3.7/src/tools/README.md000066400000000000000000000007151524402210300161060ustar00rootroot00000000000000# SocNetV Tools This directory contains headless utilities used for refactoring safety nets. ## socnetv-cli `socnetv-cli` loads a dataset headlessly (no GUI) and runs one of nine analysis kernels β€” distance, reachability, walks, prominence, io_roundtrip, clustering, connectivity, matrix, vertex_connectivity β€” dumping or strictly comparing deterministic JSON golden outputs. See: [SOCNETV_CLI_REGRESSION_TOOL](../../docs/SOCNETV_CLI_REGRESSION_TOOL.md)SocNetV-3.7/src/tools/baselines/000077500000000000000000000000001524402210300165715ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/BASELINES__README.md000066400000000000000000000236421524402210300216030ustar00rootroot00000000000000# Baselines This folder contains committed golden outputs for `socnetv-cli`. Baselines are used to detect regressions while refactoring core algorithms. If a baseline changes, it must be justified in a commit message. Baselines are schema-versioned and kernel-specific. --- # 1. Schema Versions Each algorithm family owns a dedicated schema version. | Kernel | Schema | Folder | |---------------|--------|--------| | distance | v1 | `src/tools/baselines/distance/` | | reachability | v2 | `src/tools/baselines/reachability/` | | walks_matrix | v3 | `src/tools/baselines/walks/` | | prominence | v4 | `src/tools/baselines/prominence/` | | io_roundtrip | v5 | `src/tools/baselines/io_roundtrip/` | | clustering | v6 | `src/tools/baselines/clustering/` | | connectivity | v7 | `src/tools/baselines/connectivity/` | | matrix | v8 | `src/tools/baselines/matrix/` | | vertex_connectivity | v9 | `src/tools/baselines/vertex_connectivity/` | Schemas are never modified retroactively. New algorithm families must use a new schema version. --- # 2. Naming Convention ## Distance Kernel (schema v1) ``` __FT__C<0|1>_W<0|1>_IW<0|1>_DI<0|1>.json ``` * `FT` β†’ file type (`-f` argument) * `C` β†’ computeCentralities * `W` β†’ considerWeights * `IW` β†’ inverseWeights * `DI` β†’ dropIsolates Example: `SmallWorld_N10_E12__FT1__C1_W0_IW1_DI0.json` --- ## Reachability Kernel (schema v2) ``` __REACH__V2.json ``` Example: `StokmanZiegler_Netherlands__REACH__V2.json` --- ## Walks Kernel (schema v3) ``` __WALKS_K__V3.json ``` Example: `TinyPath_N3_E2__WALKS_K2__V3.json` --- ## Prominence Kernel (schema v4) ``` __PROM__V4__FT__W<0|1>_IW<0|1>_DI<0|1>.json ``` Note: `C` is not encoded β€” prominence always computes the full index set. Example: `Krackhardt_Kite_N10__PROM__V4__FT2__W0_IW1_DI0.json` --- ## IO Roundtrip Kernel (schema v5) ``` __FT.json ``` Example: `TinyGraphML_Weighted_Dir_N3__FT1.json` --- ## Clustering Kernel (schema v6) ``` __CLUST__V6__FT__W<0|1>_IW<0|1>_DI<0|1>.json ``` Example: `Krackhardt_Kite_N10__CLUST__V6__FT2__W0_IW1_DI0.json` --- ## Connectivity Kernel (schema v7) ``` __CONN__V7__FT.json ``` No weight/centrality flags β€” connectivity is topology-only. Add `__STRONG` for `--connectivity-type strong` on directed graphs (default `weak` gets no suffix). Example: `TinyDisconnected_Undir_N6_E4__CONN__V7__FT2.json`, `TinyArc_Dir_N2_E1__CONN__V7__FT2__STRONG.json` --- ## Matrix Kernel (schema v8) ``` __MATRIX__V8__FT__W<0|1>_IW<0|1>_DI<0|1>.json ``` Note: `C` is not encoded β€” matrix requires `-c 0` (rejects centralities outright). Example: `TinyPath_N3_E2__MATRIX__V8__FT2__W0_IW1_DI0.json` --- ## Vertex Connectivity Kernel (schema v9) ``` __VCONN__V9__FT__.json ``` No weight/centrality flags β€” topology-only. Mode suffix is `global` (optionally `_weak`/`_strong` on directed graphs) or `local__` (optionally `_adjacent` when the pair has no finite cut). Example: `TinyPath_N3_E2__VCONN__V9__FT2__local_1_3.json`, `TinyWeaklyConn_Dir_N3_E2__VCONN__V9__FT2__global_strong.json` --- # 3. Golden Checklist (Pre-Release) Before tagging a release: 1. Build `socnetv-cli` 2. Run all golden comparisons: `scripts/run_golden_compares.sh` 3. Ensure **no mismatches** If any case reports a mismatch: * Do NOT regenerate baselines blindly. * Investigate. * Only update baseline if the change is intentional and documented. --- # 4. How to Add a New Baseline ## Distance (v1) ```bash ./build/socnetv-cli \ -i -f \ -c <0|1> -w <0|1> -x <0|1> -k <0|1> \ --dump-json src/tools/baselines/distance/.json ``` ## Reachability (v2) ```bash ./build/socnetv-cli --kernel reachability \ -i -f \ -c 0 -w <0|1> -x <0|1> -k <0|1> \ --dump-json src/tools/baselines/reachability/.json ``` ## Walks (v3) ```bash ./build/socnetv-cli --kernel walks_matrix \ --walks-length \ -i -f \ --dump-json src/tools/baselines/walks/.json ``` ## Prominence (v4) ```bash ./build/socnetv-cli --kernel prominence \ -i -f \ -w <0|1> -x <0|1> -k <0|1> \ --dump-json src/tools/baselines/prominence/.json ``` ## IO Roundtrip (v5) ```bash ./build/socnetv-cli --kernel io_roundtrip \ -i -f \ --dump-json src/tools/baselines/io_roundtrip/.json ``` ## Clustering (v6) ```bash ./build/socnetv-cli --kernel clustering \ -i -f \ -w <0|1> -x <0|1> -k <0|1> \ --dump-json src/tools/baselines/clustering/.json ``` ## Connectivity (v7) ```bash ./build/socnetv-cli --kernel connectivity \ -i -f [--connectivity-type weak|strong] \ --dump-json src/tools/baselines/connectivity/.json ``` ## Matrix (v8) ```bash ./build/socnetv-cli --kernel matrix \ -i -f \ -c 0 -w <0|1> -x <0|1> -k <0|1> \ --dump-json src/tools/baselines/matrix/.json ``` ## Vertex Connectivity (v9) ```bash ./build/socnetv-cli --kernel vertex_connectivity \ -i -f \ --conn-mode local|global [--conn-source S --conn-target T] [--connectivity-type weak|strong] \ --dump-json src/tools/baselines/vertex_connectivity/.json ``` --- # 5. What Is Verified ## Distance Kernel (v1) Graph-level: nodes, links_sna, ties_graph, directed, weighted, average geodesic distance, diameter, disconnected_pairs, connected Per-node (when C=1): CC/SCC, BC/SBC, SC/SSC, EC/SEC, PC/SPC, distance_sum, eccentricity Floating-point values are serialized as strings. --- ## Reachability Kernel (v2) nodes, matrix (0/1), reachable_pairs, reachable_density Diagonal convention: R(i,i) = 1 --- ## Walks Kernel (v3) nodes, matrix (integer counts), walks.length, walks.total_walks Walk semantics: XM(i,j) = number of walks of exact length K from i to j --- ## Prominence Kernel (v4) Graph-level: nodes, links_sna, ties_graph, directed, weighted Per-node centrality: DC/SDC, CC/SCC, IRCC/SIRCC, BC/SBC, SC/SSC, PC/SPC, IC/SIC, EVC/SEVC, eccentricity Per-node prestige: DP/SDP, PP/SPP, PRP/SPRP All floating-point values are serialized as strings. --- ## IO Roundtrip Kernel (v5) Graph-level: nodes, relations, directed, symmetric, weighted, ties_graph, links_sna Roundtrip: performed vs skipped export, ROUNDTRIP_EQUIV, per-relation signature bundle for multi-relational datasets --- ## Clustering Kernel (v6) Graph-level: nodes, links_sna, ties_graph, directed, weighted Metrics: averageCLC, nodesWithCLC Per-node: CLC, hasCLC Triad census (16 MAN classes): 003, 012, 102, 021D, 021U, 021C, 111D, 111U, 030T, 030C, 201, 120D, 120U, 120C, 210, 300 Cliques: counts by size, max_clique_size, total_cliques --- ## Connectivity Kernel (v7) Graph-level: component_count, connected (bool), type ("connected" for undirected, "weak" or "strong" for directed per `--connectivity-type`, default `weak`) Per-node: component_id (1-based integer) β€” **weak mode only**; strong mode reports a count, not per-vertex membership (`Graph::graphStronglyConnectedComponents()` doesn't track it) Connectivity semantics: * Undirected: standard BFS β€” connected if 1 component; `--connectivity-type` has no effect * Directed, weak (default): BFS treating all arcs as undirected β€” connected if 1 weak component * Directed, strong: Tarjan's SCC algorithm, respecting arc direction β€” connected if 1 strong component --- ## Matrix Kernel (v8) Raw contents of every `Matrix`-producing `Graph` operation: adjacency, adjacency inverse, distances, similarity, reachability, walks, total walks (skipped above `kTotalWalksSkipThreshold` nodes β€” see `kernel_matrix_v8.cpp`), clique co-membership. Networks above `kFullGridSizeLimit` (20 nodes) are dumped as a compact summary (row/col sums, trace, five sampled cells) instead of the full NΓ—N grid β€” a full-grid dump is impractical past a few dozen nodes (WS5 A2.0 measured ~461 MB for one `Matrix` at N=7,343). Exists specifically because every other kernel only ever checks downstream results (centrality scores, distance values, clique counts), never a `Matrix`'s actual contents β€” see `roadmap_ws6_testing_ci_regression.md`'s WS6.7 section for the full design rationale. --- ## Vertex Connectivity Kernel (v9) Local kappa(s,t) (`--conn-mode local`) or global kappa(G) (`--conn-mode global`, the default) via Menger's theorem / vertex-split max-flow β€” see #7 and `roadmap_ws11_algorithm_additions.md`. Local mode: `source`, `target`, `status` (`"ok"` with a `value`, `"adjacent"` β€” no finite cut exists, or `"invalid"` β€” bad source/target). Global mode: `value` = kappa(G), 0 meaning already disconnected. Same weak/strong split as the Connectivity Kernel above, via `--connectivity-type`. --- # 6. Exit Codes & CI Integration `socnetv-cli --compare-json` exits with: * `0` β†’ match * non-zero β†’ mismatch or runtime error `scripts/run_golden_compares.sh` aggregates failures and exits non-zero if any mismatch occurs. --- # 7. Existing Baselines | Kernel | Directory | |---------------|-----------| | distance v1 | `src/tools/baselines/distance/` | | reachability v2 | `src/tools/baselines/reachability/` | | walks v3 | `src/tools/baselines/walks/` | | prominence v4 | `src/tools/baselines/prominence/` | | io_roundtrip v5 | `src/tools/baselines/io_roundtrip/` | | clustering v6 | `src/tools/baselines/clustering/` | | connectivity v7 | `src/tools/baselines/connectivity/` | | matrix v8 | `src/tools/baselines/matrix/` | | vertex_connectivity v9 | `src/tools/baselines/vertex_connectivity/` | --- # 8. Notes * `LINKS_SNA` reflects loader semantics. * `TIES_GRAPH` reflects canonical Graph model. * Baselines must be generated from identical datasets in `src/data/`. * Deterministic ordering is mandatory. * Never update baselines silently. * Schema structures must remain immutable once committed. SocNetV-3.7/src/tools/baselines/clustering/000077500000000000000000000000001524402210300207505ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/clustering/DunbarGelada_H22a__CLUST__V6__FT2__W0_IW1_DI0.json000066400000000000000000000061631524402210300312140ustar00rootroot00000000000000{ "cliques": { "by_size": { "2": 7, "3": 3, "4": 3 }, "max_clique_size": 4, "total_cliques": 13 }, "counts": { "links_sna": 48, "nodes": 12, "ties_graph": 24 }, "dataset": { "filetype": 2, "name": "Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj", "path": "src/data/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" }, "graph": { "directed": false, "weighted": true }, "kernel": "clustering", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "Dunbar&Dunbar_Gelada_baboon_colony_H22a", "ok": true }, "metrics": { "averageCLC": "0.30476190476190473", "nodesWithCLC": 12 }, "per_node": [ { "CLC": "0.20000000000000001", "hasCLC": true, "id": 1, "label": "Adult Female" }, { "CLC": "0", "hasCLC": true, "id": 2, "label": "2 years Male" }, { "CLC": "0.38095238095238093", "hasCLC": true, "id": 3, "label": "Adult Female" }, { "CLC": "0.47619047619047616", "hasCLC": true, "id": 4, "label": "Adult Female" }, { "CLC": "0.40000000000000002", "hasCLC": true, "id": 5, "label": "Adult Male" }, { "CLC": "0.69999999999999996", "hasCLC": true, "id": 6, "label": "3 years Female" }, { "CLC": "0.33333333333333331", "hasCLC": true, "id": 7, "label": "3 years Male" }, { "CLC": "0", "hasCLC": true, "id": 8, "label": "Adult Female" }, { "CLC": "0", "hasCLC": true, "id": 9, "label": "1 year Male" }, { "CLC": "0.5", "hasCLC": true, "id": 10, "label": "3 years Female" }, { "CLC": "0", "hasCLC": true, "id": 11, "label": "2 years Female" }, { "CLC": "0.66666666666666663", "hasCLC": true, "id": 12, "label": "1 year Male" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 6, "triad_census": { "classes": { "003": 61, "012": 0, "021C": 0, "021D": 0, "021U": 0, "030C": 0, "030T": 0, "102": 91, "111D": 0, "111U": 0, "120C": 0, "120D": 0, "120U": 0, "201": 55, "210": 0, "300": 13 }, "total_triads": 220 } } SocNetV-3.7/src/tools/baselines/clustering/DunbarGelada_H22a__CLUST__V6__FT2__W1_IW1_DI0.json000066400000000000000000000061621524402210300312140ustar00rootroot00000000000000{ "cliques": { "by_size": { "2": 7, "3": 3, "4": 3 }, "max_clique_size": 4, "total_cliques": 13 }, "counts": { "links_sna": 48, "nodes": 12, "ties_graph": 24 }, "dataset": { "filetype": 2, "name": "Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj", "path": "src/data/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" }, "graph": { "directed": false, "weighted": true }, "kernel": "clustering", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "Dunbar&Dunbar_Gelada_baboon_colony_H22a", "ok": true }, "metrics": { "averageCLC": "0.30476190476190473", "nodesWithCLC": 12 }, "per_node": [ { "CLC": "0.20000000000000001", "hasCLC": true, "id": 1, "label": "Adult Female" }, { "CLC": "0", "hasCLC": true, "id": 2, "label": "2 years Male" }, { "CLC": "0.38095238095238093", "hasCLC": true, "id": 3, "label": "Adult Female" }, { "CLC": "0.47619047619047616", "hasCLC": true, "id": 4, "label": "Adult Female" }, { "CLC": "0.40000000000000002", "hasCLC": true, "id": 5, "label": "Adult Male" }, { "CLC": "0.69999999999999996", "hasCLC": true, "id": 6, "label": "3 years Female" }, { "CLC": "0.33333333333333331", "hasCLC": true, "id": 7, "label": "3 years Male" }, { "CLC": "0", "hasCLC": true, "id": 8, "label": "Adult Female" }, { "CLC": "0", "hasCLC": true, "id": 9, "label": "1 year Male" }, { "CLC": "0.5", "hasCLC": true, "id": 10, "label": "3 years Female" }, { "CLC": "0", "hasCLC": true, "id": 11, "label": "2 years Female" }, { "CLC": "0.66666666666666663", "hasCLC": true, "id": 12, "label": "1 year Male" } ], "run": { "considerWeights": true, "dropIsolates": false, "inverseWeights": true }, "schema_version": 6, "triad_census": { "classes": { "003": 61, "012": 0, "021C": 0, "021D": 0, "021U": 0, "030C": 0, "030T": 0, "102": 91, "111D": 0, "111U": 0, "120C": 0, "120D": 0, "120U": 0, "201": 55, "210": 0, "300": 13 }, "total_triads": 220 } } SocNetV-3.7/src/tools/baselines/clustering/Krackhardt_Kite_N10__CLUST__V6__FT2__W0_IW1_DI0.json000066400000000000000000000052231524402210300315730ustar00rootroot00000000000000{ "cliques": { "by_size": { "2": 2, "3": 3, "4": 2 }, "max_clique_size": 4, "total_cliques": 7 }, "counts": { "links_sna": 36, "nodes": 10, "ties_graph": 18 }, "dataset": { "filetype": 2, "name": "Krackhardt_Kite_N10.paj", "path": "src/data/Krackhardt_Kite_N10.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "clustering", "load_report": { "fileType_signal": 2, "load_ms": 2, "load_msg": "", "net_name": "Krackhardt_Kite_N10.paj", "ok": true }, "metrics": { "averageCLC": "0.51999999999999991", "nodesWithCLC": 10 }, "per_node": [ { "CLC": "0.66666666666666663", "hasCLC": true, "id": 1, "label": "Andre" }, { "CLC": "0.66666666666666663", "hasCLC": true, "id": 2, "label": "Beverley" }, { "CLC": "1", "hasCLC": true, "id": 3, "label": "Carol" }, { "CLC": "0.53333333333333333", "hasCLC": true, "id": 4, "label": "Diane" }, { "CLC": "1", "hasCLC": true, "id": 5, "label": "Ed" }, { "CLC": "0.5", "hasCLC": true, "id": 6, "label": "Fernando" }, { "CLC": "0.5", "hasCLC": true, "id": 7, "label": "Garth" }, { "CLC": "0.33333333333333331", "hasCLC": true, "id": 8, "label": "Heather" }, { "CLC": "0", "hasCLC": true, "id": 9, "label": "Ike" }, { "CLC": "0", "hasCLC": true, "id": 10, "label": "Jane" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 6, "triad_census": { "classes": { "003": 22, "012": 0, "021C": 0, "021D": 0, "021U": 0, "030C": 0, "030T": 0, "102": 63, "111D": 0, "111U": 0, "120C": 0, "120D": 0, "120U": 0, "201": 24, "210": 0, "300": 11 }, "total_triads": 120 } } SocNetV-3.7/src/tools/baselines/clustering/Sampson_Monks_N18__CLUST__V6__FT2__W0_IW1_DI0.json000066400000000000000000000075131524402210300313440ustar00rootroot00000000000000{ "cliques": { "by_size": { "1": 3, "2": 7, "3": 3 }, "max_clique_size": 3, "total_cliques": 13 }, "counts": { "links_sna": 57, "nodes": 18, "ties_graph": 57 }, "dataset": { "filetype": 2, "name": "Sampson_Monks_N18.net", "path": "src/data/Sampson_Monks_N18.net" }, "graph": { "directed": true, "weighted": false }, "kernel": "clustering", "load_report": { "fileType_signal": 2, "load_ms": 3, "load_msg": "", "net_name": "Sampson_Monks_N18.net", "ok": true }, "metrics": { "averageCLC": "0.34047619047619043", "nodesWithCLC": 18 }, "per_node": [ { "CLC": "0.33333333333333331", "hasCLC": true, "id": 1, "label": "JohnBosco" }, { "CLC": "0.26190476190476192", "hasCLC": true, "id": 2, "label": "Gregory" }, { "CLC": "0.25", "hasCLC": true, "id": 3, "label": "Basil" }, { "CLC": "0.29999999999999999", "hasCLC": true, "id": 4, "label": "Peter" }, { "CLC": "0.23333333333333334", "hasCLC": true, "id": 5, "label": "Bonaventure" }, { "CLC": "0.58333333333333337", "hasCLC": true, "id": 6, "label": "Berthold" }, { "CLC": "0.29999999999999999", "hasCLC": true, "id": 7, "label": "Mark" }, { "CLC": "0.33333333333333331", "hasCLC": true, "id": 8, "label": "Victor" }, { "CLC": "0.14999999999999999", "hasCLC": true, "id": 9, "label": "Ambrose" }, { "CLC": "0.41666666666666669", "hasCLC": true, "id": 10, "label": "Romuald" }, { "CLC": "0.25", "hasCLC": true, "id": 11, "label": "Louis" }, { "CLC": "0.26666666666666666", "hasCLC": true, "id": 12, "label": "Winifrid" }, { "CLC": "0.14999999999999999", "hasCLC": true, "id": 13, "label": "Amand" }, { "CLC": "0.25", "hasCLC": true, "id": 14, "label": "Hugh" }, { "CLC": "0.55000000000000004", "hasCLC": true, "id": 15, "label": "Boniface" }, { "CLC": "0.66666666666666663", "hasCLC": true, "id": 16, "label": "Albert" }, { "CLC": "0.5", "hasCLC": true, "id": 17, "label": "Elias" }, { "CLC": "0.33333333333333331", "hasCLC": true, "id": 18, "label": "Simplicius" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 6, "triad_census": { "classes": { "003": 293, "012": 247, "021C": 19, "021D": 7, "021U": 12, "030C": 1, "030T": 2, "102": 165, "111D": 27, "111U": 13, "120C": 4, "120D": 7, "120U": 1, "201": 11, "210": 4, "300": 3 }, "total_triads": 816 } } StokmanZiegler_Netherlands__CLUST__V6__FT5__W0_IW1_DI0.json000066400000000000000000000072241524402210300333370ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/clustering{ "cliques": { "by_size": { "1": 1, "3": 1, "4": 10, "5": 4, "6": 3 }, "max_clique_size": 6, "total_cliques": 19 }, "counts": { "links_sna": 120, "nodes": 16, "ties_graph": 120 }, "dataset": { "filetype": 5, "name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "path": "src/data/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" }, "graph": { "directed": true, "weighted": true }, "kernel": "clustering", "load_report": { "fileType_signal": 5, "load_ms": 1, "load_msg": "", "net_name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "ok": true }, "metrics": { "averageCLC": "0.60382846320346317", "nodesWithCLC": 16 }, "per_node": [ { "CLC": "0.51111111111111107", "hasCLC": true, "id": 1, "label": "ABN" }, { "CLC": "0.51515151515151514", "hasCLC": true, "id": 2, "label": "AMRO" }, { "CLC": "0.7142857142857143", "hasCLC": true, "id": 3, "label": "ENNIA" }, { "CLC": "0.75", "hasCLC": true, "id": 4, "label": "NS" }, { "CLC": "0.59999999999999998", "hasCLC": true, "id": 5, "label": "BUHRT" }, { "CLC": "0.80000000000000004", "hasCLC": true, "id": 6, "label": "AGO" }, { "CLC": "0.58181818181818179", "hasCLC": true, "id": 7, "label": "AKZO" }, { "CLC": "0.72222222222222221", "hasCLC": true, "id": 8, "label": "NB" }, { "CLC": "0.66666666666666663", "hasCLC": true, "id": 9, "label": "SHV" }, { "CLC": "0.59999999999999998", "hasCLC": true, "id": 10, "label": "FGH" }, { "CLC": "0.61904761904761907", "hasCLC": true, "id": 11, "label": "HEINK" }, { "CLC": "0.58333333333333337", "hasCLC": true, "id": 12, "label": "PHLPS" }, { "CLC": "0.8571428571428571", "hasCLC": true, "id": 13, "label": "NATND" }, { "CLC": "0.53333333333333333", "hasCLC": true, "id": 14, "label": "OGEM" }, { "CLC": "0.6071428571428571", "hasCLC": true, "id": 15, "label": "RSV" }, { "CLC": "0", "hasCLC": true, "id": 16, "label": "NSU" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 6, "triad_census": { "classes": { "003": 75, "012": 0, "021C": 0, "021D": 0, "021U": 0, "030C": 0, "030T": 0, "102": 223, "111D": 0, "111U": 0, "120C": 0, "120D": 0, "120U": 0, "201": 169, "210": 0, "300": 93 }, "total_triads": 560 } } StokmanZiegler_Netherlands__CLUST__V6__FT5__W1_IW1_DI0.json000066400000000000000000000072231524402210300333370ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/clustering{ "cliques": { "by_size": { "1": 1, "3": 1, "4": 10, "5": 4, "6": 3 }, "max_clique_size": 6, "total_cliques": 19 }, "counts": { "links_sna": 120, "nodes": 16, "ties_graph": 120 }, "dataset": { "filetype": 5, "name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "path": "src/data/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" }, "graph": { "directed": true, "weighted": true }, "kernel": "clustering", "load_report": { "fileType_signal": 5, "load_ms": 0, "load_msg": "", "net_name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "ok": true }, "metrics": { "averageCLC": "0.60382846320346317", "nodesWithCLC": 16 }, "per_node": [ { "CLC": "0.51111111111111107", "hasCLC": true, "id": 1, "label": "ABN" }, { "CLC": "0.51515151515151514", "hasCLC": true, "id": 2, "label": "AMRO" }, { "CLC": "0.7142857142857143", "hasCLC": true, "id": 3, "label": "ENNIA" }, { "CLC": "0.75", "hasCLC": true, "id": 4, "label": "NS" }, { "CLC": "0.59999999999999998", "hasCLC": true, "id": 5, "label": "BUHRT" }, { "CLC": "0.80000000000000004", "hasCLC": true, "id": 6, "label": "AGO" }, { "CLC": "0.58181818181818179", "hasCLC": true, "id": 7, "label": "AKZO" }, { "CLC": "0.72222222222222221", "hasCLC": true, "id": 8, "label": "NB" }, { "CLC": "0.66666666666666663", "hasCLC": true, "id": 9, "label": "SHV" }, { "CLC": "0.59999999999999998", "hasCLC": true, "id": 10, "label": "FGH" }, { "CLC": "0.61904761904761907", "hasCLC": true, "id": 11, "label": "HEINK" }, { "CLC": "0.58333333333333337", "hasCLC": true, "id": 12, "label": "PHLPS" }, { "CLC": "0.8571428571428571", "hasCLC": true, "id": 13, "label": "NATND" }, { "CLC": "0.53333333333333333", "hasCLC": true, "id": 14, "label": "OGEM" }, { "CLC": "0.6071428571428571", "hasCLC": true, "id": 15, "label": "RSV" }, { "CLC": "0", "hasCLC": true, "id": 16, "label": "NSU" } ], "run": { "considerWeights": true, "dropIsolates": false, "inverseWeights": true }, "schema_version": 6, "triad_census": { "classes": { "003": 75, "012": 0, "021C": 0, "021D": 0, "021U": 0, "030C": 0, "030T": 0, "102": 223, "111D": 0, "111U": 0, "120C": 0, "120D": 0, "120U": 0, "201": 169, "210": 0, "300": 93 }, "total_triads": 560 } } SocNetV-3.7/src/tools/baselines/clustering/TinyDirChain_N3__CLUST__V6__FT2__W0_IW1_DI0.json000066400000000000000000000032121524402210300310040ustar00rootroot00000000000000{ "cliques": { "by_size": { "1": 3 }, "max_clique_size": 1, "total_cliques": 3 }, "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyDirChain_N3.paj", "path": "src/data/TinyDirChain_N3.paj" }, "graph": { "directed": true, "weighted": false }, "kernel": "clustering", "load_report": { "fileType_signal": 2, "load_ms": 3, "load_msg": "", "net_name": "TinyDirChain_N3.paj", "ok": true }, "metrics": { "averageCLC": "0", "nodesWithCLC": 3 }, "per_node": [ { "CLC": "0", "hasCLC": true, "id": 1, "label": "A" }, { "CLC": "0", "hasCLC": true, "id": 2, "label": "B" }, { "CLC": "0", "hasCLC": true, "id": 3, "label": "C" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 6, "triad_census": { "classes": { "003": 0, "012": 0, "021C": 1, "021D": 0, "021U": 0, "030C": 0, "030T": 0, "102": 0, "111D": 0, "111U": 0, "120C": 0, "120D": 0, "120U": 0, "201": 0, "210": 0, "300": 0 }, "total_triads": 1 } } SocNetV-3.7/src/tools/baselines/clustering/TinyPath_N3_E2__CLUST__V6__FT2__W0_IW1_DI0.json000066400000000000000000000032101524402210300305430ustar00rootroot00000000000000{ "cliques": { "by_size": { "2": 2 }, "max_clique_size": 2, "total_cliques": 2 }, "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "src/data/TinyPath_N3_E2.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "clustering", "load_report": { "fileType_signal": 2, "load_ms": 2, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "metrics": { "averageCLC": "0", "nodesWithCLC": 3 }, "per_node": [ { "CLC": "0", "hasCLC": true, "id": 1, "label": "A" }, { "CLC": "0", "hasCLC": true, "id": 2, "label": "B" }, { "CLC": "0", "hasCLC": true, "id": 3, "label": "C" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 6, "triad_census": { "classes": { "003": 0, "012": 0, "021C": 0, "021D": 0, "021U": 0, "030C": 0, "030T": 0, "102": 0, "111D": 0, "111U": 0, "120C": 0, "120D": 0, "120U": 0, "201": 1, "210": 0, "300": 0 }, "total_triads": 1 } } SocNetV-3.7/src/tools/baselines/connectivity/000077500000000000000000000000001524402210300213075ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/connectivity/TinyArc_Dir_N2_E1__CONN__V7__FT2.json000066400000000000000000000015641524402210300275440ustar00rootroot00000000000000{ "connectivity": { "component_count": 1, "connected": true, "type": "weak" }, "counts": { "links_sna": 1, "nodes": 2, "ties_graph": 1 }, "dataset": { "filetype": 2, "name": "TinyArc_Dir_N2_E1.paj", "path": "/home/dimitris/socnetv/app/src/data/TinyArc_Dir_N2_E1.paj" }, "graph": { "directed": true, "weighted": false }, "kernel": "connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyArc_Dir_N2_E1.paj", "ok": true }, "per_node": [ { "component_id": 1, "id": 1, "label": "A" }, { "component_id": 1, "id": 2, "label": "B" } ], "schema_version": 7 } SocNetV-3.7/src/tools/baselines/connectivity/TinyArc_Dir_N2_E1__CONN__V7__FT2__STRONG.json000066400000000000000000000014361524402210300307750ustar00rootroot00000000000000{ "connectivity": { "component_count": 2, "connected": false, "type": "strong" }, "counts": { "links_sna": 1, "nodes": 2, "ties_graph": 1 }, "dataset": { "filetype": 2, "name": "TinyArc_Dir_N2_E1.paj", "path": "src/data/TinyArc_Dir_N2_E1.paj" }, "graph": { "directed": true, "weighted": false }, "kernel": "connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyArc_Dir_N2_E1.paj", "ok": true }, "per_node": [ { "id": 1, "label": "A" }, { "id": 2, "label": "B" } ], "schema_version": 7 } SocNetV-3.7/src/tools/baselines/connectivity/TinyDisconnected_Dir_N5_E3__CONN__V7__FT2.json000066400000000000000000000022661524402210300314460ustar00rootroot00000000000000{ "connectivity": { "component_count": 2, "connected": false, "type": "weak" }, "counts": { "links_sna": 3, "nodes": 5, "ties_graph": 3 }, "dataset": { "filetype": 2, "name": "TinyDisconnected_Dir_N5_E3.paj", "path": "/home/dimitris/socnetv/app/src/data/TinyDisconnected_Dir_N5_E3.paj" }, "graph": { "directed": true, "weighted": false }, "kernel": "connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyDisconnected_Dir_N5_E3.paj", "ok": true }, "per_node": [ { "component_id": 1, "id": 1, "label": "A" }, { "component_id": 1, "id": 2, "label": "B" }, { "component_id": 1, "id": 3, "label": "C" }, { "component_id": 2, "id": 4, "label": "D" }, { "component_id": 2, "id": 5, "label": "E" } ], "schema_version": 7 } SocNetV-3.7/src/tools/baselines/connectivity/TinyDisconnected_Dir_N5_E3__CONN__V7__FT2__STRONG.json000066400000000000000000000020021524402210300326650ustar00rootroot00000000000000{ "connectivity": { "component_count": 5, "connected": false, "type": "strong" }, "counts": { "links_sna": 3, "nodes": 5, "ties_graph": 3 }, "dataset": { "filetype": 2, "name": "TinyDisconnected_Dir_N5_E3.paj", "path": "src/data/TinyDisconnected_Dir_N5_E3.paj" }, "graph": { "directed": true, "weighted": false }, "kernel": "connectivity", "load_report": { "fileType_signal": 2, "load_ms": 3, "load_msg": "", "net_name": "TinyDisconnected_Dir_N5_E3.paj", "ok": true }, "per_node": [ { "id": 1, "label": "A" }, { "id": 2, "label": "B" }, { "id": 3, "label": "C" }, { "id": 4, "label": "D" }, { "id": 5, "label": "E" } ], "schema_version": 7 } SocNetV-3.7/src/tools/baselines/connectivity/TinyDisconnected_Undir_N6_E4__CONN__V7__FT2.json000066400000000000000000000024441524402210300320110ustar00rootroot00000000000000{ "connectivity": { "component_count": 3, "connected": false, "type": "connected" }, "counts": { "links_sna": 6, "nodes": 6, "ties_graph": 3 }, "dataset": { "filetype": 2, "name": "TinyDisconnected_Undir_N6_E4.paj", "path": "/home/dimitris/socnetv/app/src/data/TinyDisconnected_Undir_N6_E4.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyDisconnected_Undir_N6_E4.paj", "ok": true }, "per_node": [ { "component_id": 1, "id": 1, "label": "A" }, { "component_id": 1, "id": 2, "label": "B" }, { "component_id": 1, "id": 3, "label": "C" }, { "component_id": 2, "id": 4, "label": "D" }, { "component_id": 2, "id": 5, "label": "E" }, { "component_id": 3, "id": 6, "label": "F" } ], "schema_version": 7 } SocNetV-3.7/src/tools/baselines/connectivity/TinyIsolated_Dir_N2_E0__CONN__V7__FT2.json000066400000000000000000000016121524402210300305740ustar00rootroot00000000000000{ "connectivity": { "component_count": 2, "connected": false, "type": "connected" }, "counts": { "links_sna": 0, "nodes": 2, "ties_graph": 0 }, "dataset": { "filetype": 2, "name": "TinyIsolated_Dir_N2_E0.paj", "path": "/home/dimitris/socnetv/app/src/data/TinyIsolated_Dir_N2_E0.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyIsolated_Dir_N2_E0.paj", "ok": true }, "per_node": [ { "component_id": 1, "id": 1, "label": "A" }, { "component_id": 2, "id": 2, "label": "B" } ], "schema_version": 7 } SocNetV-3.7/src/tools/baselines/connectivity/TinyIsolated_Undir_N3_E0__CONN__V7__FT2.json000066400000000000000000000017621524402210300311460ustar00rootroot00000000000000{ "connectivity": { "component_count": 3, "connected": false, "type": "connected" }, "counts": { "links_sna": 0, "nodes": 3, "ties_graph": 0 }, "dataset": { "filetype": 2, "name": "TinyIsolated_Undir_N3_E0.paj", "path": "/home/dimitris/socnetv/app/src/data/TinyIsolated_Undir_N3_E0.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyIsolated_Undir_N3_E0.paj", "ok": true }, "per_node": [ { "component_id": 1, "id": 1, "label": "A" }, { "component_id": 2, "id": 2, "label": "B" }, { "component_id": 3, "id": 3, "label": "C" } ], "schema_version": 7 } SocNetV-3.7/src/tools/baselines/connectivity/TinyPath_N3_E2__CONN__V7__FT2.json000066400000000000000000000017231524402210300271340ustar00rootroot00000000000000{ "connectivity": { "component_count": 1, "connected": true, "type": "connected" }, "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "/home/dimitris/socnetv/app/src/data/TinyPath_N3_E2.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "per_node": [ { "component_id": 1, "id": 1, "label": "A" }, { "component_id": 1, "id": 2, "label": "B" }, { "component_id": 1, "id": 3, "label": "C" } ], "schema_version": 7 } SocNetV-3.7/src/tools/baselines/connectivity/TinyWeaklyConn_Dir_N3_E2__CONN__V7__FT2.json000066400000000000000000000017531524402210300311130ustar00rootroot00000000000000{ "connectivity": { "component_count": 1, "connected": true, "type": "weak" }, "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyWeaklyConn_Dir_N3_E2.paj", "path": "/home/dimitris/socnetv/app/src/data/TinyWeaklyConn_Dir_N3_E2.paj" }, "graph": { "directed": true, "weighted": false }, "kernel": "connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyWeaklyConn_Dir_N3_E2.paj", "ok": true }, "per_node": [ { "component_id": 1, "id": 1, "label": "A" }, { "component_id": 1, "id": 2, "label": "B" }, { "component_id": 1, "id": 3, "label": "C" } ], "schema_version": 7 } SocNetV-3.7/src/tools/baselines/connectivity/TinyWeaklyConn_Dir_N3_E2__CONN__V7__FT2__STRONG.json000066400000000000000000000015661524402210300323500ustar00rootroot00000000000000{ "connectivity": { "component_count": 3, "connected": false, "type": "strong" }, "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyWeaklyConn_Dir_N3_E2.paj", "path": "src/data/TinyWeaklyConn_Dir_N3_E2.paj" }, "graph": { "directed": true, "weighted": false }, "kernel": "connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyWeaklyConn_Dir_N3_E2.paj", "ok": true }, "per_node": [ { "id": 1, "label": "A" }, { "id": 2, "label": "B" }, { "id": 3, "label": "C" } ], "schema_version": 7 } SocNetV-3.7/src/tools/baselines/distance/000077500000000000000000000000001524402210300203635ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/distance/DunbarGelada_H22a__FT2__C1_W0_IW1_DI0.json000066400000000000000000000166461524402210300273360ustar00rootroot00000000000000{ "counts": { "links_sna": 48, "nodes": 12, "ties_graph": 24 }, "dataset": { "filetype": 2, "name": "Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj", "path": "src/data/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" }, "graph": { "directed": false, "weighted": true }, "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "Dunbar&Dunbar_Gelada_baboon_colony_H22a", "ok": true }, "metrics": { "avg_distance": "3.7878787878787881", "connected": true, "density": "0.36363636363636365", "diameter": 4, "disconnected_pairs": 0 }, "per_node": [ { "BC": "12.472222222222221", "CC": "0.055555555555555552", "EC": "0.33333333333333331", "PC": "7.833333333333333", "SBC": "0.22676767676767676", "SC": "13", "SCC": "0.61111111111111105", "SEC": "0.33333333333333331", "SPC": "0.71212121212121215", "SSC": "0.16250000000000001", "distance_sum": "18", "eccentricity": "3", "eccentricity_inf": false, "id": 1, "label": "Adult Female" }, { "BC": "0", "CC": "0.035714285714285712", "EC": "0.25", "PC": "4.9166666666666661", "SBC": "0", "SC": "0", "SCC": "0.39285714285714285", "SEC": "0.25", "SPC": "0.44696969696969691", "SSC": "0", "distance_sum": "28", "eccentricity": "4", "eccentricity_inf": false, "id": 2, "label": "2 years Male" }, { "BC": "9.1666666666666661", "CC": "0.058823529411764705", "EC": "0.33333333333333331", "PC": "8.6666666666666679", "SBC": "0.16666666666666666", "SC": "15", "SCC": "0.6470588235294118", "SEC": "0.33333333333333331", "SPC": "0.78787878787878796", "SSC": "0.1875", "distance_sum": "17", "eccentricity": "3", "eccentricity_inf": false, "id": 3, "label": "Adult Female" }, { "BC": "8.3333333333333339", "CC": "0.0625", "EC": "0.33333333333333331", "PC": "8.8333333333333321", "SBC": "0.15151515151515152", "SC": "14", "SCC": "0.6875", "SEC": "0.33333333333333331", "SPC": "0.80303030303030298", "SSC": "0.17499999999999999", "distance_sum": "16", "eccentricity": "3", "eccentricity_inf": false, "id": 4, "label": "Adult Female" }, { "BC": "9.3888888888888893", "CC": "0.058823529411764705", "EC": "0.5", "PC": "8", "SBC": "0.17070707070707072", "SC": "10.5", "SCC": "0.6470588235294118", "SEC": "0.5", "SPC": "0.72727272727272729", "SSC": "0.13125000000000001", "distance_sum": "17", "eccentricity": "2", "eccentricity_inf": false, "id": 5, "label": "Adult Male" }, { "BC": "1.2777777777777777", "CC": "0.052631578947368418", "EC": "0.33333333333333331", "PC": "7.666666666666667", "SBC": "0.023232323232323229", "SC": "4", "SCC": "0.57894736842105265", "SEC": "0.33333333333333331", "SPC": "0.69696969696969702", "SSC": "0.050000000000000003", "distance_sum": "19", "eccentricity": "3", "eccentricity_inf": false, "id": 6, "label": "3 years Female" }, { "BC": "2.2777777777777777", "CC": "0.050000000000000003", "EC": "0.33333333333333331", "PC": "6.8333333333333339", "SBC": "0.04141414141414141", "SC": "4", "SCC": "0.55000000000000004", "SEC": "0.33333333333333331", "SPC": "0.62121212121212133", "SSC": "0.050000000000000003", "distance_sum": "20", "eccentricity": "3", "eccentricity_inf": false, "id": 7, "label": "3 years Male" }, { "BC": "10.916666666666666", "CC": "0.047619047619047616", "EC": "0.33333333333333331", "PC": "7", "SBC": "0.19848484848484849", "SC": "9.5", "SCC": "0.52380952380952372", "SEC": "0.33333333333333331", "SPC": "0.63636363636363635", "SSC": "0.11874999999999999", "distance_sum": "21", "eccentricity": "3", "eccentricity_inf": false, "id": 8, "label": "Adult Female" }, { "BC": "0", "CC": "0.032258064516129031", "EC": "0.25", "PC": "4.583333333333333", "SBC": "0", "SC": "0", "SCC": "0.35483870967741937", "SEC": "0.25", "SPC": "0.41666666666666663", "SSC": "0", "distance_sum": "31", "eccentricity": "4", "eccentricity_inf": false, "id": 9, "label": "1 year Male" }, { "BC": "3.5", "CC": "0.052631578947368418", "EC": "0.33333333333333331", "PC": "7.333333333333333", "SBC": "0.06363636363636363", "SC": "5.5", "SCC": "0.57894736842105265", "SEC": "0.33333333333333331", "SPC": "0.66666666666666663", "SSC": "0.068750000000000006", "distance_sum": "19", "eccentricity": "3", "eccentricity_inf": false, "id": 10, "label": "3 years Female" }, { "BC": "0.5", "CC": "0.043478260869565216", "EC": "0.25", "PC": "6.0833333333333339", "SBC": "0.0090909090909090905", "SC": "1.5", "SCC": "0.47826086956521741", "SEC": "0.25", "SPC": "0.55303030303030309", "SSC": "0.018749999999999999", "distance_sum": "23", "eccentricity": "4", "eccentricity_inf": false, "id": 11, "label": "2 years Female" }, { "BC": "1.1666666666666665", "CC": "0.047619047619047616", "EC": "0.25", "PC": "7.083333333333333", "SBC": "0.02121212121212121", "SC": "3", "SCC": "0.52380952380952372", "SEC": "0.25", "SPC": "0.64393939393939392", "SSC": "0.037499999999999999", "distance_sum": "21", "eccentricity": "4", "eccentricity_inf": false, "id": 12, "label": "1 year Male" } ], "run": { "computeCentralities": true, "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 1 } SocNetV-3.7/src/tools/baselines/distance/DunbarGelada_H22a__FT2__C1_W1_IW1_DI0.json000066400000000000000000000166351524402210300273350ustar00rootroot00000000000000{ "counts": { "links_sna": 48, "nodes": 12, "ties_graph": 24 }, "dataset": { "filetype": 2, "name": "Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj", "path": "src/data/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" }, "graph": { "directed": false, "weighted": true }, "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "Dunbar&Dunbar_Gelada_baboon_colony_H22a", "ok": true }, "metrics": { "avg_distance": "0.42027859123447353", "connected": true, "density": "0.36363636363636365", "diameter": 1, "disconnected_pairs": 0 }, "per_node": [ { "BC": "23", "CC": "0.2512380902620982", "EC": "0.83333333333333337", "PC": "56.855755672539495", "SBC": "0.41818181818181815", "SC": "0", "SCC": "0.092120633096102661", "SEC": "0.83333333333333337", "SPC": "3.3444562160317348", "SSC": "nan", "distance_sum": "3.980288175876411", "eccentricity": "1.2", "eccentricity_inf": false, "id": 1, "label": "Adult Female" }, { "BC": "0", "CC": "0.20452494736490326", "EC": "0.77464788732394363", "PC": "44.591943775744994", "SBC": "0", "SC": "0", "SCC": "0.07499248070046452", "SEC": "0.77464788732394363", "SPC": "2.6230555162202935", "SSC": "nan", "distance_sum": "4.8893790849673202", "eccentricity": "1.290909090909091", "eccentricity_inf": false, "id": 2, "label": "2 years Male" }, { "BC": "31.5", "CC": "0.32335358924954299", "EC": "1", "PC": "87.358573982578477", "SBC": "0.57272727272727275", "SC": "0", "SCC": "0.11856298272483243", "SEC": "1", "SPC": "5.4599108739111548", "SSC": "nan", "distance_sum": "3.0925897631779984", "eccentricity": "1", "eccentricity_inf": false, "id": 3, "label": "Adult Female" }, { "BC": "33", "CC": "0.33415873177954203", "EC": "1", "PC": "88.874529827533266", "SBC": "0.59999999999999998", "SC": "0", "SCC": "0.1225248683191654", "SEC": "1", "SPC": "5.2279135192666626", "SSC": "nan", "distance_sum": "2.9925897631779987", "eccentricity": "1", "eccentricity_inf": false, "id": 4, "label": "Adult Female" }, { "BC": "21", "CC": "0.30719246529710792", "EC": "0.80000000000000004", "PC": "80.93270063270063", "SBC": "0.38181818181818183", "SC": "0", "SCC": "0.11263723727560623", "SEC": "0.80000000000000004", "SPC": "4.4962611462611459", "SSC": "nan", "distance_sum": "3.2552881758764105", "eccentricity": "1.25", "eccentricity_inf": false, "id": 5, "label": "Adult Male" }, { "BC": "0.5", "CC": "0.22968964310468021", "EC": "0.75", "PC": "67.817262505875036", "SBC": "0.0090909090909090905", "SC": "0", "SCC": "0.0842195358050494", "SEC": "0.75", "SPC": "3.5693296055723702", "SSC": "nan", "distance_sum": "4.3537008742891103", "eccentricity": "1.3333333333333333", "eccentricity_inf": false, "id": 6, "label": "3 years Female" }, { "BC": "4", "CC": "0.2159475061142509", "EC": "0.75", "PC": "56.184523983005541", "SBC": "0.072727272727272724", "SC": "0", "SCC": "0.079180752241891986", "SEC": "0.75", "SPC": "2.8092261991502774", "SSC": "nan", "distance_sum": "4.6307550292844413", "eccentricity": "1.3333333333333333", "eccentricity_inf": false, "id": 7, "label": "3 years Male" }, { "BC": "12.5", "CC": "0.22053878593220508", "EC": "0.69230769230769229", "PC": "56.571176817359813", "SBC": "0.22727272727272727", "SC": "0", "SCC": "0.080864221508475193", "SEC": "0.69230769230769229", "SPC": "3.1428431565199895", "SSC": "nan", "distance_sum": "4.5343498005262708", "eccentricity": "1.4444444444444444", "eccentricity_inf": false, "id": 8, "label": "Adult Female" }, { "BC": "0", "CC": "0.10618322848479345", "EC": "0.51428571428571435", "PC": "19.218835564759708", "SBC": "0", "SC": "0", "SCC": "0.03893385044442426", "SEC": "0.51428571428571435", "SPC": "1.1305197391035122", "SSC": "nan", "distance_sum": "9.4176831338596045", "eccentricity": "1.9444444444444444", "eccentricity_inf": false, "id": 9, "label": "1 year Male" }, { "BC": "0", "CC": "0.30503577004637294", "EC": "0.88888888888888884", "PC": "57.917156895825563", "SBC": "0", "SC": "0", "SCC": "0.1118464490170034", "SEC": "0.88888888888888884", "SPC": "3.6198223059890977", "SSC": "nan", "distance_sum": "3.2783040488922839", "eccentricity": "1.125", "eccentricity_inf": false, "id": 10, "label": "3 years Female" }, { "BC": "0", "CC": "0.20579280275438491", "EC": "0.66666666666666663", "PC": "46.159778242976024", "SBC": "0", "SC": "0", "SCC": "0.075457361009941132", "SEC": "0.66666666666666663", "SPC": "2.3079889121488013", "SSC": "nan", "distance_sum": "4.8592564298446659", "eccentricity": "1.5", "eccentricity_inf": false, "id": 11, "label": "2 years Female" }, { "BC": "0", "CC": "0.1614833273707455", "EC": "0.75", "PC": "30.661796009709402", "SBC": "0", "SC": "0", "SCC": "0.059210553369273347", "SEC": "0.75", "SPC": "1.7034331116505221", "SSC": "nan", "distance_sum": "6.192589763177998", "eccentricity": "1.3333333333333333", "eccentricity_inf": false, "id": 12, "label": "1 year Male" } ], "run": { "computeCentralities": true, "considerWeights": true, "dropIsolates": false, "inverseWeights": true }, "schema_version": 1 } SocNetV-3.7/src/tools/baselines/distance/StokmanZiegler_Netherlands__FT5__C1_W0_IW1_DI0.json000066400000000000000000000207631524402210300315340ustar00rootroot00000000000000{ "counts": { "links_sna": 120, "nodes": 16, "ties_graph": 120 }, "dataset": { "filetype": 5, "name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "path": "src/data/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" }, "graph": { "directed": true, "weighted": true }, "load_report": { "fileType_signal": 5, "load_ms": 2, "load_msg": "", "net_name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "ok": true }, "metrics": { "avg_distance": "306783381", "connected": false, "diameter": 2, "disconnected_pairs": 30 }, "per_node": [ { "BC": "6.60952380952381", "CC": "0", "EC": "0", "PC": "12", "SBC": "0.062947845804988661", "SC": "22", "SCC": "0", "SEC": "0", "SPC": "0.8571428571428571", "SSC": "0.13017751479289941", "distance_sum": "18", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 1, "label": "ABN" }, { "BC": "9.7595238095238095", "CC": "0", "EC": "0", "PC": "13", "SBC": "0.09294784580498866", "SC": "32", "SCC": "0", "SEC": "0", "SPC": "0.92857142857142849", "SSC": "0.1893491124260355", "distance_sum": "16", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "AMRO" }, { "BC": "1.6166666666666665", "CC": "0", "EC": "0", "PC": "10.5", "SBC": "0.015396825396825395", "SC": "6", "SCC": "0", "SEC": "0", "SPC": "0.75", "SSC": "0.035502958579881658", "distance_sum": "21", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "ENNIA" }, { "BC": "1.427777777777778", "CC": "0", "EC": "0", "PC": "11", "SBC": "0.0135978835978836", "SC": "7", "SCC": "0", "SEC": "0", "SPC": "0.7857142857142857", "SSC": "0.04142011834319527", "distance_sum": "20", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 4, "label": "NS" }, { "BC": "1.4777777777777776", "CC": "0", "EC": "0", "PC": "10", "SBC": "0.014074074074074072", "SC": "6", "SCC": "0", "SEC": "0", "SPC": "0.71428571428571419", "SSC": "0.035502958579881658", "distance_sum": "22", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 5, "label": "BUHRT" }, { "BC": "0.45396825396825391", "CC": "0", "EC": "0", "PC": "10", "SBC": "0.0043235071806500373", "SC": "3", "SCC": "0", "SEC": "0", "SPC": "0.71428571428571419", "SSC": "0.017751479289940829", "distance_sum": "22", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 6, "label": "AGO" }, { "BC": "6.3206349206349204", "CC": "0", "EC": "0", "PC": "12.5", "SBC": "0.060196523053665907", "SC": "23", "SCC": "0", "SEC": "0", "SPC": "0.89285714285714279", "SSC": "0.13609467455621302", "distance_sum": "17", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 7, "label": "AKZO" }, { "BC": "1.9706349206349207", "CC": "0", "EC": "0", "PC": "11.5", "SBC": "0.018767951625094485", "SC": "10", "SCC": "0", "SEC": "0", "SPC": "0.8214285714285714", "SSC": "0.059171597633136092", "distance_sum": "19", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 8, "label": "NB" }, { "BC": "2.8277777777777779", "CC": "0", "EC": "0", "PC": "11.5", "SBC": "0.026931216931216934", "SC": "12", "SCC": "0", "SEC": "0", "SPC": "0.8214285714285714", "SSC": "0.071005917159763315", "distance_sum": "19", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 9, "label": "SHV" }, { "BC": "1.0333333333333332", "CC": "0", "EC": "0", "PC": "9.5", "SBC": "0.00984126984126984", "SC": "4", "SCC": "0", "SEC": "0", "SPC": "0.67857142857142849", "SSC": "0.023668639053254437", "distance_sum": "23", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 10, "label": "FGH" }, { "BC": "2.3111111111111109", "CC": "0", "EC": "0", "PC": "10.5", "SBC": "0.022010582010582008", "SC": "8", "SCC": "0", "SEC": "0", "SPC": "0.75", "SSC": "0.047337278106508875", "distance_sum": "21", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 11, "label": "HEINK" }, { "BC": "3.8611111111111112", "CC": "0", "EC": "0", "PC": "11.5", "SBC": "0.036772486772486776", "SC": "15", "SCC": "0", "SEC": "0", "SPC": "0.8214285714285714", "SSC": "0.088757396449704137", "distance_sum": "19", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 12, "label": "PHLPS" }, { "BC": "0.42063492063492058", "CC": "0", "EC": "0", "PC": "10.5", "SBC": "0.0040060468631897194", "SC": "3", "SCC": "0", "SEC": "0", "SPC": "0.75", "SSC": "0.017751479289940829", "distance_sum": "21", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 13, "label": "NATND" }, { "BC": "2.1666666666666665", "CC": "0", "EC": "0", "PC": "10", "SBC": "0.020634920634920634", "SC": "7", "SCC": "0", "SEC": "0", "SPC": "0.71428571428571419", "SSC": "0.04142011834319527", "distance_sum": "22", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 14, "label": "OGEM" }, { "BC": "2.7428571428571429", "CC": "0", "EC": "0", "PC": "11", "SBC": "0.026122448979591838", "SC": "11", "SCC": "0", "SEC": "0", "SPC": "0.7857142857142857", "SSC": "0.065088757396449703", "distance_sum": "20", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 15, "label": "RSV" }, { "BC": "0", "CC": "0", "EC": "0", "PC": "0", "SBC": "0", "SC": "0", "SCC": "0", "SEC": "0", "SPC": "0", "SSC": "0", "distance_sum": "0", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 16, "label": "NSU" } ], "run": { "computeCentralities": true, "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 1 } SocNetV-3.7/src/tools/baselines/distance/StokmanZiegler_Netherlands__FT5__C1_W1_IW1_DI0.json000066400000000000000000000215011524402210300315240ustar00rootroot00000000000000{ "counts": { "links_sna": 120, "nodes": 16, "ties_graph": 120 }, "dataset": { "filetype": 5, "name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "path": "src/data/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" }, "graph": { "directed": true, "weighted": true }, "load_report": { "fileType_signal": 5, "load_ms": 3, "load_msg": "", "net_name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "ok": true }, "metrics": { "avg_distance": "262957182.14625847", "connected": false, "diameter": 2, "disconnected_pairs": 30 }, "per_node": [ { "BC": "17.375000000000004", "CC": "0", "EC": "0", "PC": "22.390476190476189", "SBC": "0.1654761904761905", "SC": "4", "SCC": "0", "SEC": "0", "SPC": "1.3170868347338935", "SSC": "0.087912087912087919", "distance_sum": "12.166666666666666", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 1, "label": "ABN" }, { "BC": "32", "CC": "0", "EC": "0", "PC": "25.745454545454546", "SBC": "0.30476190476190479", "SC": "9.5", "SCC": "0", "SEC": "0", "SPC": "1.6090909090909091", "SSC": "0.2087912087912088", "distance_sum": "10.25", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "AMRO" }, { "BC": "3.25", "CC": "0", "EC": "0", "PC": "22.760219780219781", "SBC": "0.030952380952380953", "SC": "0.5", "SCC": "0", "SEC": "0", "SPC": "1.1979063042220937", "SSC": "0.01098901098901099", "distance_sum": "12.916666666666668", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "ENNIA" }, { "BC": "3.833333333333333", "CC": "0", "EC": "0", "PC": "19.199999999999999", "SBC": "0.036507936507936503", "SC": "3", "SCC": "0", "SEC": "0", "SPC": "1.2", "SSC": "0.065934065934065936", "distance_sum": "13.833333333333334", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 4, "label": "NS" }, { "BC": "3.8333333333333335", "CC": "0", "EC": "0", "PC": "14.083333333333334", "SBC": "0.03650793650793651", "SC": "0.5", "SCC": "0", "SEC": "0", "SPC": "0.93888888888888888", "SSC": "0.01098901098901099", "distance_sum": "16.083333333333332", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 5, "label": "BUHRT" }, { "BC": "1", "CC": "0", "EC": "0", "PC": "15.9", "SBC": "0.0095238095238095247", "SC": "0.5", "SCC": "0", "SEC": "0", "SPC": "1.0600000000000001", "SSC": "0.01098901098901099", "distance_sum": "15.166666666666666", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 6, "label": "AGO" }, { "BC": "9.3333333333333321", "CC": "0", "EC": "0", "PC": "17.666666666666668", "SBC": "0.088888888888888878", "SC": "9.5", "SCC": "0", "SEC": "0", "SPC": "1.1777777777777778", "SSC": "0.2087912087912088", "distance_sum": "13.25", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 7, "label": "AKZO" }, { "BC": "1.0833333333333333", "CC": "0", "EC": "0", "PC": "16.233333333333334", "SBC": "0.010317460317460317", "SC": "1.5", "SCC": "0", "SEC": "0", "SPC": "1.0822222222222222", "SSC": "0.032967032967032968", "distance_sum": "14.666666666666666", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 8, "label": "NB" }, { "BC": "3.0833333333333335", "CC": "0", "EC": "0", "PC": "17.066666666666666", "SBC": "0.029365079365079365", "SC": "5", "SCC": "0", "SEC": "0", "SPC": "1.1377777777777778", "SSC": "0.10989010989010989", "distance_sum": "14.166666666666666", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 9, "label": "SHV" }, { "BC": "0.75", "CC": "0", "EC": "0", "PC": "11.916883116883117", "SBC": "0.0071428571428571426", "SC": "1.5", "SCC": "0", "SEC": "0", "SPC": "0.70099312452253626", "SSC": "0.032967032967032968", "distance_sum": "20.5", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 10, "label": "FGH" }, { "BC": "3.6666666666666665", "CC": "0", "EC": "0", "PC": "22.833333333333332", "SBC": "0.034920634920634921", "SC": "2", "SCC": "0", "SEC": "0", "SPC": "1.2017543859649122", "SSC": "0.043956043956043959", "distance_sum": "11.916666666666666", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 11, "label": "HEINK" }, { "BC": "0.83333333333333326", "CC": "0", "EC": "0", "PC": "13.25", "SBC": "0.0079365079365079361", "SC": "2", "SCC": "0", "SEC": "0", "SPC": "0.828125", "SSC": "0.043956043956043959", "distance_sum": "16.833333333333332", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 12, "label": "PHLPS" }, { "BC": "0.66666666666666663", "CC": "0", "EC": "0", "PC": "20.466666666666665", "SBC": "0.0063492063492063492", "SC": "0", "SCC": "0", "SEC": "0", "SPC": "1.2791666666666666", "SSC": "0", "distance_sum": "12.583333333333332", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 13, "label": "NATND" }, { "BC": "0.33333333333333331", "CC": "0", "EC": "0", "PC": "12.845238095238095", "SBC": "0.0031746031746031746", "SC": "1", "SCC": "0", "SEC": "0", "SPC": "0.75560224089635852", "SSC": "0.02197802197802198", "distance_sum": "18.333333333333332", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 14, "label": "OGEM" }, { "BC": "2.5", "CC": "0", "EC": "0", "PC": "19.399999999999999", "SBC": "0.023809523809523808", "SC": "5", "SCC": "0", "SEC": "0", "SPC": "1.1411764705882352", "SSC": "0.10989010989010989", "distance_sum": "13.166666666666666", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 15, "label": "RSV" }, { "BC": "0", "CC": "0", "EC": "0", "PC": "0", "SBC": "0", "SC": "0", "SCC": "0", "SEC": "0", "SPC": "0", "SSC": "0", "distance_sum": "0", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 16, "label": "NSU" } ], "run": { "computeCentralities": true, "considerWeights": true, "dropIsolates": false, "inverseWeights": true }, "schema_version": 1 } SocNetV-3.7/src/tools/baselines/io_roundtrip/000077500000000000000000000000001524402210300213065ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/io_roundtrip/Benchmark_BA_Directed_N500_m3__FT2_big.json000066400000000000000000013060311524402210300310000ustar00rootroot00000000000000{ "counts": { "links_sna": 1219, "nodes": 500, "ties_graph": 1219 }, "dataset": { "filetype": 2, "name": "Benchmark_BA_Directed_N500_m3.paj", "path": "src/data/Benchmark_BA_Directed_N500_m3.paj" }, "graph": { "directed": true, "relations": 1, "weighted": false }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 2, "load_ms": 10, "load_msg": "", "net_name": "scale-free", "ok": true }, "metrics": { "density": "0.0048857715430861721" }, "relations_bundle": [ { "index": 0, "name": "scale-free", "signature": { "edge_list": [ { "u": 2, "v": 1, "w": "1" }, { "u": 3, "v": 1, "w": "1" }, { "u": 3, "v": 2, "w": "1" }, { "u": 4, "v": 1, "w": "1" }, { "u": 4, "v": 2, "w": "1" }, { "u": 5, "v": 1, "w": "1" }, { "u": 5, "v": 3, "w": "1" }, { "u": 6, "v": 1, "w": "1" }, { "u": 6, "v": 2, "w": "1" }, { "u": 7, "v": 3, "w": "1" }, { "u": 7, "v": 6, "w": "1" }, { "u": 8, "v": 2, "w": "1" }, { "u": 8, "v": 3, "w": "1" }, { "u": 8, "v": 5, "w": "1" }, { "u": 9, "v": 2, "w": "1" }, { "u": 9, "v": 3, "w": "1" }, { "u": 10, "v": 1, "w": "1" }, { "u": 10, "v": 2, "w": "1" }, { "u": 10, "v": 6, "w": "1" }, { "u": 11, "v": 1, "w": "1" }, { "u": 11, "v": 2, "w": "1" }, { "u": 11, "v": 6, "w": "1" }, { "u": 12, "v": 1, "w": "1" }, { "u": 12, "v": 2, "w": "1" }, { "u": 12, "v": 3, "w": "1" }, { "u": 13, "v": 1, "w": "1" }, { "u": 13, "v": 2, "w": "1" }, { "u": 13, "v": 3, "w": "1" }, { "u": 14, "v": 1, "w": "1" }, { "u": 14, "v": 2, "w": "1" }, { "u": 14, "v": 3, "w": "1" }, { "u": 15, "v": 2, "w": "1" }, { "u": 15, "v": 11, "w": "1" }, { "u": 16, "v": 2, "w": "1" }, { "u": 16, "v": 6, "w": "1" }, { "u": 17, "v": 2, "w": "1" }, { "u": 17, "v": 3, "w": "1" }, { "u": 17, "v": 6, "w": "1" }, { "u": 18, "v": 2, "w": "1" }, { "u": 18, "v": 3, "w": "1" }, { "u": 18, "v": 6, "w": "1" }, { "u": 19, "v": 1, "w": "1" }, { "u": 19, "v": 11, "w": "1" }, { "u": 19, "v": 13, "w": "1" }, { "u": 20, "v": 2, "w": "1" }, { "u": 20, "v": 10, "w": "1" }, { "u": 20, "v": 14, "w": "1" }, { "u": 21, "v": 2, "w": "1" }, { "u": 21, "v": 5, "w": "1" }, { "u": 22, "v": 1, "w": "1" }, { "u": 22, "v": 2, "w": "1" }, { "u": 22, "v": 11, "w": "1" }, { "u": 23, "v": 2, "w": "1" }, { "u": 23, "v": 5, "w": "1" }, { "u": 24, "v": 2, "w": "1" }, { "u": 24, "v": 6, "w": "1" }, { "u": 25, "v": 3, "w": "1" }, { "u": 25, "v": 5, "w": "1" }, { "u": 25, "v": 6, "w": "1" }, { "u": 26, "v": 1, "w": "1" }, { "u": 26, "v": 2, "w": "1" }, { "u": 26, "v": 3, "w": "1" }, { "u": 27, "v": 1, "w": "1" }, { "u": 28, "v": 2, "w": "1" }, { "u": 29, "v": 1, "w": "1" }, { "u": 29, "v": 14, "w": "1" }, { "u": 30, "v": 1, "w": "1" }, { "u": 30, "v": 6, "w": "1" }, { "u": 30, "v": 11, "w": "1" }, { "u": 31, "v": 2, "w": "1" }, { "u": 31, "v": 6, "w": "1" }, { "u": 32, "v": 1, "w": "1" }, { "u": 32, "v": 3, "w": "1" }, { "u": 32, "v": 11, "w": "1" }, { "u": 33, "v": 1, "w": "1" }, { "u": 33, "v": 3, "w": "1" }, { "u": 33, "v": 13, "w": "1" }, { "u": 34, "v": 1, "w": "1" }, { "u": 34, "v": 5, "w": "1" }, { "u": 34, "v": 6, "w": "1" }, { "u": 35, "v": 1, "w": "1" }, { "u": 35, "v": 2, "w": "1" }, { "u": 35, "v": 3, "w": "1" }, { "u": 36, "v": 1, "w": "1" }, { "u": 36, "v": 2, "w": "1" }, { "u": 36, "v": 5, "w": "1" }, { "u": 37, "v": 1, "w": "1" }, { "u": 37, "v": 5, "w": "1" }, { "u": 37, "v": 6, "w": "1" }, { "u": 38, "v": 1, "w": "1" }, { "u": 38, "v": 2, "w": "1" }, { "u": 39, "v": 1, "w": "1" }, { "u": 39, "v": 2, "w": "1" }, { "u": 40, "v": 2, "w": "1" }, { "u": 41, "v": 1, "w": "1" }, { "u": 41, "v": 2, "w": "1" }, { "u": 41, "v": 11, "w": "1" }, { "u": 42, "v": 3, "w": "1" }, { "u": 42, "v": 6, "w": "1" }, { "u": 43, "v": 2, "w": "1" }, { "u": 43, "v": 3, "w": "1" }, { "u": 43, "v": 6, "w": "1" }, { "u": 44, "v": 1, "w": "1" }, { "u": 44, "v": 2, "w": "1" }, { "u": 45, "v": 1, "w": "1" }, { "u": 45, "v": 5, "w": "1" }, { "u": 46, "v": 1, "w": "1" }, { "u": 46, "v": 2, "w": "1" }, { "u": 46, "v": 11, "w": "1" }, { "u": 47, "v": 2, "w": "1" }, { "u": 47, "v": 11, "w": "1" }, { "u": 47, "v": 13, "w": "1" }, { "u": 48, "v": 1, "w": "1" }, { "u": 48, "v": 11, "w": "1" }, { "u": 49, "v": 1, "w": "1" }, { "u": 49, "v": 3, "w": "1" }, { "u": 49, "v": 11, "w": "1" }, { "u": 50, "v": 1, "w": "1" }, { "u": 50, "v": 2, "w": "1" }, { "u": 50, "v": 11, "w": "1" }, { "u": 51, "v": 1, "w": "1" }, { "u": 51, "v": 2, "w": "1" }, { "u": 51, "v": 11, "w": "1" }, { "u": 52, "v": 2, "w": "1" }, { "u": 52, "v": 6, "w": "1" }, { "u": 53, "v": 1, "w": "1" }, { "u": 53, "v": 11, "w": "1" }, { "u": 54, "v": 2, "w": "1" }, { "u": 54, "v": 3, "w": "1" }, { "u": 54, "v": 11, "w": "1" }, { "u": 55, "v": 2, "w": "1" }, { "u": 55, "v": 6, "w": "1" }, { "u": 55, "v": 11, "w": "1" }, { "u": 56, "v": 2, "w": "1" }, { "u": 56, "v": 5, "w": "1" }, { "u": 56, "v": 6, "w": "1" }, { "u": 57, "v": 2, "w": "1" }, { "u": 57, "v": 3, "w": "1" }, { "u": 57, "v": 6, "w": "1" }, { "u": 58, "v": 6, "w": "1" }, { "u": 58, "v": 11, "w": "1" }, { "u": 59, "v": 2, "w": "1" }, { "u": 59, "v": 11, "w": "1" }, { "u": 60, "v": 2, "w": "1" }, { "u": 60, "v": 3, "w": "1" }, { "u": 60, "v": 11, "w": "1" }, { "u": 61, "v": 2, "w": "1" }, { "u": 61, "v": 11, "w": "1" }, { "u": 62, "v": 1, "w": "1" }, { "u": 62, "v": 2, "w": "1" }, { "u": 62, "v": 6, "w": "1" }, { "u": 63, "v": 2, "w": "1" }, { "u": 63, "v": 5, "w": "1" }, { "u": 64, "v": 2, "w": "1" }, { "u": 64, "v": 6, "w": "1" }, { "u": 65, "v": 1, "w": "1" }, { "u": 65, "v": 6, "w": "1" }, { "u": 65, "v": 13, "w": "1" }, { "u": 66, "v": 1, "w": "1" }, { "u": 66, "v": 6, "w": "1" }, { "u": 67, "v": 1, "w": "1" }, { "u": 67, "v": 6, "w": "1" }, { "u": 67, "v": 11, "w": "1" }, { "u": 68, "v": 2, "w": "1" }, { "u": 68, "v": 13, "w": "1" }, { "u": 69, "v": 1, "w": "1" }, { "u": 69, "v": 6, "w": "1" }, { "u": 69, "v": 11, "w": "1" }, { "u": 70, "v": 2, "w": "1" }, { "u": 70, "v": 3, "w": "1" }, { "u": 70, "v": 11, "w": "1" }, { "u": 71, "v": 1, "w": "1" }, { "u": 71, "v": 6, "w": "1" }, { "u": 71, "v": 11, "w": "1" }, { "u": 72, "v": 2, "w": "1" }, { "u": 72, "v": 5, "w": "1" }, { "u": 73, "v": 1, "w": "1" }, { "u": 73, "v": 3, "w": "1" }, { "u": 73, "v": 6, "w": "1" }, { "u": 74, "v": 1, "w": "1" }, { "u": 74, "v": 6, "w": "1" }, { "u": 74, "v": 11, "w": "1" }, { "u": 75, "v": 2, "w": "1" }, { "u": 75, "v": 3, "w": "1" }, { "u": 76, "v": 1, "w": "1" }, { "u": 76, "v": 2, "w": "1" }, { "u": 76, "v": 6, "w": "1" }, { "u": 77, "v": 2, "w": "1" }, { "u": 77, "v": 6, "w": "1" }, { "u": 77, "v": 11, "w": "1" }, { "u": 78, "v": 1, "w": "1" }, { "u": 78, "v": 2, "w": "1" }, { "u": 78, "v": 6, "w": "1" }, { "u": 79, "v": 1, "w": "1" }, { "u": 79, "v": 6, "w": "1" }, { "u": 79, "v": 11, "w": "1" }, { "u": 80, "v": 2, "w": "1" }, { "u": 80, "v": 3, "w": "1" }, { "u": 81, "v": 1, "w": "1" }, { "u": 81, "v": 3, "w": "1" }, { "u": 82, "v": 3, "w": "1" }, { "u": 82, "v": 11, "w": "1" }, { "u": 83, "v": 1, "w": "1" }, { "u": 83, "v": 6, "w": "1" }, { "u": 84, "v": 1, "w": "1" }, { "u": 84, "v": 2, "w": "1" }, { "u": 84, "v": 6, "w": "1" }, { "u": 85, "v": 1, "w": "1" }, { "u": 85, "v": 3, "w": "1" }, { "u": 85, "v": 6, "w": "1" }, { "u": 86, "v": 1, "w": "1" }, { "u": 86, "v": 2, "w": "1" }, { "u": 86, "v": 11, "w": "1" }, { "u": 87, "v": 2, "w": "1" }, { "u": 87, "v": 3, "w": "1" }, { "u": 87, "v": 6, "w": "1" }, { "u": 88, "v": 2, "w": "1" }, { "u": 88, "v": 6, "w": "1" }, { "u": 89, "v": 2, "w": "1" }, { "u": 89, "v": 3, "w": "1" }, { "u": 89, "v": 6, "w": "1" }, { "u": 90, "v": 1, "w": "1" }, { "u": 90, "v": 2, "w": "1" }, { "u": 90, "v": 6, "w": "1" }, { "u": 91, "v": 2, "w": "1" }, { "u": 91, "v": 6, "w": "1" }, { "u": 91, "v": 11, "w": "1" }, { "u": 92, "v": 2, "w": "1" }, { "u": 92, "v": 3, "w": "1" }, { "u": 92, "v": 6, "w": "1" }, { "u": 93, "v": 1, "w": "1" }, { "u": 93, "v": 2, "w": "1" }, { "u": 93, "v": 11, "w": "1" }, { "u": 94, "v": 2, "w": "1" }, { "u": 94, "v": 11, "w": "1" }, { "u": 95, "v": 1, "w": "1" }, { "u": 95, "v": 2, "w": "1" }, { "u": 96, "v": 2, "w": "1" }, { "u": 96, "v": 5, "w": "1" }, { "u": 97, "v": 3, "w": "1" }, { "u": 97, "v": 5, "w": "1" }, { "u": 98, "v": 1, "w": "1" }, { "u": 99, "v": 1, "w": "1" }, { "u": 99, "v": 2, "w": "1" }, { "u": 100, "v": 1, "w": "1" }, { "u": 100, "v": 2, "w": "1" }, { "u": 100, "v": 6, "w": "1" }, { "u": 101, "v": 1, "w": "1" }, { "u": 101, "v": 2, "w": "1" }, { "u": 101, "v": 3, "w": "1" }, { "u": 102, "v": 1, "w": "1" }, { "u": 102, "v": 2, "w": "1" }, { "u": 103, "v": 3, "w": "1" }, { "u": 103, "v": 6, "w": "1" }, { "u": 104, "v": 1, "w": "1" }, { "u": 104, "v": 2, "w": "1" }, { "u": 104, "v": 6, "w": "1" }, { "u": 105, "v": 1, "w": "1" }, { "u": 105, "v": 6, "w": "1" }, { "u": 106, "v": 2, "w": "1" }, { "u": 106, "v": 6, "w": "1" }, { "u": 107, "v": 2, "w": "1" }, { "u": 107, "v": 6, "w": "1" }, { "u": 108, "v": 1, "w": "1" }, { "u": 108, "v": 2, "w": "1" }, { "u": 108, "v": 3, "w": "1" }, { "u": 109, "v": 2, "w": "1" }, { "u": 109, "v": 5, "w": "1" }, { "u": 109, "v": 6, "w": "1" }, { "u": 110, "v": 1, "w": "1" }, { "u": 110, "v": 2, "w": "1" }, { "u": 110, "v": 3, "w": "1" }, { "u": 111, "v": 1, "w": "1" }, { "u": 111, "v": 2, "w": "1" }, { "u": 112, "v": 1, "w": "1" }, { "u": 112, "v": 2, "w": "1" }, { "u": 113, "v": 1, "w": "1" }, { "u": 113, "v": 2, "w": "1" }, { "u": 114, "v": 1, "w": "1" }, { "u": 114, "v": 2, "w": "1" }, { "u": 115, "v": 2, "w": "1" }, { "u": 115, "v": 6, "w": "1" }, { "u": 115, "v": 11, "w": "1" }, { "u": 116, "v": 2, "w": "1" }, { "u": 116, "v": 6, "w": "1" }, { "u": 116, "v": 11, "w": "1" }, { "u": 117, "v": 2, "w": "1" }, { "u": 117, "v": 6, "w": "1" }, { "u": 118, "v": 2, "w": "1" }, { "u": 119, "v": 1, "w": "1" }, { "u": 119, "v": 2, "w": "1" }, { "u": 119, "v": 6, "w": "1" }, { "u": 120, "v": 1, "w": "1" }, { "u": 120, "v": 2, "w": "1" }, { "u": 120, "v": 6, "w": "1" }, { "u": 121, "v": 2, "w": "1" }, { "u": 121, "v": 6, "w": "1" }, { "u": 121, "v": 11, "w": "1" }, { "u": 122, "v": 1, "w": "1" }, { "u": 122, "v": 2, "w": "1" }, { "u": 123, "v": 2, "w": "1" }, { "u": 123, "v": 6, "w": "1" }, { "u": 124, "v": 1, "w": "1" }, { "u": 124, "v": 2, "w": "1" }, { "u": 124, "v": 3, "w": "1" }, { "u": 125, "v": 1, "w": "1" }, { "u": 125, "v": 2, "w": "1" }, { "u": 126, "v": 1, "w": "1" }, { "u": 126, "v": 2, "w": "1" }, { "u": 126, "v": 6, "w": "1" }, { "u": 127, "v": 1, "w": "1" }, { "u": 127, "v": 2, "w": "1" }, { "u": 128, "v": 2, "w": "1" }, { "u": 128, "v": 3, "w": "1" }, { "u": 128, "v": 5, "w": "1" }, { "u": 129, "v": 1, "w": "1" }, { "u": 129, "v": 5, "w": "1" }, { "u": 129, "v": 11, "w": "1" }, { "u": 130, "v": 2, "w": "1" }, { "u": 130, "v": 3, "w": "1" }, { "u": 130, "v": 6, "w": "1" }, { "u": 131, "v": 2, "w": "1" }, { "u": 131, "v": 3, "w": "1" }, { "u": 131, "v": 11, "w": "1" }, { "u": 132, "v": 1, "w": "1" }, { "u": 132, "v": 3, "w": "1" }, { "u": 133, "v": 2, "w": "1" }, { "u": 133, "v": 5, "w": "1" }, { "u": 134, "v": 2, "w": "1" }, { "u": 135, "v": 1, "w": "1" }, { "u": 135, "v": 2, "w": "1" }, { "u": 135, "v": 11, "w": "1" }, { "u": 136, "v": 1, "w": "1" }, { "u": 136, "v": 2, "w": "1" }, { "u": 137, "v": 1, "w": "1" }, { "u": 138, "v": 1, "w": "1" }, { "u": 138, "v": 3, "w": "1" }, { "u": 139, "v": 2, "w": "1" }, { "u": 139, "v": 11, "w": "1" }, { "u": 140, "v": 1, "w": "1" }, { "u": 140, "v": 2, "w": "1" }, { "u": 141, "v": 3, "w": "1" }, { "u": 141, "v": 6, "w": "1" }, { "u": 142, "v": 1, "w": "1" }, { "u": 142, "v": 2, "w": "1" }, { "u": 143, "v": 3, "w": "1" }, { "u": 143, "v": 5, "w": "1" }, { "u": 144, "v": 2, "w": "1" }, { "u": 144, "v": 6, "w": "1" }, { "u": 145, "v": 2, "w": "1" }, { "u": 145, "v": 3, "w": "1" }, { "u": 146, "v": 1, "w": "1" }, { "u": 146, "v": 2, "w": "1" }, { "u": 147, "v": 1, "w": "1" }, { "u": 147, "v": 2, "w": "1" }, { "u": 147, "v": 11, "w": "1" }, { "u": 148, "v": 6, "w": "1" }, { "u": 149, "v": 2, "w": "1" }, { "u": 149, "v": 3, "w": "1" }, { "u": 150, "v": 1, "w": "1" }, { "u": 150, "v": 6, "w": "1" }, { "u": 151, "v": 3, "w": "1" }, { "u": 151, "v": 11, "w": "1" }, { "u": 152, "v": 1, "w": "1" }, { "u": 152, "v": 2, "w": "1" }, { "u": 152, "v": 3, "w": "1" }, { "u": 153, "v": 1, "w": "1" }, { "u": 153, "v": 6, "w": "1" }, { "u": 154, "v": 1, "w": "1" }, { "u": 155, "v": 1, "w": "1" }, { "u": 155, "v": 2, "w": "1" }, { "u": 155, "v": 6, "w": "1" }, { "u": 156, "v": 2, "w": "1" }, { "u": 156, "v": 6, "w": "1" }, { "u": 156, "v": 11, "w": "1" }, { "u": 157, "v": 1, "w": "1" }, { "u": 157, "v": 2, "w": "1" }, { "u": 157, "v": 3, "w": "1" }, { "u": 158, "v": 1, "w": "1" }, { "u": 158, "v": 6, "w": "1" }, { "u": 158, "v": 11, "w": "1" }, { "u": 159, "v": 1, "w": "1" }, { "u": 159, "v": 11, "w": "1" }, { "u": 160, "v": 1, "w": "1" }, { "u": 160, "v": 3, "w": "1" }, { "u": 160, "v": 6, "w": "1" }, { "u": 161, "v": 2, "w": "1" }, { "u": 161, "v": 6, "w": "1" }, { "u": 162, "v": 1, "w": "1" }, { "u": 162, "v": 2, "w": "1" }, { "u": 163, "v": 2, "w": "1" }, { "u": 163, "v": 6, "w": "1" }, { "u": 164, "v": 3, "w": "1" }, { "u": 164, "v": 5, "w": "1" }, { "u": 164, "v": 6, "w": "1" }, { "u": 165, "v": 1, "w": "1" }, { "u": 165, "v": 2, "w": "1" }, { "u": 166, "v": 2, "w": "1" }, { "u": 166, "v": 3, "w": "1" }, { "u": 166, "v": 6, "w": "1" }, { "u": 167, "v": 1, "w": "1" }, { "u": 167, "v": 2, "w": "1" }, { "u": 167, "v": 3, "w": "1" }, { "u": 168, "v": 1, "w": "1" }, { "u": 168, "v": 5, "w": "1" }, { "u": 169, "v": 1, "w": "1" }, { "u": 169, "v": 2, "w": "1" }, { "u": 169, "v": 6, "w": "1" }, { "u": 170, "v": 2, "w": "1" }, { "u": 170, "v": 3, "w": "1" }, { "u": 170, "v": 6, "w": "1" }, { "u": 171, "v": 1, "w": "1" }, { "u": 171, "v": 2, "w": "1" }, { "u": 172, "v": 1, "w": "1" }, { "u": 172, "v": 2, "w": "1" }, { "u": 173, "v": 1, "w": "1" }, { "u": 173, "v": 2, "w": "1" }, { "u": 174, "v": 2, "w": "1" }, { "u": 174, "v": 11, "w": "1" }, { "u": 175, "v": 1, "w": "1" }, { "u": 175, "v": 6, "w": "1" }, { "u": 175, "v": 11, "w": "1" }, { "u": 176, "v": 2, "w": "1" }, { "u": 176, "v": 6, "w": "1" }, { "u": 177, "v": 1, "w": "1" }, { "u": 177, "v": 11, "w": "1" }, { "u": 178, "v": 2, "w": "1" }, { "u": 178, "v": 3, "w": "1" }, { "u": 178, "v": 11, "w": "1" }, { "u": 179, "v": 1, "w": "1" }, { "u": 179, "v": 3, "w": "1" }, { "u": 180, "v": 1, "w": "1" }, { "u": 180, "v": 2, "w": "1" }, { "u": 181, "v": 1, "w": "1" }, { "u": 181, "v": 6, "w": "1" }, { "u": 181, "v": 11, "w": "1" }, { "u": 182, "v": 1, "w": "1" }, { "u": 182, "v": 2, "w": "1" }, { "u": 183, "v": 1, "w": "1" }, { "u": 183, "v": 2, "w": "1" }, { "u": 184, "v": 1, "w": "1" }, { "u": 185, "v": 2, "w": "1" }, { "u": 185, "v": 3, "w": "1" }, { "u": 185, "v": 5, "w": "1" }, { "u": 186, "v": 1, "w": "1" }, { "u": 186, "v": 6, "w": "1" }, { "u": 186, "v": 11, "w": "1" }, { "u": 187, "v": 1, "w": "1" }, { "u": 187, "v": 6, "w": "1" }, { "u": 188, "v": 1, "w": "1" }, { "u": 188, "v": 3, "w": "1" }, { "u": 188, "v": 5, "w": "1" }, { "u": 189, "v": 1, "w": "1" }, { "u": 189, "v": 11, "w": "1" }, { "u": 190, "v": 1, "w": "1" }, { "u": 190, "v": 2, "w": "1" }, { "u": 190, "v": 6, "w": "1" }, { "u": 191, "v": 2, "w": "1" }, { "u": 191, "v": 6, "w": "1" }, { "u": 192, "v": 1, "w": "1" }, { "u": 193, "v": 2, "w": "1" }, { "u": 193, "v": 6, "w": "1" }, { "u": 193, "v": 11, "w": "1" }, { "u": 194, "v": 2, "w": "1" }, { "u": 195, "v": 2, "w": "1" }, { "u": 195, "v": 11, "w": "1" }, { "u": 196, "v": 1, "w": "1" }, { "u": 196, "v": 11, "w": "1" }, { "u": 197, "v": 1, "w": "1" }, { "u": 197, "v": 6, "w": "1" }, { "u": 198, "v": 2, "w": "1" }, { "u": 198, "v": 5, "w": "1" }, { "u": 199, "v": 1, "w": "1" }, { "u": 199, "v": 2, "w": "1" }, { "u": 199, "v": 6, "w": "1" }, { "u": 200, "v": 1, "w": "1" }, { "u": 200, "v": 2, "w": "1" }, { "u": 200, "v": 3, "w": "1" }, { "u": 201, "v": 2, "w": "1" }, { "u": 201, "v": 11, "w": "1" }, { "u": 202, "v": 2, "w": "1" }, { "u": 202, "v": 6, "w": "1" }, { "u": 203, "v": 1, "w": "1" }, { "u": 203, "v": 2, "w": "1" }, { "u": 203, "v": 6, "w": "1" }, { "u": 204, "v": 2, "w": "1" }, { "u": 204, "v": 5, "w": "1" }, { "u": 204, "v": 6, "w": "1" }, { "u": 205, "v": 1, "w": "1" }, { "u": 205, "v": 2, "w": "1" }, { "u": 205, "v": 11, "w": "1" }, { "u": 206, "v": 1, "w": "1" }, { "u": 206, "v": 2, "w": "1" }, { "u": 206, "v": 3, "w": "1" }, { "u": 207, "v": 1, "w": "1" }, { "u": 207, "v": 3, "w": "1" }, { "u": 207, "v": 6, "w": "1" }, { "u": 208, "v": 1, "w": "1" }, { "u": 208, "v": 3, "w": "1" }, { "u": 208, "v": 6, "w": "1" }, { "u": 209, "v": 1, "w": "1" }, { "u": 209, "v": 2, "w": "1" }, { "u": 210, "v": 1, "w": "1" }, { "u": 210, "v": 3, "w": "1" }, { "u": 211, "v": 1, "w": "1" }, { "u": 211, "v": 2, "w": "1" }, { "u": 212, "v": 1, "w": "1" }, { "u": 212, "v": 6, "w": "1" }, { "u": 213, "v": 2, "w": "1" }, { "u": 213, "v": 6, "w": "1" }, { "u": 214, "v": 3, "w": "1" }, { "u": 214, "v": 11, "w": "1" }, { "u": 215, "v": 2, "w": "1" }, { "u": 215, "v": 3, "w": "1" }, { "u": 215, "v": 11, "w": "1" }, { "u": 216, "v": 1, "w": "1" }, { "u": 216, "v": 3, "w": "1" }, { "u": 216, "v": 6, "w": "1" }, { "u": 217, "v": 1, "w": "1" }, { "u": 217, "v": 2, "w": "1" }, { "u": 218, "v": 2, "w": "1" }, { "u": 218, "v": 6, "w": "1" }, { "u": 219, "v": 2, "w": "1" }, { "u": 219, "v": 6, "w": "1" }, { "u": 220, "v": 2, "w": "1" }, { "u": 220, "v": 3, "w": "1" }, { "u": 220, "v": 6, "w": "1" }, { "u": 221, "v": 1, "w": "1" }, { "u": 221, "v": 2, "w": "1" }, { "u": 221, "v": 11, "w": "1" }, { "u": 222, "v": 2, "w": "1" }, { "u": 222, "v": 11, "w": "1" }, { "u": 223, "v": 2, "w": "1" }, { "u": 223, "v": 11, "w": "1" }, { "u": 224, "v": 1, "w": "1" }, { "u": 224, "v": 2, "w": "1" }, { "u": 225, "v": 3, "w": "1" }, { "u": 225, "v": 6, "w": "1" }, { "u": 225, "v": 11, "w": "1" }, { "u": 226, "v": 2, "w": "1" }, { "u": 227, "v": 1, "w": "1" }, { "u": 227, "v": 2, "w": "1" }, { "u": 227, "v": 3, "w": "1" }, { "u": 228, "v": 1, "w": "1" }, { "u": 228, "v": 2, "w": "1" }, { "u": 228, "v": 5, "w": "1" }, { "u": 229, "v": 2, "w": "1" }, { "u": 229, "v": 6, "w": "1" }, { "u": 230, "v": 1, "w": "1" }, { "u": 230, "v": 2, "w": "1" }, { "u": 230, "v": 5, "w": "1" }, { "u": 231, "v": 1, "w": "1" }, { "u": 231, "v": 2, "w": "1" }, { "u": 231, "v": 3, "w": "1" }, { "u": 232, "v": 1, "w": "1" }, { "u": 233, "v": 2, "w": "1" }, { "u": 233, "v": 6, "w": "1" }, { "u": 234, "v": 1, "w": "1" }, { "u": 234, "v": 2, "w": "1" }, { "u": 235, "v": 2, "w": "1" }, { "u": 235, "v": 3, "w": "1" }, { "u": 235, "v": 6, "w": "1" }, { "u": 236, "v": 1, "w": "1" }, { "u": 236, "v": 2, "w": "1" }, { "u": 236, "v": 11, "w": "1" }, { "u": 237, "v": 1, "w": "1" }, { "u": 237, "v": 11, "w": "1" }, { "u": 238, "v": 2, "w": "1" }, { "u": 238, "v": 3, "w": "1" }, { "u": 239, "v": 2, "w": "1" }, { "u": 239, "v": 3, "w": "1" }, { "u": 239, "v": 6, "w": "1" }, { "u": 240, "v": 2, "w": "1" }, { "u": 240, "v": 3, "w": "1" }, { "u": 241, "v": 2, "w": "1" }, { "u": 241, "v": 3, "w": "1" }, { "u": 241, "v": 11, "w": "1" }, { "u": 242, "v": 1, "w": "1" }, { "u": 242, "v": 6, "w": "1" }, { "u": 242, "v": 11, "w": "1" }, { "u": 243, "v": 1, "w": "1" }, { "u": 243, "v": 3, "w": "1" }, { "u": 243, "v": 6, "w": "1" }, { "u": 244, "v": 1, "w": "1" }, { "u": 245, "v": 3, "w": "1" }, { "u": 245, "v": 11, "w": "1" }, { "u": 246, "v": 2, "w": "1" }, { "u": 246, "v": 3, "w": "1" }, { "u": 246, "v": 5, "w": "1" }, { "u": 247, "v": 2, "w": "1" }, { "u": 247, "v": 3, "w": "1" }, { "u": 247, "v": 11, "w": "1" }, { "u": 248, "v": 1, "w": "1" }, { "u": 248, "v": 2, "w": "1" }, { "u": 248, "v": 6, "w": "1" }, { "u": 249, "v": 3, "w": "1" }, { "u": 249, "v": 11, "w": "1" }, { "u": 250, "v": 2, "w": "1" }, { "u": 250, "v": 3, "w": "1" }, { "u": 250, "v": 11, "w": "1" }, { "u": 251, "v": 1, "w": "1" }, { "u": 251, "v": 6, "w": "1" }, { "u": 252, "v": 1, "w": "1" }, { "u": 252, "v": 2, "w": "1" }, { "u": 252, "v": 6, "w": "1" }, { "u": 253, "v": 2, "w": "1" }, { "u": 253, "v": 3, "w": "1" }, { "u": 253, "v": 6, "w": "1" }, { "u": 254, "v": 1, "w": "1" }, { "u": 254, "v": 2, "w": "1" }, { "u": 254, "v": 3, "w": "1" }, { "u": 255, "v": 2, "w": "1" }, { "u": 255, "v": 11, "w": "1" }, { "u": 256, "v": 3, "w": "1" }, { "u": 256, "v": 5, "w": "1" }, { "u": 256, "v": 11, "w": "1" }, { "u": 257, "v": 1, "w": "1" }, { "u": 257, "v": 11, "w": "1" }, { "u": 258, "v": 1, "w": "1" }, { "u": 258, "v": 2, "w": "1" }, { "u": 258, "v": 6, "w": "1" }, { "u": 259, "v": 1, "w": "1" }, { "u": 259, "v": 2, "w": "1" }, { "u": 259, "v": 3, "w": "1" }, { "u": 260, "v": 1, "w": "1" }, { "u": 260, "v": 5, "w": "1" }, { "u": 260, "v": 6, "w": "1" }, { "u": 261, "v": 2, "w": "1" }, { "u": 261, "v": 3, "w": "1" }, { "u": 261, "v": 5, "w": "1" }, { "u": 262, "v": 2, "w": "1" }, { "u": 262, "v": 3, "w": "1" }, { "u": 262, "v": 6, "w": "1" }, { "u": 263, "v": 2, "w": "1" }, { "u": 263, "v": 6, "w": "1" }, { "u": 264, "v": 1, "w": "1" }, { "u": 264, "v": 6, "w": "1" }, { "u": 265, "v": 1, "w": "1" }, { "u": 265, "v": 2, "w": "1" }, { "u": 265, "v": 3, "w": "1" }, { "u": 266, "v": 2, "w": "1" }, { "u": 266, "v": 3, "w": "1" }, { "u": 266, "v": 11, "w": "1" }, { "u": 267, "v": 1, "w": "1" }, { "u": 267, "v": 2, "w": "1" }, { "u": 267, "v": 5, "w": "1" }, { "u": 268, "v": 1, "w": "1" }, { "u": 268, "v": 3, "w": "1" }, { "u": 268, "v": 11, "w": "1" }, { "u": 269, "v": 1, "w": "1" }, { "u": 270, "v": 1, "w": "1" }, { "u": 270, "v": 3, "w": "1" }, { "u": 271, "v": 1, "w": "1" }, { "u": 271, "v": 2, "w": "1" }, { "u": 271, "v": 11, "w": "1" }, { "u": 272, "v": 2, "w": "1" }, { "u": 272, "v": 5, "w": "1" }, { "u": 272, "v": 6, "w": "1" }, { "u": 273, "v": 1, "w": "1" }, { "u": 273, "v": 3, "w": "1" }, { "u": 274, "v": 2, "w": "1" }, { "u": 274, "v": 11, "w": "1" }, { "u": 275, "v": 1, "w": "1" }, { "u": 275, "v": 3, "w": "1" }, { "u": 275, "v": 11, "w": "1" }, { "u": 276, "v": 2, "w": "1" }, { "u": 276, "v": 3, "w": "1" }, { "u": 276, "v": 6, "w": "1" }, { "u": 277, "v": 1, "w": "1" }, { "u": 277, "v": 2, "w": "1" }, { "u": 277, "v": 3, "w": "1" }, { "u": 278, "v": 1, "w": "1" }, { "u": 278, "v": 2, "w": "1" }, { "u": 278, "v": 3, "w": "1" }, { "u": 279, "v": 1, "w": "1" }, { "u": 279, "v": 3, "w": "1" }, { "u": 279, "v": 6, "w": "1" }, { "u": 280, "v": 1, "w": "1" }, { "u": 280, "v": 2, "w": "1" }, { "u": 281, "v": 2, "w": "1" }, { "u": 281, "v": 3, "w": "1" }, { "u": 282, "v": 1, "w": "1" }, { "u": 282, "v": 6, "w": "1" }, { "u": 283, "v": 1, "w": "1" }, { "u": 283, "v": 2, "w": "1" }, { "u": 283, "v": 3, "w": "1" }, { "u": 284, "v": 1, "w": "1" }, { "u": 284, "v": 3, "w": "1" }, { "u": 285, "v": 1, "w": "1" }, { "u": 285, "v": 11, "w": "1" }, { "u": 286, "v": 1, "w": "1" }, { "u": 286, "v": 3, "w": "1" }, { "u": 286, "v": 6, "w": "1" }, { "u": 287, "v": 1, "w": "1" }, { "u": 287, "v": 3, "w": "1" }, { "u": 288, "v": 1, "w": "1" }, { "u": 288, "v": 11, "w": "1" }, { "u": 289, "v": 3, "w": "1" }, { "u": 289, "v": 6, "w": "1" }, { "u": 290, "v": 1, "w": "1" }, { "u": 290, "v": 6, "w": "1" }, { "u": 290, "v": 11, "w": "1" }, { "u": 291, "v": 2, "w": "1" }, { "u": 291, "v": 3, "w": "1" }, { "u": 292, "v": 2, "w": "1" }, { "u": 292, "v": 3, "w": "1" }, { "u": 292, "v": 5, "w": "1" }, { "u": 293, "v": 1, "w": "1" }, { "u": 293, "v": 3, "w": "1" }, { "u": 293, "v": 11, "w": "1" }, { "u": 294, "v": 2, "w": "1" }, { "u": 294, "v": 5, "w": "1" }, { "u": 295, "v": 1, "w": "1" }, { "u": 295, "v": 3, "w": "1" }, { "u": 295, "v": 6, "w": "1" }, { "u": 296, "v": 1, "w": "1" }, { "u": 296, "v": 2, "w": "1" }, { "u": 297, "v": 1, "w": "1" }, { "u": 297, "v": 2, "w": "1" }, { "u": 297, "v": 5, "w": "1" }, { "u": 298, "v": 2, "w": "1" }, { "u": 298, "v": 6, "w": "1" }, { "u": 299, "v": 1, "w": "1" }, { "u": 299, "v": 3, "w": "1" }, { "u": 300, "v": 2, "w": "1" }, { "u": 301, "v": 1, "w": "1" }, { "u": 301, "v": 6, "w": "1" }, { "u": 302, "v": 1, "w": "1" }, { "u": 302, "v": 2, "w": "1" }, { "u": 302, "v": 5, "w": "1" }, { "u": 303, "v": 1, "w": "1" }, { "u": 303, "v": 2, "w": "1" }, { "u": 303, "v": 6, "w": "1" }, { "u": 304, "v": 1, "w": "1" }, { "u": 304, "v": 2, "w": "1" }, { "u": 304, "v": 6, "w": "1" }, { "u": 305, "v": 1, "w": "1" }, { "u": 305, "v": 2, "w": "1" }, { "u": 306, "v": 2, "w": "1" }, { "u": 306, "v": 3, "w": "1" }, { "u": 306, "v": 6, "w": "1" }, { "u": 307, "v": 1, "w": "1" }, { "u": 307, "v": 2, "w": "1" }, { "u": 308, "v": 1, "w": "1" }, { "u": 308, "v": 2, "w": "1" }, { "u": 308, "v": 11, "w": "1" }, { "u": 309, "v": 1, "w": "1" }, { "u": 309, "v": 5, "w": "1" }, { "u": 309, "v": 6, "w": "1" }, { "u": 310, "v": 1, "w": "1" }, { "u": 310, "v": 6, "w": "1" }, { "u": 311, "v": 1, "w": "1" }, { "u": 311, "v": 2, "w": "1" }, { "u": 312, "v": 1, "w": "1" }, { "u": 312, "v": 2, "w": "1" }, { "u": 312, "v": 6, "w": "1" }, { "u": 313, "v": 1, "w": "1" }, { "u": 313, "v": 2, "w": "1" }, { "u": 314, "v": 1, "w": "1" }, { "u": 314, "v": 3, "w": "1" }, { "u": 314, "v": 6, "w": "1" }, { "u": 315, "v": 1, "w": "1" }, { "u": 315, "v": 6, "w": "1" }, { "u": 315, "v": 11, "w": "1" }, { "u": 316, "v": 1, "w": "1" }, { "u": 316, "v": 2, "w": "1" }, { "u": 316, "v": 3, "w": "1" }, { "u": 317, "v": 1, "w": "1" }, { "u": 317, "v": 3, "w": "1" }, { "u": 317, "v": 6, "w": "1" }, { "u": 318, "v": 2, "w": "1" }, { "u": 318, "v": 3, "w": "1" }, { "u": 318, "v": 11, "w": "1" }, { "u": 319, "v": 2, "w": "1" }, { "u": 319, "v": 3, "w": "1" }, { "u": 320, "v": 2, "w": "1" }, { "u": 320, "v": 3, "w": "1" }, { "u": 320, "v": 6, "w": "1" }, { "u": 321, "v": 2, "w": "1" }, { "u": 321, "v": 5, "w": "1" }, { "u": 321, "v": 6, "w": "1" }, { "u": 322, "v": 1, "w": "1" }, { "u": 322, "v": 2, "w": "1" }, { "u": 323, "v": 1, "w": "1" }, { "u": 323, "v": 2, "w": "1" }, { "u": 324, "v": 1, "w": "1" }, { "u": 324, "v": 6, "w": "1" }, { "u": 325, "v": 1, "w": "1" }, { "u": 326, "v": 1, "w": "1" }, { "u": 326, "v": 6, "w": "1" }, { "u": 327, "v": 1, "w": "1" }, { "u": 327, "v": 3, "w": "1" }, { "u": 327, "v": 6, "w": "1" }, { "u": 328, "v": 1, "w": "1" }, { "u": 328, "v": 6, "w": "1" }, { "u": 328, "v": 11, "w": "1" }, { "u": 329, "v": 1, "w": "1" }, { "u": 329, "v": 2, "w": "1" }, { "u": 330, "v": 1, "w": "1" }, { "u": 330, "v": 2, "w": "1" }, { "u": 331, "v": 1, "w": "1" }, { "u": 331, "v": 2, "w": "1" }, { "u": 332, "v": 1, "w": "1" }, { "u": 332, "v": 2, "w": "1" }, { "u": 333, "v": 2, "w": "1" }, { "u": 333, "v": 3, "w": "1" }, { "u": 333, "v": 11, "w": "1" }, { "u": 334, "v": 2, "w": "1" }, { "u": 334, "v": 3, "w": "1" }, { "u": 334, "v": 11, "w": "1" }, { "u": 335, "v": 1, "w": "1" }, { "u": 335, "v": 2, "w": "1" }, { "u": 335, "v": 6, "w": "1" }, { "u": 336, "v": 2, "w": "1" }, { "u": 336, "v": 6, "w": "1" }, { "u": 337, "v": 2, "w": "1" }, { "u": 337, "v": 6, "w": "1" }, { "u": 338, "v": 3, "w": "1" }, { "u": 338, "v": 6, "w": "1" }, { "u": 338, "v": 11, "w": "1" }, { "u": 339, "v": 1, "w": "1" }, { "u": 339, "v": 2, "w": "1" }, { "u": 339, "v": 6, "w": "1" }, { "u": 340, "v": 1, "w": "1" }, { "u": 340, "v": 3, "w": "1" }, { "u": 340, "v": 6, "w": "1" }, { "u": 341, "v": 5, "w": "1" }, { "u": 341, "v": 6, "w": "1" }, { "u": 342, "v": 2, "w": "1" }, { "u": 343, "v": 1, "w": "1" }, { "u": 344, "v": 1, "w": "1" }, { "u": 344, "v": 2, "w": "1" }, { "u": 344, "v": 3, "w": "1" }, { "u": 345, "v": 1, "w": "1" }, { "u": 345, "v": 2, "w": "1" }, { "u": 345, "v": 6, "w": "1" }, { "u": 346, "v": 1, "w": "1" }, { "u": 346, "v": 11, "w": "1" }, { "u": 347, "v": 2, "w": "1" }, { "u": 347, "v": 6, "w": "1" }, { "u": 348, "v": 2, "w": "1" }, { "u": 348, "v": 5, "w": "1" }, { "u": 348, "v": 11, "w": "1" }, { "u": 349, "v": 1, "w": "1" }, { "u": 349, "v": 2, "w": "1" }, { "u": 349, "v": 11, "w": "1" }, { "u": 350, "v": 1, "w": "1" }, { "u": 350, "v": 3, "w": "1" }, { "u": 351, "v": 2, "w": "1" }, { "u": 351, "v": 6, "w": "1" }, { "u": 351, "v": 11, "w": "1" }, { "u": 352, "v": 2, "w": "1" }, { "u": 352, "v": 3, "w": "1" }, { "u": 352, "v": 11, "w": "1" }, { "u": 353, "v": 1, "w": "1" }, { "u": 353, "v": 2, "w": "1" }, { "u": 354, "v": 1, "w": "1" }, { "u": 354, "v": 2, "w": "1" }, { "u": 355, "v": 1, "w": "1" }, { "u": 355, "v": 11, "w": "1" }, { "u": 356, "v": 2, "w": "1" }, { "u": 356, "v": 3, "w": "1" }, { "u": 356, "v": 6, "w": "1" }, { "u": 357, "v": 2, "w": "1" }, { "u": 357, "v": 3, "w": "1" }, { "u": 358, "v": 2, "w": "1" }, { "u": 358, "v": 5, "w": "1" }, { "u": 358, "v": 11, "w": "1" }, { "u": 359, "v": 2, "w": "1" }, { "u": 359, "v": 11, "w": "1" }, { "u": 360, "v": 1, "w": "1" }, { "u": 360, "v": 3, "w": "1" }, { "u": 361, "v": 1, "w": "1" }, { "u": 361, "v": 2, "w": "1" }, { "u": 362, "v": 2, "w": "1" }, { "u": 362, "v": 3, "w": "1" }, { "u": 363, "v": 1, "w": "1" }, { "u": 363, "v": 6, "w": "1" }, { "u": 363, "v": 11, "w": "1" }, { "u": 364, "v": 3, "w": "1" }, { "u": 364, "v": 5, "w": "1" }, { "u": 364, "v": 6, "w": "1" }, { "u": 365, "v": 1, "w": "1" }, { "u": 365, "v": 2, "w": "1" }, { "u": 365, "v": 5, "w": "1" }, { "u": 366, "v": 2, "w": "1" }, { "u": 366, "v": 3, "w": "1" }, { "u": 367, "v": 1, "w": "1" }, { "u": 367, "v": 2, "w": "1" }, { "u": 367, "v": 11, "w": "1" }, { "u": 368, "v": 1, "w": "1" }, { "u": 368, "v": 6, "w": "1" }, { "u": 369, "v": 1, "w": "1" }, { "u": 369, "v": 6, "w": "1" }, { "u": 370, "v": 2, "w": "1" }, { "u": 370, "v": 3, "w": "1" }, { "u": 370, "v": 6, "w": "1" }, { "u": 371, "v": 1, "w": "1" }, { "u": 371, "v": 2, "w": "1" }, { "u": 371, "v": 6, "w": "1" }, { "u": 372, "v": 1, "w": "1" }, { "u": 372, "v": 2, "w": "1" }, { "u": 372, "v": 6, "w": "1" }, { "u": 373, "v": 1, "w": "1" }, { "u": 373, "v": 2, "w": "1" }, { "u": 374, "v": 1, "w": "1" }, { "u": 374, "v": 5, "w": "1" }, { "u": 374, "v": 11, "w": "1" }, { "u": 375, "v": 2, "w": "1" }, { "u": 375, "v": 6, "w": "1" }, { "u": 376, "v": 2, "w": "1" }, { "u": 376, "v": 5, "w": "1" }, { "u": 376, "v": 6, "w": "1" }, { "u": 377, "v": 1, "w": "1" }, { "u": 377, "v": 2, "w": "1" }, { "u": 378, "v": 1, "w": "1" }, { "u": 378, "v": 11, "w": "1" }, { "u": 379, "v": 1, "w": "1" }, { "u": 379, "v": 6, "w": "1" }, { "u": 379, "v": 11, "w": "1" }, { "u": 380, "v": 1, "w": "1" }, { "u": 380, "v": 2, "w": "1" }, { "u": 381, "v": 1, "w": "1" }, { "u": 381, "v": 2, "w": "1" }, { "u": 381, "v": 6, "w": "1" }, { "u": 382, "v": 1, "w": "1" }, { "u": 382, "v": 2, "w": "1" }, { "u": 383, "v": 1, "w": "1" }, { "u": 383, "v": 2, "w": "1" }, { "u": 384, "v": 1, "w": "1" }, { "u": 384, "v": 2, "w": "1" }, { "u": 384, "v": 6, "w": "1" }, { "u": 385, "v": 3, "w": "1" }, { "u": 385, "v": 6, "w": "1" }, { "u": 385, "v": 11, "w": "1" }, { "u": 386, "v": 1, "w": "1" }, { "u": 386, "v": 3, "w": "1" }, { "u": 386, "v": 11, "w": "1" }, { "u": 387, "v": 2, "w": "1" }, { "u": 387, "v": 6, "w": "1" }, { "u": 388, "v": 2, "w": "1" }, { "u": 388, "v": 11, "w": "1" }, { "u": 389, "v": 1, "w": "1" }, { "u": 389, "v": 11, "w": "1" }, { "u": 390, "v": 1, "w": "1" }, { "u": 390, "v": 3, "w": "1" }, { "u": 391, "v": 1, "w": "1" }, { "u": 391, "v": 2, "w": "1" }, { "u": 391, "v": 3, "w": "1" }, { "u": 392, "v": 3, "w": "1" }, { "u": 392, "v": 6, "w": "1" }, { "u": 393, "v": 6, "w": "1" }, { "u": 393, "v": 11, "w": "1" }, { "u": 394, "v": 2, "w": "1" }, { "u": 394, "v": 6, "w": "1" }, { "u": 394, "v": 11, "w": "1" }, { "u": 395, "v": 1, "w": "1" }, { "u": 395, "v": 2, "w": "1" }, { "u": 395, "v": 6, "w": "1" }, { "u": 396, "v": 1, "w": "1" }, { "u": 396, "v": 2, "w": "1" }, { "u": 396, "v": 6, "w": "1" }, { "u": 397, "v": 2, "w": "1" }, { "u": 397, "v": 11, "w": "1" }, { "u": 398, "v": 2, "w": "1" }, { "u": 398, "v": 6, "w": "1" }, { "u": 399, "v": 2, "w": "1" }, { "u": 399, "v": 3, "w": "1" }, { "u": 399, "v": 6, "w": "1" }, { "u": 400, "v": 1, "w": "1" }, { "u": 400, "v": 11, "w": "1" }, { "u": 401, "v": 2, "w": "1" }, { "u": 401, "v": 6, "w": "1" }, { "u": 402, "v": 1, "w": "1" }, { "u": 402, "v": 2, "w": "1" }, { "u": 402, "v": 3, "w": "1" }, { "u": 403, "v": 1, "w": "1" }, { "u": 403, "v": 2, "w": "1" }, { "u": 403, "v": 11, "w": "1" }, { "u": 404, "v": 1, "w": "1" }, { "u": 405, "v": 1, "w": "1" }, { "u": 405, "v": 2, "w": "1" }, { "u": 405, "v": 3, "w": "1" }, { "u": 406, "v": 1, "w": "1" }, { "u": 406, "v": 2, "w": "1" }, { "u": 406, "v": 3, "w": "1" }, { "u": 407, "v": 1, "w": "1" }, { "u": 407, "v": 2, "w": "1" }, { "u": 407, "v": 6, "w": "1" }, { "u": 408, "v": 1, "w": "1" }, { "u": 408, "v": 2, "w": "1" }, { "u": 408, "v": 3, "w": "1" }, { "u": 409, "v": 2, "w": "1" }, { "u": 409, "v": 6, "w": "1" }, { "u": 409, "v": 11, "w": "1" }, { "u": 410, "v": 1, "w": "1" }, { "u": 410, "v": 2, "w": "1" }, { "u": 410, "v": 5, "w": "1" }, { "u": 411, "v": 1, "w": "1" }, { "u": 411, "v": 3, "w": "1" }, { "u": 411, "v": 6, "w": "1" }, { "u": 412, "v": 2, "w": "1" }, { "u": 412, "v": 3, "w": "1" }, { "u": 412, "v": 6, "w": "1" }, { "u": 413, "v": 1, "w": "1" }, { "u": 413, "v": 2, "w": "1" }, { "u": 414, "v": 1, "w": "1" }, { "u": 414, "v": 2, "w": "1" }, { "u": 415, "v": 1, "w": "1" }, { "u": 415, "v": 2, "w": "1" }, { "u": 415, "v": 6, "w": "1" }, { "u": 416, "v": 2, "w": "1" }, { "u": 416, "v": 6, "w": "1" }, { "u": 416, "v": 11, "w": "1" }, { "u": 417, "v": 2, "w": "1" }, { "u": 417, "v": 3, "w": "1" }, { "u": 417, "v": 11, "w": "1" }, { "u": 418, "v": 1, "w": "1" }, { "u": 418, "v": 11, "w": "1" }, { "u": 419, "v": 1, "w": "1" }, { "u": 419, "v": 3, "w": "1" }, { "u": 419, "v": 6, "w": "1" }, { "u": 420, "v": 1, "w": "1" }, { "u": 420, "v": 2, "w": "1" }, { "u": 421, "v": 1, "w": "1" }, { "u": 421, "v": 6, "w": "1" }, { "u": 422, "v": 2, "w": "1" }, { "u": 422, "v": 3, "w": "1" }, { "u": 422, "v": 6, "w": "1" }, { "u": 423, "v": 1, "w": "1" }, { "u": 423, "v": 3, "w": "1" }, { "u": 424, "v": 1, "w": "1" }, { "u": 424, "v": 6, "w": "1" }, { "u": 425, "v": 1, "w": "1" }, { "u": 425, "v": 2, "w": "1" }, { "u": 425, "v": 6, "w": "1" }, { "u": 426, "v": 1, "w": "1" }, { "u": 426, "v": 2, "w": "1" }, { "u": 427, "v": 1, "w": "1" }, { "u": 427, "v": 11, "w": "1" }, { "u": 428, "v": 3, "w": "1" }, { "u": 428, "v": 6, "w": "1" }, { "u": 428, "v": 11, "w": "1" }, { "u": 429, "v": 1, "w": "1" }, { "u": 429, "v": 2, "w": "1" }, { "u": 429, "v": 11, "w": "1" }, { "u": 430, "v": 2, "w": "1" }, { "u": 430, "v": 3, "w": "1" }, { "u": 431, "v": 2, "w": "1" }, { "u": 431, "v": 3, "w": "1" }, { "u": 431, "v": 6, "w": "1" }, { "u": 432, "v": 2, "w": "1" }, { "u": 432, "v": 3, "w": "1" }, { "u": 432, "v": 6, "w": "1" }, { "u": 433, "v": 1, "w": "1" }, { "u": 433, "v": 2, "w": "1" }, { "u": 434, "v": 2, "w": "1" }, { "u": 434, "v": 3, "w": "1" }, { "u": 435, "v": 1, "w": "1" }, { "u": 435, "v": 6, "w": "1" }, { "u": 436, "v": 1, "w": "1" }, { "u": 436, "v": 2, "w": "1" }, { "u": 436, "v": 3, "w": "1" }, { "u": 437, "v": 2, "w": "1" }, { "u": 437, "v": 6, "w": "1" }, { "u": 437, "v": 11, "w": "1" }, { "u": 438, "v": 1, "w": "1" }, { "u": 438, "v": 6, "w": "1" }, { "u": 439, "v": 1, "w": "1" }, { "u": 439, "v": 2, "w": "1" }, { "u": 439, "v": 6, "w": "1" }, { "u": 440, "v": 3, "w": "1" }, { "u": 440, "v": 6, "w": "1" }, { "u": 441, "v": 2, "w": "1" }, { "u": 441, "v": 6, "w": "1" }, { "u": 442, "v": 2, "w": "1" }, { "u": 443, "v": 5, "w": "1" }, { "u": 443, "v": 6, "w": "1" }, { "u": 443, "v": 11, "w": "1" }, { "u": 444, "v": 1, "w": "1" }, { "u": 444, "v": 2, "w": "1" }, { "u": 444, "v": 3, "w": "1" }, { "u": 445, "v": 2, "w": "1" }, { "u": 445, "v": 3, "w": "1" }, { "u": 445, "v": 6, "w": "1" }, { "u": 446, "v": 2, "w": "1" }, { "u": 446, "v": 3, "w": "1" }, { "u": 446, "v": 11, "w": "1" }, { "u": 447, "v": 2, "w": "1" }, { "u": 447, "v": 6, "w": "1" }, { "u": 447, "v": 11, "w": "1" }, { "u": 448, "v": 2, "w": "1" }, { "u": 448, "v": 3, "w": "1" }, { "u": 448, "v": 5, "w": "1" }, { "u": 449, "v": 1, "w": "1" }, { "u": 449, "v": 6, "w": "1" }, { "u": 449, "v": 11, "w": "1" }, { "u": 450, "v": 3, "w": "1" }, { "u": 450, "v": 6, "w": "1" }, { "u": 451, "v": 1, "w": "1" }, { "u": 451, "v": 2, "w": "1" }, { "u": 451, "v": 6, "w": "1" }, { "u": 452, "v": 1, "w": "1" }, { "u": 452, "v": 2, "w": "1" }, { "u": 452, "v": 6, "w": "1" }, { "u": 453, "v": 1, "w": "1" }, { "u": 453, "v": 2, "w": "1" }, { "u": 453, "v": 6, "w": "1" }, { "u": 454, "v": 1, "w": "1" }, { "u": 454, "v": 2, "w": "1" }, { "u": 455, "v": 1, "w": "1" }, { "u": 455, "v": 3, "w": "1" }, { "u": 456, "v": 2, "w": "1" }, { "u": 456, "v": 3, "w": "1" }, { "u": 456, "v": 6, "w": "1" }, { "u": 457, "v": 1, "w": "1" }, { "u": 457, "v": 3, "w": "1" }, { "u": 457, "v": 6, "w": "1" }, { "u": 458, "v": 2, "w": "1" }, { "u": 458, "v": 3, "w": "1" }, { "u": 459, "v": 1, "w": "1" }, { "u": 459, "v": 6, "w": "1" }, { "u": 460, "v": 1, "w": "1" }, { "u": 460, "v": 2, "w": "1" }, { "u": 461, "v": 1, "w": "1" }, { "u": 461, "v": 2, "w": "1" }, { "u": 461, "v": 3, "w": "1" }, { "u": 462, "v": 1, "w": "1" }, { "u": 462, "v": 3, "w": "1" }, { "u": 462, "v": 5, "w": "1" }, { "u": 463, "v": 1, "w": "1" }, { "u": 463, "v": 11, "w": "1" }, { "u": 464, "v": 1, "w": "1" }, { "u": 464, "v": 6, "w": "1" }, { "u": 465, "v": 1, "w": "1" }, { "u": 465, "v": 2, "w": "1" }, { "u": 466, "v": 1, "w": "1" }, { "u": 466, "v": 11, "w": "1" }, { "u": 467, "v": 1, "w": "1" }, { "u": 467, "v": 6, "w": "1" }, { "u": 468, "v": 1, "w": "1" }, { "u": 468, "v": 5, "w": "1" }, { "u": 469, "v": 1, "w": "1" }, { "u": 469, "v": 2, "w": "1" }, { "u": 469, "v": 3, "w": "1" }, { "u": 470, "v": 2, "w": "1" }, { "u": 470, "v": 3, "w": "1" }, { "u": 470, "v": 11, "w": "1" }, { "u": 471, "v": 1, "w": "1" }, { "u": 471, "v": 3, "w": "1" }, { "u": 471, "v": 6, "w": "1" }, { "u": 472, "v": 1, "w": "1" }, { "u": 472, "v": 3, "w": "1" }, { "u": 473, "v": 1, "w": "1" }, { "u": 473, "v": 2, "w": "1" }, { "u": 473, "v": 6, "w": "1" }, { "u": 474, "v": 6, "w": "1" }, { "u": 474, "v": 11, "w": "1" }, { "u": 475, "v": 2, "w": "1" }, { "u": 475, "v": 6, "w": "1" }, { "u": 475, "v": 11, "w": "1" }, { "u": 476, "v": 1, "w": "1" }, { "u": 476, "v": 5, "w": "1" }, { "u": 477, "v": 1, "w": "1" }, { "u": 477, "v": 2, "w": "1" }, { "u": 477, "v": 6, "w": "1" }, { "u": 478, "v": 2, "w": "1" }, { "u": 478, "v": 3, "w": "1" }, { "u": 478, "v": 6, "w": "1" }, { "u": 479, "v": 1, "w": "1" }, { "u": 479, "v": 2, "w": "1" }, { "u": 480, "v": 1, "w": "1" }, { "u": 480, "v": 6, "w": "1" }, { "u": 481, "v": 1, "w": "1" }, { "u": 481, "v": 5, "w": "1" }, { "u": 481, "v": 11, "w": "1" }, { "u": 482, "v": 1, "w": "1" }, { "u": 482, "v": 2, "w": "1" }, { "u": 482, "v": 3, "w": "1" }, { "u": 483, "v": 1, "w": "1" }, { "u": 483, "v": 2, "w": "1" }, { "u": 484, "v": 1, "w": "1" }, { "u": 484, "v": 2, "w": "1" }, { "u": 484, "v": 6, "w": "1" }, { "u": 485, "v": 1, "w": "1" }, { "u": 485, "v": 2, "w": "1" }, { "u": 486, "v": 1, "w": "1" }, { "u": 486, "v": 5, "w": "1" }, { "u": 487, "v": 2, "w": "1" }, { "u": 487, "v": 3, "w": "1" }, { "u": 488, "v": 2, "w": "1" }, { "u": 488, "v": 11, "w": "1" }, { "u": 489, "v": 2, "w": "1" }, { "u": 489, "v": 5, "w": "1" }, { "u": 490, "v": 1, "w": "1" }, { "u": 490, "v": 11, "w": "1" }, { "u": 491, "v": 1, "w": "1" }, { "u": 491, "v": 6, "w": "1" }, { "u": 491, "v": 11, "w": "1" }, { "u": 492, "v": 1, "w": "1" }, { "u": 492, "v": 6, "w": "1" }, { "u": 493, "v": 1, "w": "1" }, { "u": 493, "v": 2, "w": "1" }, { "u": 493, "v": 11, "w": "1" }, { "u": 494, "v": 1, "w": "1" }, { "u": 494, "v": 2, "w": "1" }, { "u": 495, "v": 2, "w": "1" }, { "u": 496, "v": 2, "w": "1" }, { "u": 496, "v": 3, "w": "1" }, { "u": 497, "v": 1, "w": "1" }, { "u": 497, "v": 2, "w": "1" }, { "u": 497, "v": 11, "w": "1" }, { "u": 498, "v": 1, "w": "1" }, { "u": 498, "v": 2, "w": "1" }, { "u": 498, "v": 6, "w": "1" }, { "u": 499, "v": 1, "w": "1" }, { "u": 499, "v": 11, "w": "1" }, { "u": 500, "v": 1, "w": "1" }, { "u": 500, "v": 6, "w": "1" } ], "hash_sha256": "2075a9bd34e0bdb3d0ca82ff9e433bf05852da9650fbe8e5a28054812ad83b02", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "100", "101", "102", "103", "104", "105", "106", "107", "108", "109", "110", "111", "112", "113", "114", "115", "116", "117", "118", "119", "120", "121", "122", "123", "124", "125", "126", "127", "128", "129", "130", "131", "132", "133", "134", "135", "136", "137", "138", "139", "140", "141", "142", "143", "144", "145", "146", "147", "148", "149", "150", "151", "152", "153", "154", "155", "156", "157", "158", "159", "160", "161", "162", "163", "164", "165", "166", "167", "168", "169", "170", "171", "172", "173", "174", "175", "176", "177", "178", "179", "180", "181", "182", "183", "184", "185", "186", "187", "188", "189", "190", "191", "192", "193", "194", "195", "196", "197", "198", "199", "200", "201", "202", "203", "204", "205", "206", "207", "208", "209", "210", "211", "212", "213", "214", "215", "216", "217", "218", "219", "220", "221", "222", "223", "224", "225", "226", "227", "228", "229", "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", "240", "241", "242", "243", "244", "245", "246", "247", "248", "249", "250", "251", "252", "253", "254", "255", "256", "257", "258", "259", "260", "261", "262", "263", "264", "265", "266", "267", "268", "269", "270", "271", "272", "273", "274", "275", "276", "277", "278", "279", "280", "281", "282", "283", "284", "285", "286", "287", "288", "289", "290", "291", "292", "293", "294", "295", "296", "297", "298", "299", "300", "301", "302", "303", "304", "305", "306", "307", "308", "309", "310", "311", "312", "313", "314", "315", "316", "317", "318", "319", "320", "321", "322", "323", "324", "325", "326", "327", "328", "329", "330", "331", "332", "333", "334", "335", "336", "337", "338", "339", "340", "341", "342", "343", "344", "345", "346", "347", "348", "349", "350", "351", "352", "353", "354", "355", "356", "357", "358", "359", "360", "361", "362", "363", "364", "365", "366", "367", "368", "369", "370", "371", "372", "373", "374", "375", "376", "377", "378", "379", "380", "381", "382", "383", "384", "385", "386", "387", "388", "389", "390", "391", "392", "393", "394", "395", "396", "397", "398", "399", "400", "401", "402", "403", "404", "405", "406", "407", "408", "409", "410", "411", "412", "413", "414", "415", "416", "417", "418", "419", "420", "421", "422", "423", "424", "425", "426", "427", "428", "429", "430", "431", "432", "433", "434", "435", "436", "437", "438", "439", "440", "441", "442", "443", "444", "445", "446", "447", "448", "449", "450", "451", "452", "453", "454", "455", "456", "457", "458", "459", "460", "461", "462", "463", "464", "465", "466", "467", "468", "469", "470", "471", "472", "473", "474", "475", "476", "477", "478", "479", "480", "481", "482", "483", "484", "485", "486", "487", "488", "489", "490", "491", "492", "493", "494", "495", "496", "497", "498", "499", "500" ] }, "ties_graph": 1219 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 2, "v": 1, "w": "1" }, { "u": 3, "v": 1, "w": "1" }, { "u": 3, "v": 2, "w": "1" }, { "u": 4, "v": 1, "w": "1" }, { "u": 4, "v": 2, "w": "1" }, { "u": 5, "v": 1, "w": "1" }, { "u": 5, "v": 3, "w": "1" }, { "u": 6, "v": 1, "w": "1" }, { "u": 6, "v": 2, "w": "1" }, { "u": 7, "v": 3, "w": "1" }, { "u": 7, "v": 6, "w": "1" }, { "u": 8, "v": 2, "w": "1" }, { "u": 8, "v": 3, "w": "1" }, { "u": 8, "v": 5, "w": "1" }, { "u": 9, "v": 2, "w": "1" }, { "u": 9, "v": 3, "w": "1" }, { "u": 10, "v": 1, "w": "1" }, { "u": 10, "v": 2, "w": "1" }, { "u": 10, "v": 6, "w": "1" }, { "u": 11, "v": 1, "w": "1" }, { "u": 11, "v": 2, "w": "1" }, { "u": 11, "v": 6, "w": "1" }, { "u": 12, "v": 1, "w": "1" }, { "u": 12, "v": 2, "w": "1" }, { "u": 12, "v": 3, "w": "1" }, { "u": 13, "v": 1, "w": "1" }, { "u": 13, "v": 2, "w": "1" }, { "u": 13, "v": 3, "w": "1" }, { "u": 14, "v": 1, "w": "1" }, { "u": 14, "v": 2, "w": "1" }, { "u": 14, "v": 3, "w": "1" }, { "u": 15, "v": 2, "w": "1" }, { "u": 15, "v": 11, "w": "1" }, { "u": 16, "v": 2, "w": "1" }, { "u": 16, "v": 6, "w": "1" }, { "u": 17, "v": 2, "w": "1" }, { "u": 17, "v": 3, "w": "1" }, { "u": 17, "v": 6, "w": "1" }, { "u": 18, "v": 2, "w": "1" }, { "u": 18, "v": 3, "w": "1" }, { "u": 18, "v": 6, "w": "1" }, { "u": 19, "v": 1, "w": "1" }, { "u": 19, "v": 11, "w": "1" }, { "u": 19, "v": 13, "w": "1" }, { "u": 20, "v": 2, "w": "1" }, { "u": 20, "v": 10, "w": "1" }, { "u": 20, "v": 14, "w": "1" }, { "u": 21, "v": 2, "w": "1" }, { "u": 21, "v": 5, "w": "1" }, { "u": 22, "v": 1, "w": "1" }, { "u": 22, "v": 2, "w": "1" }, { "u": 22, "v": 11, "w": "1" }, { "u": 23, "v": 2, "w": "1" }, { "u": 23, "v": 5, "w": "1" }, { "u": 24, "v": 2, "w": "1" }, { "u": 24, "v": 6, "w": "1" }, { "u": 25, "v": 3, "w": "1" }, { "u": 25, "v": 5, "w": "1" }, { "u": 25, "v": 6, "w": "1" }, { "u": 26, "v": 1, "w": "1" }, { "u": 26, "v": 2, "w": "1" }, { "u": 26, "v": 3, "w": "1" }, { "u": 27, "v": 1, "w": "1" }, { "u": 28, "v": 2, "w": "1" }, { "u": 29, "v": 1, "w": "1" }, { "u": 29, "v": 14, "w": "1" }, { "u": 30, "v": 1, "w": "1" }, { "u": 30, "v": 6, "w": "1" }, { "u": 30, "v": 11, "w": "1" }, { "u": 31, "v": 2, "w": "1" }, { "u": 31, "v": 6, "w": "1" }, { "u": 32, "v": 1, "w": "1" }, { "u": 32, "v": 3, "w": "1" }, { "u": 32, "v": 11, "w": "1" }, { "u": 33, "v": 1, "w": "1" }, { "u": 33, "v": 3, "w": "1" }, { "u": 33, "v": 13, "w": "1" }, { "u": 34, "v": 1, "w": "1" }, { "u": 34, "v": 5, "w": "1" }, { "u": 34, "v": 6, "w": "1" }, { "u": 35, "v": 1, "w": "1" }, { "u": 35, "v": 2, "w": "1" }, { "u": 35, "v": 3, "w": "1" }, { "u": 36, "v": 1, "w": "1" }, { "u": 36, "v": 2, "w": "1" }, { "u": 36, "v": 5, "w": "1" }, { "u": 37, "v": 1, "w": "1" }, { "u": 37, "v": 5, "w": "1" }, { "u": 37, "v": 6, "w": "1" }, { "u": 38, "v": 1, "w": "1" }, { "u": 38, "v": 2, "w": "1" }, { "u": 39, "v": 1, "w": "1" }, { "u": 39, "v": 2, "w": "1" }, { "u": 40, "v": 2, "w": "1" }, { "u": 41, "v": 1, "w": "1" }, { "u": 41, "v": 2, "w": "1" }, { "u": 41, "v": 11, "w": "1" }, { "u": 42, "v": 3, "w": "1" }, { "u": 42, "v": 6, "w": "1" }, { "u": 43, "v": 2, "w": "1" }, { "u": 43, "v": 3, "w": "1" }, { "u": 43, "v": 6, "w": "1" }, { "u": 44, "v": 1, "w": "1" }, { "u": 44, "v": 2, "w": "1" }, { "u": 45, "v": 1, "w": "1" }, { "u": 45, "v": 5, "w": "1" }, { "u": 46, "v": 1, "w": "1" }, { "u": 46, "v": 2, "w": "1" }, { "u": 46, "v": 11, "w": "1" }, { "u": 47, "v": 2, "w": "1" }, { "u": 47, "v": 11, "w": "1" }, { "u": 47, "v": 13, "w": "1" }, { "u": 48, "v": 1, "w": "1" }, { "u": 48, "v": 11, "w": "1" }, { "u": 49, "v": 1, "w": "1" }, { "u": 49, "v": 3, "w": "1" }, { "u": 49, "v": 11, "w": "1" }, { "u": 50, "v": 1, "w": "1" }, { "u": 50, "v": 2, "w": "1" }, { "u": 50, "v": 11, "w": "1" }, { "u": 51, "v": 1, "w": "1" }, { "u": 51, "v": 2, "w": "1" }, { "u": 51, "v": 11, "w": "1" }, { "u": 52, "v": 2, "w": "1" }, { "u": 52, "v": 6, "w": "1" }, { "u": 53, "v": 1, "w": "1" }, { "u": 53, "v": 11, "w": "1" }, { "u": 54, "v": 2, "w": "1" }, { "u": 54, "v": 3, "w": "1" }, { "u": 54, "v": 11, "w": "1" }, { "u": 55, "v": 2, "w": "1" }, { "u": 55, "v": 6, "w": "1" }, { "u": 55, "v": 11, "w": "1" }, { "u": 56, "v": 2, "w": "1" }, { "u": 56, "v": 5, "w": "1" }, { "u": 56, "v": 6, "w": "1" }, { "u": 57, "v": 2, "w": "1" }, { "u": 57, "v": 3, "w": "1" }, { "u": 57, "v": 6, "w": "1" }, { "u": 58, "v": 6, "w": "1" }, { "u": 58, "v": 11, "w": "1" }, { "u": 59, "v": 2, "w": "1" }, { "u": 59, "v": 11, "w": "1" }, { "u": 60, "v": 2, "w": "1" }, { "u": 60, "v": 3, "w": "1" }, { "u": 60, "v": 11, "w": "1" }, { "u": 61, "v": 2, "w": "1" }, { "u": 61, "v": 11, "w": "1" }, { "u": 62, "v": 1, "w": "1" }, { "u": 62, "v": 2, "w": "1" }, { "u": 62, "v": 6, "w": "1" }, { "u": 63, "v": 2, "w": "1" }, { "u": 63, "v": 5, "w": "1" }, { "u": 64, "v": 2, "w": "1" }, { "u": 64, "v": 6, "w": "1" }, { "u": 65, "v": 1, "w": "1" }, { "u": 65, "v": 6, "w": "1" }, { "u": 65, "v": 13, "w": "1" }, { "u": 66, "v": 1, "w": "1" }, { "u": 66, "v": 6, "w": "1" }, { "u": 67, "v": 1, "w": "1" }, { "u": 67, "v": 6, "w": "1" }, { "u": 67, "v": 11, "w": "1" }, { "u": 68, "v": 2, "w": "1" }, { "u": 68, "v": 13, "w": "1" }, { "u": 69, "v": 1, "w": "1" }, { "u": 69, "v": 6, "w": "1" }, { "u": 69, "v": 11, "w": "1" }, { "u": 70, "v": 2, "w": "1" }, { "u": 70, "v": 3, "w": "1" }, { "u": 70, "v": 11, "w": "1" }, { "u": 71, "v": 1, "w": "1" }, { "u": 71, "v": 6, "w": "1" }, { "u": 71, "v": 11, "w": "1" }, { "u": 72, "v": 2, "w": "1" }, { "u": 72, "v": 5, "w": "1" }, { "u": 73, "v": 1, "w": "1" }, { "u": 73, "v": 3, "w": "1" }, { "u": 73, "v": 6, "w": "1" }, { "u": 74, "v": 1, "w": "1" }, { "u": 74, "v": 6, "w": "1" }, { "u": 74, "v": 11, "w": "1" }, { "u": 75, "v": 2, "w": "1" }, { "u": 75, "v": 3, "w": "1" }, { "u": 76, "v": 1, "w": "1" }, { "u": 76, "v": 2, "w": "1" }, { "u": 76, "v": 6, "w": "1" }, { "u": 77, "v": 2, "w": "1" }, { "u": 77, "v": 6, "w": "1" }, { "u": 77, "v": 11, "w": "1" }, { "u": 78, "v": 1, "w": "1" }, { "u": 78, "v": 2, "w": "1" }, { "u": 78, "v": 6, "w": "1" }, { "u": 79, "v": 1, "w": "1" }, { "u": 79, "v": 6, "w": "1" }, { "u": 79, "v": 11, "w": "1" }, { "u": 80, "v": 2, "w": "1" }, { "u": 80, "v": 3, "w": "1" }, { "u": 81, "v": 1, "w": "1" }, { "u": 81, "v": 3, "w": "1" }, { "u": 82, "v": 3, "w": "1" }, { "u": 82, "v": 11, "w": "1" }, { "u": 83, "v": 1, "w": "1" }, { "u": 83, "v": 6, "w": "1" }, { "u": 84, "v": 1, "w": "1" }, { "u": 84, "v": 2, "w": "1" }, { "u": 84, "v": 6, "w": "1" }, { "u": 85, "v": 1, "w": "1" }, { "u": 85, "v": 3, "w": "1" }, { "u": 85, "v": 6, "w": "1" }, { "u": 86, "v": 1, "w": "1" }, { "u": 86, "v": 2, "w": "1" }, { "u": 86, "v": 11, "w": "1" }, { "u": 87, "v": 2, "w": "1" }, { "u": 87, "v": 3, "w": "1" }, { "u": 87, "v": 6, "w": "1" }, { "u": 88, "v": 2, "w": "1" }, { "u": 88, "v": 6, "w": "1" }, { "u": 89, "v": 2, "w": "1" }, { "u": 89, "v": 3, "w": "1" }, { "u": 89, "v": 6, "w": "1" }, { "u": 90, "v": 1, "w": "1" }, { "u": 90, "v": 2, "w": "1" }, { "u": 90, "v": 6, "w": "1" }, { "u": 91, "v": 2, "w": "1" }, { "u": 91, "v": 6, "w": "1" }, { "u": 91, "v": 11, "w": "1" }, { "u": 92, "v": 2, "w": "1" }, { "u": 92, "v": 3, "w": "1" }, { "u": 92, "v": 6, "w": "1" }, { "u": 93, "v": 1, "w": "1" }, { "u": 93, "v": 2, "w": "1" }, { "u": 93, "v": 11, "w": "1" }, { "u": 94, "v": 2, "w": "1" }, { "u": 94, "v": 11, "w": "1" }, { "u": 95, "v": 1, "w": "1" }, { "u": 95, "v": 2, "w": "1" }, { "u": 96, "v": 2, "w": "1" }, { "u": 96, "v": 5, "w": "1" }, { "u": 97, "v": 3, "w": "1" }, { "u": 97, "v": 5, "w": "1" }, { "u": 98, "v": 1, "w": "1" }, { "u": 99, "v": 1, "w": "1" }, { "u": 99, "v": 2, "w": "1" }, { "u": 100, "v": 1, "w": "1" }, { "u": 100, "v": 2, "w": "1" }, { "u": 100, "v": 6, "w": "1" }, { "u": 101, "v": 1, "w": "1" }, { "u": 101, "v": 2, "w": "1" }, { "u": 101, "v": 3, "w": "1" }, { "u": 102, "v": 1, "w": "1" }, { "u": 102, "v": 2, "w": "1" }, { "u": 103, "v": 3, "w": "1" }, { "u": 103, "v": 6, "w": "1" }, { "u": 104, "v": 1, "w": "1" }, { "u": 104, "v": 2, "w": "1" }, { "u": 104, "v": 6, "w": "1" }, { "u": 105, "v": 1, "w": "1" }, { "u": 105, "v": 6, "w": "1" }, { "u": 106, "v": 2, "w": "1" }, { "u": 106, "v": 6, "w": "1" }, { "u": 107, "v": 2, "w": "1" }, { "u": 107, "v": 6, "w": "1" }, { "u": 108, "v": 1, "w": "1" }, { "u": 108, "v": 2, "w": "1" }, { "u": 108, "v": 3, "w": "1" }, { "u": 109, "v": 2, "w": "1" }, { "u": 109, "v": 5, "w": "1" }, { "u": 109, "v": 6, "w": "1" }, { "u": 110, "v": 1, "w": "1" }, { "u": 110, "v": 2, "w": "1" }, { "u": 110, "v": 3, "w": "1" }, { "u": 111, "v": 1, "w": "1" }, { "u": 111, "v": 2, "w": "1" }, { "u": 112, "v": 1, "w": "1" }, { "u": 112, "v": 2, "w": "1" }, { "u": 113, "v": 1, "w": "1" }, { "u": 113, "v": 2, "w": "1" }, { "u": 114, "v": 1, "w": "1" }, { "u": 114, "v": 2, "w": "1" }, { "u": 115, "v": 2, "w": "1" }, { "u": 115, "v": 6, "w": "1" }, { "u": 115, "v": 11, "w": "1" }, { "u": 116, "v": 2, "w": "1" }, { "u": 116, "v": 6, "w": "1" }, { "u": 116, "v": 11, "w": "1" }, { "u": 117, "v": 2, "w": "1" }, { "u": 117, "v": 6, "w": "1" }, { "u": 118, "v": 2, "w": "1" }, { "u": 119, "v": 1, "w": "1" }, { "u": 119, "v": 2, "w": "1" }, { "u": 119, "v": 6, "w": "1" }, { "u": 120, "v": 1, "w": "1" }, { "u": 120, "v": 2, "w": "1" }, { "u": 120, "v": 6, "w": "1" }, { "u": 121, "v": 2, "w": "1" }, { "u": 121, "v": 6, "w": "1" }, { "u": 121, "v": 11, "w": "1" }, { "u": 122, "v": 1, "w": "1" }, { "u": 122, "v": 2, "w": "1" }, { "u": 123, "v": 2, "w": "1" }, { "u": 123, "v": 6, "w": "1" }, { "u": 124, "v": 1, "w": "1" }, { "u": 124, "v": 2, "w": "1" }, { "u": 124, "v": 3, "w": "1" }, { "u": 125, "v": 1, "w": "1" }, { "u": 125, "v": 2, "w": "1" }, { "u": 126, "v": 1, "w": "1" }, { "u": 126, "v": 2, "w": "1" }, { "u": 126, "v": 6, "w": "1" }, { "u": 127, "v": 1, "w": "1" }, { "u": 127, "v": 2, "w": "1" }, { "u": 128, "v": 2, "w": "1" }, { "u": 128, "v": 3, "w": "1" }, { "u": 128, "v": 5, "w": "1" }, { "u": 129, "v": 1, "w": "1" }, { "u": 129, "v": 5, "w": "1" }, { "u": 129, "v": 11, "w": "1" }, { "u": 130, "v": 2, "w": "1" }, { "u": 130, "v": 3, "w": "1" }, { "u": 130, "v": 6, "w": "1" }, { "u": 131, "v": 2, "w": "1" }, { "u": 131, "v": 3, "w": "1" }, { "u": 131, "v": 11, "w": "1" }, { "u": 132, "v": 1, "w": "1" }, { "u": 132, "v": 3, "w": "1" }, { "u": 133, "v": 2, "w": "1" }, { "u": 133, "v": 5, "w": "1" }, { "u": 134, "v": 2, "w": "1" }, { "u": 135, "v": 1, "w": "1" }, { "u": 135, "v": 2, "w": "1" }, { "u": 135, "v": 11, "w": "1" }, { "u": 136, "v": 1, "w": "1" }, { "u": 136, "v": 2, "w": "1" }, { "u": 137, "v": 1, "w": "1" }, { "u": 138, "v": 1, "w": "1" }, { "u": 138, "v": 3, "w": "1" }, { "u": 139, "v": 2, "w": "1" }, { "u": 139, "v": 11, "w": "1" }, { "u": 140, "v": 1, "w": "1" }, { "u": 140, "v": 2, "w": "1" }, { "u": 141, "v": 3, "w": "1" }, { "u": 141, "v": 6, "w": "1" }, { "u": 142, "v": 1, "w": "1" }, { "u": 142, "v": 2, "w": "1" }, { "u": 143, "v": 3, "w": "1" }, { "u": 143, "v": 5, "w": "1" }, { "u": 144, "v": 2, "w": "1" }, { "u": 144, "v": 6, "w": "1" }, { "u": 145, "v": 2, "w": "1" }, { "u": 145, "v": 3, "w": "1" }, { "u": 146, "v": 1, "w": "1" }, { "u": 146, "v": 2, "w": "1" }, { "u": 147, "v": 1, "w": "1" }, { "u": 147, "v": 2, "w": "1" }, { "u": 147, "v": 11, "w": "1" }, { "u": 148, "v": 6, "w": "1" }, { "u": 149, "v": 2, "w": "1" }, { "u": 149, "v": 3, "w": "1" }, { "u": 150, "v": 1, "w": "1" }, { "u": 150, "v": 6, "w": "1" }, { "u": 151, "v": 3, "w": "1" }, { "u": 151, "v": 11, "w": "1" }, { "u": 152, "v": 1, "w": "1" }, { "u": 152, "v": 2, "w": "1" }, { "u": 152, "v": 3, "w": "1" }, { "u": 153, "v": 1, "w": "1" }, { "u": 153, "v": 6, "w": "1" }, { "u": 154, "v": 1, "w": "1" }, { "u": 155, "v": 1, "w": "1" }, { "u": 155, "v": 2, "w": "1" }, { "u": 155, "v": 6, "w": "1" }, { "u": 156, "v": 2, "w": "1" }, { "u": 156, "v": 6, "w": "1" }, { "u": 156, "v": 11, "w": "1" }, { "u": 157, "v": 1, "w": "1" }, { "u": 157, "v": 2, "w": "1" }, { "u": 157, "v": 3, "w": "1" }, { "u": 158, "v": 1, "w": "1" }, { "u": 158, "v": 6, "w": "1" }, { "u": 158, "v": 11, "w": "1" }, { "u": 159, "v": 1, "w": "1" }, { "u": 159, "v": 11, "w": "1" }, { "u": 160, "v": 1, "w": "1" }, { "u": 160, "v": 3, "w": "1" }, { "u": 160, "v": 6, "w": "1" }, { "u": 161, "v": 2, "w": "1" }, { "u": 161, "v": 6, "w": "1" }, { "u": 162, "v": 1, "w": "1" }, { "u": 162, "v": 2, "w": "1" }, { "u": 163, "v": 2, "w": "1" }, { "u": 163, "v": 6, "w": "1" }, { "u": 164, "v": 3, "w": "1" }, { "u": 164, "v": 5, "w": "1" }, { "u": 164, "v": 6, "w": "1" }, { "u": 165, "v": 1, "w": "1" }, { "u": 165, "v": 2, "w": "1" }, { "u": 166, "v": 2, "w": "1" }, { "u": 166, "v": 3, "w": "1" }, { "u": 166, "v": 6, "w": "1" }, { "u": 167, "v": 1, "w": "1" }, { "u": 167, "v": 2, "w": "1" }, { "u": 167, "v": 3, "w": "1" }, { "u": 168, "v": 1, "w": "1" }, { "u": 168, "v": 5, "w": "1" }, { "u": 169, "v": 1, "w": "1" }, { "u": 169, "v": 2, "w": "1" }, { "u": 169, "v": 6, "w": "1" }, { "u": 170, "v": 2, "w": "1" }, { "u": 170, "v": 3, "w": "1" }, { "u": 170, "v": 6, "w": "1" }, { "u": 171, "v": 1, "w": "1" }, { "u": 171, "v": 2, "w": "1" }, { "u": 172, "v": 1, "w": "1" }, { "u": 172, "v": 2, "w": "1" }, { "u": 173, "v": 1, "w": "1" }, { "u": 173, "v": 2, "w": "1" }, { "u": 174, "v": 2, "w": "1" }, { "u": 174, "v": 11, "w": "1" }, { "u": 175, "v": 1, "w": "1" }, { "u": 175, "v": 6, "w": "1" }, { "u": 175, "v": 11, "w": "1" }, { "u": 176, "v": 2, "w": "1" }, { "u": 176, "v": 6, "w": "1" }, { "u": 177, "v": 1, "w": "1" }, { "u": 177, "v": 11, "w": "1" }, { "u": 178, "v": 2, "w": "1" }, { "u": 178, "v": 3, "w": "1" }, { "u": 178, "v": 11, "w": "1" }, { "u": 179, "v": 1, "w": "1" }, { "u": 179, "v": 3, "w": "1" }, { "u": 180, "v": 1, "w": "1" }, { "u": 180, "v": 2, "w": "1" }, { "u": 181, "v": 1, "w": "1" }, { "u": 181, "v": 6, "w": "1" }, { "u": 181, "v": 11, "w": "1" }, { "u": 182, "v": 1, "w": "1" }, { "u": 182, "v": 2, "w": "1" }, { "u": 183, "v": 1, "w": "1" }, { "u": 183, "v": 2, "w": "1" }, { "u": 184, "v": 1, "w": "1" }, { "u": 185, "v": 2, "w": "1" }, { "u": 185, "v": 3, "w": "1" }, { "u": 185, "v": 5, "w": "1" }, { "u": 186, "v": 1, "w": "1" }, { "u": 186, "v": 6, "w": "1" }, { "u": 186, "v": 11, "w": "1" }, { "u": 187, "v": 1, "w": "1" }, { "u": 187, "v": 6, "w": "1" }, { "u": 188, "v": 1, "w": "1" }, { "u": 188, "v": 3, "w": "1" }, { "u": 188, "v": 5, "w": "1" }, { "u": 189, "v": 1, "w": "1" }, { "u": 189, "v": 11, "w": "1" }, { "u": 190, "v": 1, "w": "1" }, { "u": 190, "v": 2, "w": "1" }, { "u": 190, "v": 6, "w": "1" }, { "u": 191, "v": 2, "w": "1" }, { "u": 191, "v": 6, "w": "1" }, { "u": 192, "v": 1, "w": "1" }, { "u": 193, "v": 2, "w": "1" }, { "u": 193, "v": 6, "w": "1" }, { "u": 193, "v": 11, "w": "1" }, { "u": 194, "v": 2, "w": "1" }, { "u": 195, "v": 2, "w": "1" }, { "u": 195, "v": 11, "w": "1" }, { "u": 196, "v": 1, "w": "1" }, { "u": 196, "v": 11, "w": "1" }, { "u": 197, "v": 1, "w": "1" }, { "u": 197, "v": 6, "w": "1" }, { "u": 198, "v": 2, "w": "1" }, { "u": 198, "v": 5, "w": "1" }, { "u": 199, "v": 1, "w": "1" }, { "u": 199, "v": 2, "w": "1" }, { "u": 199, "v": 6, "w": "1" }, { "u": 200, "v": 1, "w": "1" }, { "u": 200, "v": 2, "w": "1" }, { "u": 200, "v": 3, "w": "1" }, { "u": 201, "v": 2, "w": "1" }, { "u": 201, "v": 11, "w": "1" }, { "u": 202, "v": 2, "w": "1" }, { "u": 202, "v": 6, "w": "1" }, { "u": 203, "v": 1, "w": "1" }, { "u": 203, "v": 2, "w": "1" }, { "u": 203, "v": 6, "w": "1" }, { "u": 204, "v": 2, "w": "1" }, { "u": 204, "v": 5, "w": "1" }, { "u": 204, "v": 6, "w": "1" }, { "u": 205, "v": 1, "w": "1" }, { "u": 205, "v": 2, "w": "1" }, { "u": 205, "v": 11, "w": "1" }, { "u": 206, "v": 1, "w": "1" }, { "u": 206, "v": 2, "w": "1" }, { "u": 206, "v": 3, "w": "1" }, { "u": 207, "v": 1, "w": "1" }, { "u": 207, "v": 3, "w": "1" }, { "u": 207, "v": 6, "w": "1" }, { "u": 208, "v": 1, "w": "1" }, { "u": 208, "v": 3, "w": "1" }, { "u": 208, "v": 6, "w": "1" }, { "u": 209, "v": 1, "w": "1" }, { "u": 209, "v": 2, "w": "1" }, { "u": 210, "v": 1, "w": "1" }, { "u": 210, "v": 3, "w": "1" }, { "u": 211, "v": 1, "w": "1" }, { "u": 211, "v": 2, "w": "1" }, { "u": 212, "v": 1, "w": "1" }, { "u": 212, "v": 6, "w": "1" }, { "u": 213, "v": 2, "w": "1" }, { "u": 213, "v": 6, "w": "1" }, { "u": 214, "v": 3, "w": "1" }, { "u": 214, "v": 11, "w": "1" }, { "u": 215, "v": 2, "w": "1" }, { "u": 215, "v": 3, "w": "1" }, { "u": 215, "v": 11, "w": "1" }, { "u": 216, "v": 1, "w": "1" }, { "u": 216, "v": 3, "w": "1" }, { "u": 216, "v": 6, "w": "1" }, { "u": 217, "v": 1, "w": "1" }, { "u": 217, "v": 2, "w": "1" }, { "u": 218, "v": 2, "w": "1" }, { "u": 218, "v": 6, "w": "1" }, { "u": 219, "v": 2, "w": "1" }, { "u": 219, "v": 6, "w": "1" }, { "u": 220, "v": 2, "w": "1" }, { "u": 220, "v": 3, "w": "1" }, { "u": 220, "v": 6, "w": "1" }, { "u": 221, "v": 1, "w": "1" }, { "u": 221, "v": 2, "w": "1" }, { "u": 221, "v": 11, "w": "1" }, { "u": 222, "v": 2, "w": "1" }, { "u": 222, "v": 11, "w": "1" }, { "u": 223, "v": 2, "w": "1" }, { "u": 223, "v": 11, "w": "1" }, { "u": 224, "v": 1, "w": "1" }, { "u": 224, "v": 2, "w": "1" }, { "u": 225, "v": 3, "w": "1" }, { "u": 225, "v": 6, "w": "1" }, { "u": 225, "v": 11, "w": "1" }, { "u": 226, "v": 2, "w": "1" }, { "u": 227, "v": 1, "w": "1" }, { "u": 227, "v": 2, "w": "1" }, { "u": 227, "v": 3, "w": "1" }, { "u": 228, "v": 1, "w": "1" }, { "u": 228, "v": 2, "w": "1" }, { "u": 228, "v": 5, "w": "1" }, { "u": 229, "v": 2, "w": "1" }, { "u": 229, "v": 6, "w": "1" }, { "u": 230, "v": 1, "w": "1" }, { "u": 230, "v": 2, "w": "1" }, { "u": 230, "v": 5, "w": "1" }, { "u": 231, "v": 1, "w": "1" }, { "u": 231, "v": 2, "w": "1" }, { "u": 231, "v": 3, "w": "1" }, { "u": 232, "v": 1, "w": "1" }, { "u": 233, "v": 2, "w": "1" }, { "u": 233, "v": 6, "w": "1" }, { "u": 234, "v": 1, "w": "1" }, { "u": 234, "v": 2, "w": "1" }, { "u": 235, "v": 2, "w": "1" }, { "u": 235, "v": 3, "w": "1" }, { "u": 235, "v": 6, "w": "1" }, { "u": 236, "v": 1, "w": "1" }, { "u": 236, "v": 2, "w": "1" }, { "u": 236, "v": 11, "w": "1" }, { "u": 237, "v": 1, "w": "1" }, { "u": 237, "v": 11, "w": "1" }, { "u": 238, "v": 2, "w": "1" }, { "u": 238, "v": 3, "w": "1" }, { "u": 239, "v": 2, "w": "1" }, { "u": 239, "v": 3, "w": "1" }, { "u": 239, "v": 6, "w": "1" }, { "u": 240, "v": 2, "w": "1" }, { "u": 240, "v": 3, "w": "1" }, { "u": 241, "v": 2, "w": "1" }, { "u": 241, "v": 3, "w": "1" }, { "u": 241, "v": 11, "w": "1" }, { "u": 242, "v": 1, "w": "1" }, { "u": 242, "v": 6, "w": "1" }, { "u": 242, "v": 11, "w": "1" }, { "u": 243, "v": 1, "w": "1" }, { "u": 243, "v": 3, "w": "1" }, { "u": 243, "v": 6, "w": "1" }, { "u": 244, "v": 1, "w": "1" }, { "u": 245, "v": 3, "w": "1" }, { "u": 245, "v": 11, "w": "1" }, { "u": 246, "v": 2, "w": "1" }, { "u": 246, "v": 3, "w": "1" }, { "u": 246, "v": 5, "w": "1" }, { "u": 247, "v": 2, "w": "1" }, { "u": 247, "v": 3, "w": "1" }, { "u": 247, "v": 11, "w": "1" }, { "u": 248, "v": 1, "w": "1" }, { "u": 248, "v": 2, "w": "1" }, { "u": 248, "v": 6, "w": "1" }, { "u": 249, "v": 3, "w": "1" }, { "u": 249, "v": 11, "w": "1" }, { "u": 250, "v": 2, "w": "1" }, { "u": 250, "v": 3, "w": "1" }, { "u": 250, "v": 11, "w": "1" }, { "u": 251, "v": 1, "w": "1" }, { "u": 251, "v": 6, "w": "1" }, { "u": 252, "v": 1, "w": "1" }, { "u": 252, "v": 2, "w": "1" }, { "u": 252, "v": 6, "w": "1" }, { "u": 253, "v": 2, "w": "1" }, { "u": 253, "v": 3, "w": "1" }, { "u": 253, "v": 6, "w": "1" }, { "u": 254, "v": 1, "w": "1" }, { "u": 254, "v": 2, "w": "1" }, { "u": 254, "v": 3, "w": "1" }, { "u": 255, "v": 2, "w": "1" }, { "u": 255, "v": 11, "w": "1" }, { "u": 256, "v": 3, "w": "1" }, { "u": 256, "v": 5, "w": "1" }, { "u": 256, "v": 11, "w": "1" }, { "u": 257, "v": 1, "w": "1" }, { "u": 257, "v": 11, "w": "1" }, { "u": 258, "v": 1, "w": "1" }, { "u": 258, "v": 2, "w": "1" }, { "u": 258, "v": 6, "w": "1" }, { "u": 259, "v": 1, "w": "1" }, { "u": 259, "v": 2, "w": "1" }, { "u": 259, "v": 3, "w": "1" }, { "u": 260, "v": 1, "w": "1" }, { "u": 260, "v": 5, "w": "1" }, { "u": 260, "v": 6, "w": "1" }, { "u": 261, "v": 2, "w": "1" }, { "u": 261, "v": 3, "w": "1" }, { "u": 261, "v": 5, "w": "1" }, { "u": 262, "v": 2, "w": "1" }, { "u": 262, "v": 3, "w": "1" }, { "u": 262, "v": 6, "w": "1" }, { "u": 263, "v": 2, "w": "1" }, { "u": 263, "v": 6, "w": "1" }, { "u": 264, "v": 1, "w": "1" }, { "u": 264, "v": 6, "w": "1" }, { "u": 265, "v": 1, "w": "1" }, { "u": 265, "v": 2, "w": "1" }, { "u": 265, "v": 3, "w": "1" }, { "u": 266, "v": 2, "w": "1" }, { "u": 266, "v": 3, "w": "1" }, { "u": 266, "v": 11, "w": "1" }, { "u": 267, "v": 1, "w": "1" }, { "u": 267, "v": 2, "w": "1" }, { "u": 267, "v": 5, "w": "1" }, { "u": 268, "v": 1, "w": "1" }, { "u": 268, "v": 3, "w": "1" }, { "u": 268, "v": 11, "w": "1" }, { "u": 269, "v": 1, "w": "1" }, { "u": 270, "v": 1, "w": "1" }, { "u": 270, "v": 3, "w": "1" }, { "u": 271, "v": 1, "w": "1" }, { "u": 271, "v": 2, "w": "1" }, { "u": 271, "v": 11, "w": "1" }, { "u": 272, "v": 2, "w": "1" }, { "u": 272, "v": 5, "w": "1" }, { "u": 272, "v": 6, "w": "1" }, { "u": 273, "v": 1, "w": "1" }, { "u": 273, "v": 3, "w": "1" }, { "u": 274, "v": 2, "w": "1" }, { "u": 274, "v": 11, "w": "1" }, { "u": 275, "v": 1, "w": "1" }, { "u": 275, "v": 3, "w": "1" }, { "u": 275, "v": 11, "w": "1" }, { "u": 276, "v": 2, "w": "1" }, { "u": 276, "v": 3, "w": "1" }, { "u": 276, "v": 6, "w": "1" }, { "u": 277, "v": 1, "w": "1" }, { "u": 277, "v": 2, "w": "1" }, { "u": 277, "v": 3, "w": "1" }, { "u": 278, "v": 1, "w": "1" }, { "u": 278, "v": 2, "w": "1" }, { "u": 278, "v": 3, "w": "1" }, { "u": 279, "v": 1, "w": "1" }, { "u": 279, "v": 3, "w": "1" }, { "u": 279, "v": 6, "w": "1" }, { "u": 280, "v": 1, "w": "1" }, { "u": 280, "v": 2, "w": "1" }, { "u": 281, "v": 2, "w": "1" }, { "u": 281, "v": 3, "w": "1" }, { "u": 282, "v": 1, "w": "1" }, { "u": 282, "v": 6, "w": "1" }, { "u": 283, "v": 1, "w": "1" }, { "u": 283, "v": 2, "w": "1" }, { "u": 283, "v": 3, "w": "1" }, { "u": 284, "v": 1, "w": "1" }, { "u": 284, "v": 3, "w": "1" }, { "u": 285, "v": 1, "w": "1" }, { "u": 285, "v": 11, "w": "1" }, { "u": 286, "v": 1, "w": "1" }, { "u": 286, "v": 3, "w": "1" }, { "u": 286, "v": 6, "w": "1" }, { "u": 287, "v": 1, "w": "1" }, { "u": 287, "v": 3, "w": "1" }, { "u": 288, "v": 1, "w": "1" }, { "u": 288, "v": 11, "w": "1" }, { "u": 289, "v": 3, "w": "1" }, { "u": 289, "v": 6, "w": "1" }, { "u": 290, "v": 1, "w": "1" }, { "u": 290, "v": 6, "w": "1" }, { "u": 290, "v": 11, "w": "1" }, { "u": 291, "v": 2, "w": "1" }, { "u": 291, "v": 3, "w": "1" }, { "u": 292, "v": 2, "w": "1" }, { "u": 292, "v": 3, "w": "1" }, { "u": 292, "v": 5, "w": "1" }, { "u": 293, "v": 1, "w": "1" }, { "u": 293, "v": 3, "w": "1" }, { "u": 293, "v": 11, "w": "1" }, { "u": 294, "v": 2, "w": "1" }, { "u": 294, "v": 5, "w": "1" }, { "u": 295, "v": 1, "w": "1" }, { "u": 295, "v": 3, "w": "1" }, { "u": 295, "v": 6, "w": "1" }, { "u": 296, "v": 1, "w": "1" }, { "u": 296, "v": 2, "w": "1" }, { "u": 297, "v": 1, "w": "1" }, { "u": 297, "v": 2, "w": "1" }, { "u": 297, "v": 5, "w": "1" }, { "u": 298, "v": 2, "w": "1" }, { "u": 298, "v": 6, "w": "1" }, { "u": 299, "v": 1, "w": "1" }, { "u": 299, "v": 3, "w": "1" }, { "u": 300, "v": 2, "w": "1" }, { "u": 301, "v": 1, "w": "1" }, { "u": 301, "v": 6, "w": "1" }, { "u": 302, "v": 1, "w": "1" }, { "u": 302, "v": 2, "w": "1" }, { "u": 302, "v": 5, "w": "1" }, { "u": 303, "v": 1, "w": "1" }, { "u": 303, "v": 2, "w": "1" }, { "u": 303, "v": 6, "w": "1" }, { "u": 304, "v": 1, "w": "1" }, { "u": 304, "v": 2, "w": "1" }, { "u": 304, "v": 6, "w": "1" }, { "u": 305, "v": 1, "w": "1" }, { "u": 305, "v": 2, "w": "1" }, { "u": 306, "v": 2, "w": "1" }, { "u": 306, "v": 3, "w": "1" }, { "u": 306, "v": 6, "w": "1" }, { "u": 307, "v": 1, "w": "1" }, { "u": 307, "v": 2, "w": "1" }, { "u": 308, "v": 1, "w": "1" }, { "u": 308, "v": 2, "w": "1" }, { "u": 308, "v": 11, "w": "1" }, { "u": 309, "v": 1, "w": "1" }, { "u": 309, "v": 5, "w": "1" }, { "u": 309, "v": 6, "w": "1" }, { "u": 310, "v": 1, "w": "1" }, { "u": 310, "v": 6, "w": "1" }, { "u": 311, "v": 1, "w": "1" }, { "u": 311, "v": 2, "w": "1" }, { "u": 312, "v": 1, "w": "1" }, { "u": 312, "v": 2, "w": "1" }, { "u": 312, "v": 6, "w": "1" }, { "u": 313, "v": 1, "w": "1" }, { "u": 313, "v": 2, "w": "1" }, { "u": 314, "v": 1, "w": "1" }, { "u": 314, "v": 3, "w": "1" }, { "u": 314, "v": 6, "w": "1" }, { "u": 315, "v": 1, "w": "1" }, { "u": 315, "v": 6, "w": "1" }, { "u": 315, "v": 11, "w": "1" }, { "u": 316, "v": 1, "w": "1" }, { "u": 316, "v": 2, "w": "1" }, { "u": 316, "v": 3, "w": "1" }, { "u": 317, "v": 1, "w": "1" }, { "u": 317, "v": 3, "w": "1" }, { "u": 317, "v": 6, "w": "1" }, { "u": 318, "v": 2, "w": "1" }, { "u": 318, "v": 3, "w": "1" }, { "u": 318, "v": 11, "w": "1" }, { "u": 319, "v": 2, "w": "1" }, { "u": 319, "v": 3, "w": "1" }, { "u": 320, "v": 2, "w": "1" }, { "u": 320, "v": 3, "w": "1" }, { "u": 320, "v": 6, "w": "1" }, { "u": 321, "v": 2, "w": "1" }, { "u": 321, "v": 5, "w": "1" }, { "u": 321, "v": 6, "w": "1" }, { "u": 322, "v": 1, "w": "1" }, { "u": 322, "v": 2, "w": "1" }, { "u": 323, "v": 1, "w": "1" }, { "u": 323, "v": 2, "w": "1" }, { "u": 324, "v": 1, "w": "1" }, { "u": 324, "v": 6, "w": "1" }, { "u": 325, "v": 1, "w": "1" }, { "u": 326, "v": 1, "w": "1" }, { "u": 326, "v": 6, "w": "1" }, { "u": 327, "v": 1, "w": "1" }, { "u": 327, "v": 3, "w": "1" }, { "u": 327, "v": 6, "w": "1" }, { "u": 328, "v": 1, "w": "1" }, { "u": 328, "v": 6, "w": "1" }, { "u": 328, "v": 11, "w": "1" }, { "u": 329, "v": 1, "w": "1" }, { "u": 329, "v": 2, "w": "1" }, { "u": 330, "v": 1, "w": "1" }, { "u": 330, "v": 2, "w": "1" }, { "u": 331, "v": 1, "w": "1" }, { "u": 331, "v": 2, "w": "1" }, { "u": 332, "v": 1, "w": "1" }, { "u": 332, "v": 2, "w": "1" }, { "u": 333, "v": 2, "w": "1" }, { "u": 333, "v": 3, "w": "1" }, { "u": 333, "v": 11, "w": "1" }, { "u": 334, "v": 2, "w": "1" }, { "u": 334, "v": 3, "w": "1" }, { "u": 334, "v": 11, "w": "1" }, { "u": 335, "v": 1, "w": "1" }, { "u": 335, "v": 2, "w": "1" }, { "u": 335, "v": 6, "w": "1" }, { "u": 336, "v": 2, "w": "1" }, { "u": 336, "v": 6, "w": "1" }, { "u": 337, "v": 2, "w": "1" }, { "u": 337, "v": 6, "w": "1" }, { "u": 338, "v": 3, "w": "1" }, { "u": 338, "v": 6, "w": "1" }, { "u": 338, "v": 11, "w": "1" }, { "u": 339, "v": 1, "w": "1" }, { "u": 339, "v": 2, "w": "1" }, { "u": 339, "v": 6, "w": "1" }, { "u": 340, "v": 1, "w": "1" }, { "u": 340, "v": 3, "w": "1" }, { "u": 340, "v": 6, "w": "1" }, { "u": 341, "v": 5, "w": "1" }, { "u": 341, "v": 6, "w": "1" }, { "u": 342, "v": 2, "w": "1" }, { "u": 343, "v": 1, "w": "1" }, { "u": 344, "v": 1, "w": "1" }, { "u": 344, "v": 2, "w": "1" }, { "u": 344, "v": 3, "w": "1" }, { "u": 345, "v": 1, "w": "1" }, { "u": 345, "v": 2, "w": "1" }, { "u": 345, "v": 6, "w": "1" }, { "u": 346, "v": 1, "w": "1" }, { "u": 346, "v": 11, "w": "1" }, { "u": 347, "v": 2, "w": "1" }, { "u": 347, "v": 6, "w": "1" }, { "u": 348, "v": 2, "w": "1" }, { "u": 348, "v": 5, "w": "1" }, { "u": 348, "v": 11, "w": "1" }, { "u": 349, "v": 1, "w": "1" }, { "u": 349, "v": 2, "w": "1" }, { "u": 349, "v": 11, "w": "1" }, { "u": 350, "v": 1, "w": "1" }, { "u": 350, "v": 3, "w": "1" }, { "u": 351, "v": 2, "w": "1" }, { "u": 351, "v": 6, "w": "1" }, { "u": 351, "v": 11, "w": "1" }, { "u": 352, "v": 2, "w": "1" }, { "u": 352, "v": 3, "w": "1" }, { "u": 352, "v": 11, "w": "1" }, { "u": 353, "v": 1, "w": "1" }, { "u": 353, "v": 2, "w": "1" }, { "u": 354, "v": 1, "w": "1" }, { "u": 354, "v": 2, "w": "1" }, { "u": 355, "v": 1, "w": "1" }, { "u": 355, "v": 11, "w": "1" }, { "u": 356, "v": 2, "w": "1" }, { "u": 356, "v": 3, "w": "1" }, { "u": 356, "v": 6, "w": "1" }, { "u": 357, "v": 2, "w": "1" }, { "u": 357, "v": 3, "w": "1" }, { "u": 358, "v": 2, "w": "1" }, { "u": 358, "v": 5, "w": "1" }, { "u": 358, "v": 11, "w": "1" }, { "u": 359, "v": 2, "w": "1" }, { "u": 359, "v": 11, "w": "1" }, { "u": 360, "v": 1, "w": "1" }, { "u": 360, "v": 3, "w": "1" }, { "u": 361, "v": 1, "w": "1" }, { "u": 361, "v": 2, "w": "1" }, { "u": 362, "v": 2, "w": "1" }, { "u": 362, "v": 3, "w": "1" }, { "u": 363, "v": 1, "w": "1" }, { "u": 363, "v": 6, "w": "1" }, { "u": 363, "v": 11, "w": "1" }, { "u": 364, "v": 3, "w": "1" }, { "u": 364, "v": 5, "w": "1" }, { "u": 364, "v": 6, "w": "1" }, { "u": 365, "v": 1, "w": "1" }, { "u": 365, "v": 2, "w": "1" }, { "u": 365, "v": 5, "w": "1" }, { "u": 366, "v": 2, "w": "1" }, { "u": 366, "v": 3, "w": "1" }, { "u": 367, "v": 1, "w": "1" }, { "u": 367, "v": 2, "w": "1" }, { "u": 367, "v": 11, "w": "1" }, { "u": 368, "v": 1, "w": "1" }, { "u": 368, "v": 6, "w": "1" }, { "u": 369, "v": 1, "w": "1" }, { "u": 369, "v": 6, "w": "1" }, { "u": 370, "v": 2, "w": "1" }, { "u": 370, "v": 3, "w": "1" }, { "u": 370, "v": 6, "w": "1" }, { "u": 371, "v": 1, "w": "1" }, { "u": 371, "v": 2, "w": "1" }, { "u": 371, "v": 6, "w": "1" }, { "u": 372, "v": 1, "w": "1" }, { "u": 372, "v": 2, "w": "1" }, { "u": 372, "v": 6, "w": "1" }, { "u": 373, "v": 1, "w": "1" }, { "u": 373, "v": 2, "w": "1" }, { "u": 374, "v": 1, "w": "1" }, { "u": 374, "v": 5, "w": "1" }, { "u": 374, "v": 11, "w": "1" }, { "u": 375, "v": 2, "w": "1" }, { "u": 375, "v": 6, "w": "1" }, { "u": 376, "v": 2, "w": "1" }, { "u": 376, "v": 5, "w": "1" }, { "u": 376, "v": 6, "w": "1" }, { "u": 377, "v": 1, "w": "1" }, { "u": 377, "v": 2, "w": "1" }, { "u": 378, "v": 1, "w": "1" }, { "u": 378, "v": 11, "w": "1" }, { "u": 379, "v": 1, "w": "1" }, { "u": 379, "v": 6, "w": "1" }, { "u": 379, "v": 11, "w": "1" }, { "u": 380, "v": 1, "w": "1" }, { "u": 380, "v": 2, "w": "1" }, { "u": 381, "v": 1, "w": "1" }, { "u": 381, "v": 2, "w": "1" }, { "u": 381, "v": 6, "w": "1" }, { "u": 382, "v": 1, "w": "1" }, { "u": 382, "v": 2, "w": "1" }, { "u": 383, "v": 1, "w": "1" }, { "u": 383, "v": 2, "w": "1" }, { "u": 384, "v": 1, "w": "1" }, { "u": 384, "v": 2, "w": "1" }, { "u": 384, "v": 6, "w": "1" }, { "u": 385, "v": 3, "w": "1" }, { "u": 385, "v": 6, "w": "1" }, { "u": 385, "v": 11, "w": "1" }, { "u": 386, "v": 1, "w": "1" }, { "u": 386, "v": 3, "w": "1" }, { "u": 386, "v": 11, "w": "1" }, { "u": 387, "v": 2, "w": "1" }, { "u": 387, "v": 6, "w": "1" }, { "u": 388, "v": 2, "w": "1" }, { "u": 388, "v": 11, "w": "1" }, { "u": 389, "v": 1, "w": "1" }, { "u": 389, "v": 11, "w": "1" }, { "u": 390, "v": 1, "w": "1" }, { "u": 390, "v": 3, "w": "1" }, { "u": 391, "v": 1, "w": "1" }, { "u": 391, "v": 2, "w": "1" }, { "u": 391, "v": 3, "w": "1" }, { "u": 392, "v": 3, "w": "1" }, { "u": 392, "v": 6, "w": "1" }, { "u": 393, "v": 6, "w": "1" }, { "u": 393, "v": 11, "w": "1" }, { "u": 394, "v": 2, "w": "1" }, { "u": 394, "v": 6, "w": "1" }, { "u": 394, "v": 11, "w": "1" }, { "u": 395, "v": 1, "w": "1" }, { "u": 395, "v": 2, "w": "1" }, { "u": 395, "v": 6, "w": "1" }, { "u": 396, "v": 1, "w": "1" }, { "u": 396, "v": 2, "w": "1" }, { "u": 396, "v": 6, "w": "1" }, { "u": 397, "v": 2, "w": "1" }, { "u": 397, "v": 11, "w": "1" }, { "u": 398, "v": 2, "w": "1" }, { "u": 398, "v": 6, "w": "1" }, { "u": 399, "v": 2, "w": "1" }, { "u": 399, "v": 3, "w": "1" }, { "u": 399, "v": 6, "w": "1" }, { "u": 400, "v": 1, "w": "1" }, { "u": 400, "v": 11, "w": "1" }, { "u": 401, "v": 2, "w": "1" }, { "u": 401, "v": 6, "w": "1" }, { "u": 402, "v": 1, "w": "1" }, { "u": 402, "v": 2, "w": "1" }, { "u": 402, "v": 3, "w": "1" }, { "u": 403, "v": 1, "w": "1" }, { "u": 403, "v": 2, "w": "1" }, { "u": 403, "v": 11, "w": "1" }, { "u": 404, "v": 1, "w": "1" }, { "u": 405, "v": 1, "w": "1" }, { "u": 405, "v": 2, "w": "1" }, { "u": 405, "v": 3, "w": "1" }, { "u": 406, "v": 1, "w": "1" }, { "u": 406, "v": 2, "w": "1" }, { "u": 406, "v": 3, "w": "1" }, { "u": 407, "v": 1, "w": "1" }, { "u": 407, "v": 2, "w": "1" }, { "u": 407, "v": 6, "w": "1" }, { "u": 408, "v": 1, "w": "1" }, { "u": 408, "v": 2, "w": "1" }, { "u": 408, "v": 3, "w": "1" }, { "u": 409, "v": 2, "w": "1" }, { "u": 409, "v": 6, "w": "1" }, { "u": 409, "v": 11, "w": "1" }, { "u": 410, "v": 1, "w": "1" }, { "u": 410, "v": 2, "w": "1" }, { "u": 410, "v": 5, "w": "1" }, { "u": 411, "v": 1, "w": "1" }, { "u": 411, "v": 3, "w": "1" }, { "u": 411, "v": 6, "w": "1" }, { "u": 412, "v": 2, "w": "1" }, { "u": 412, "v": 3, "w": "1" }, { "u": 412, "v": 6, "w": "1" }, { "u": 413, "v": 1, "w": "1" }, { "u": 413, "v": 2, "w": "1" }, { "u": 414, "v": 1, "w": "1" }, { "u": 414, "v": 2, "w": "1" }, { "u": 415, "v": 1, "w": "1" }, { "u": 415, "v": 2, "w": "1" }, { "u": 415, "v": 6, "w": "1" }, { "u": 416, "v": 2, "w": "1" }, { "u": 416, "v": 6, "w": "1" }, { "u": 416, "v": 11, "w": "1" }, { "u": 417, "v": 2, "w": "1" }, { "u": 417, "v": 3, "w": "1" }, { "u": 417, "v": 11, "w": "1" }, { "u": 418, "v": 1, "w": "1" }, { "u": 418, "v": 11, "w": "1" }, { "u": 419, "v": 1, "w": "1" }, { "u": 419, "v": 3, "w": "1" }, { "u": 419, "v": 6, "w": "1" }, { "u": 420, "v": 1, "w": "1" }, { "u": 420, "v": 2, "w": "1" }, { "u": 421, "v": 1, "w": "1" }, { "u": 421, "v": 6, "w": "1" }, { "u": 422, "v": 2, "w": "1" }, { "u": 422, "v": 3, "w": "1" }, { "u": 422, "v": 6, "w": "1" }, { "u": 423, "v": 1, "w": "1" }, { "u": 423, "v": 3, "w": "1" }, { "u": 424, "v": 1, "w": "1" }, { "u": 424, "v": 6, "w": "1" }, { "u": 425, "v": 1, "w": "1" }, { "u": 425, "v": 2, "w": "1" }, { "u": 425, "v": 6, "w": "1" }, { "u": 426, "v": 1, "w": "1" }, { "u": 426, "v": 2, "w": "1" }, { "u": 427, "v": 1, "w": "1" }, { "u": 427, "v": 11, "w": "1" }, { "u": 428, "v": 3, "w": "1" }, { "u": 428, "v": 6, "w": "1" }, { "u": 428, "v": 11, "w": "1" }, { "u": 429, "v": 1, "w": "1" }, { "u": 429, "v": 2, "w": "1" }, { "u": 429, "v": 11, "w": "1" }, { "u": 430, "v": 2, "w": "1" }, { "u": 430, "v": 3, "w": "1" }, { "u": 431, "v": 2, "w": "1" }, { "u": 431, "v": 3, "w": "1" }, { "u": 431, "v": 6, "w": "1" }, { "u": 432, "v": 2, "w": "1" }, { "u": 432, "v": 3, "w": "1" }, { "u": 432, "v": 6, "w": "1" }, { "u": 433, "v": 1, "w": "1" }, { "u": 433, "v": 2, "w": "1" }, { "u": 434, "v": 2, "w": "1" }, { "u": 434, "v": 3, "w": "1" }, { "u": 435, "v": 1, "w": "1" }, { "u": 435, "v": 6, "w": "1" }, { "u": 436, "v": 1, "w": "1" }, { "u": 436, "v": 2, "w": "1" }, { "u": 436, "v": 3, "w": "1" }, { "u": 437, "v": 2, "w": "1" }, { "u": 437, "v": 6, "w": "1" }, { "u": 437, "v": 11, "w": "1" }, { "u": 438, "v": 1, "w": "1" }, { "u": 438, "v": 6, "w": "1" }, { "u": 439, "v": 1, "w": "1" }, { "u": 439, "v": 2, "w": "1" }, { "u": 439, "v": 6, "w": "1" }, { "u": 440, "v": 3, "w": "1" }, { "u": 440, "v": 6, "w": "1" }, { "u": 441, "v": 2, "w": "1" }, { "u": 441, "v": 6, "w": "1" }, { "u": 442, "v": 2, "w": "1" }, { "u": 443, "v": 5, "w": "1" }, { "u": 443, "v": 6, "w": "1" }, { "u": 443, "v": 11, "w": "1" }, { "u": 444, "v": 1, "w": "1" }, { "u": 444, "v": 2, "w": "1" }, { "u": 444, "v": 3, "w": "1" }, { "u": 445, "v": 2, "w": "1" }, { "u": 445, "v": 3, "w": "1" }, { "u": 445, "v": 6, "w": "1" }, { "u": 446, "v": 2, "w": "1" }, { "u": 446, "v": 3, "w": "1" }, { "u": 446, "v": 11, "w": "1" }, { "u": 447, "v": 2, "w": "1" }, { "u": 447, "v": 6, "w": "1" }, { "u": 447, "v": 11, "w": "1" }, { "u": 448, "v": 2, "w": "1" }, { "u": 448, "v": 3, "w": "1" }, { "u": 448, "v": 5, "w": "1" }, { "u": 449, "v": 1, "w": "1" }, { "u": 449, "v": 6, "w": "1" }, { "u": 449, "v": 11, "w": "1" }, { "u": 450, "v": 3, "w": "1" }, { "u": 450, "v": 6, "w": "1" }, { "u": 451, "v": 1, "w": "1" }, { "u": 451, "v": 2, "w": "1" }, { "u": 451, "v": 6, "w": "1" }, { "u": 452, "v": 1, "w": "1" }, { "u": 452, "v": 2, "w": "1" }, { "u": 452, "v": 6, "w": "1" }, { "u": 453, "v": 1, "w": "1" }, { "u": 453, "v": 2, "w": "1" }, { "u": 453, "v": 6, "w": "1" }, { "u": 454, "v": 1, "w": "1" }, { "u": 454, "v": 2, "w": "1" }, { "u": 455, "v": 1, "w": "1" }, { "u": 455, "v": 3, "w": "1" }, { "u": 456, "v": 2, "w": "1" }, { "u": 456, "v": 3, "w": "1" }, { "u": 456, "v": 6, "w": "1" }, { "u": 457, "v": 1, "w": "1" }, { "u": 457, "v": 3, "w": "1" }, { "u": 457, "v": 6, "w": "1" }, { "u": 458, "v": 2, "w": "1" }, { "u": 458, "v": 3, "w": "1" }, { "u": 459, "v": 1, "w": "1" }, { "u": 459, "v": 6, "w": "1" }, { "u": 460, "v": 1, "w": "1" }, { "u": 460, "v": 2, "w": "1" }, { "u": 461, "v": 1, "w": "1" }, { "u": 461, "v": 2, "w": "1" }, { "u": 461, "v": 3, "w": "1" }, { "u": 462, "v": 1, "w": "1" }, { "u": 462, "v": 3, "w": "1" }, { "u": 462, "v": 5, "w": "1" }, { "u": 463, "v": 1, "w": "1" }, { "u": 463, "v": 11, "w": "1" }, { "u": 464, "v": 1, "w": "1" }, { "u": 464, "v": 6, "w": "1" }, { "u": 465, "v": 1, "w": "1" }, { "u": 465, "v": 2, "w": "1" }, { "u": 466, "v": 1, "w": "1" }, { "u": 466, "v": 11, "w": "1" }, { "u": 467, "v": 1, "w": "1" }, { "u": 467, "v": 6, "w": "1" }, { "u": 468, "v": 1, "w": "1" }, { "u": 468, "v": 5, "w": "1" }, { "u": 469, "v": 1, "w": "1" }, { "u": 469, "v": 2, "w": "1" }, { "u": 469, "v": 3, "w": "1" }, { "u": 470, "v": 2, "w": "1" }, { "u": 470, "v": 3, "w": "1" }, { "u": 470, "v": 11, "w": "1" }, { "u": 471, "v": 1, "w": "1" }, { "u": 471, "v": 3, "w": "1" }, { "u": 471, "v": 6, "w": "1" }, { "u": 472, "v": 1, "w": "1" }, { "u": 472, "v": 3, "w": "1" }, { "u": 473, "v": 1, "w": "1" }, { "u": 473, "v": 2, "w": "1" }, { "u": 473, "v": 6, "w": "1" }, { "u": 474, "v": 6, "w": "1" }, { "u": 474, "v": 11, "w": "1" }, { "u": 475, "v": 2, "w": "1" }, { "u": 475, "v": 6, "w": "1" }, { "u": 475, "v": 11, "w": "1" }, { "u": 476, "v": 1, "w": "1" }, { "u": 476, "v": 5, "w": "1" }, { "u": 477, "v": 1, "w": "1" }, { "u": 477, "v": 2, "w": "1" }, { "u": 477, "v": 6, "w": "1" }, { "u": 478, "v": 2, "w": "1" }, { "u": 478, "v": 3, "w": "1" }, { "u": 478, "v": 6, "w": "1" }, { "u": 479, "v": 1, "w": "1" }, { "u": 479, "v": 2, "w": "1" }, { "u": 480, "v": 1, "w": "1" }, { "u": 480, "v": 6, "w": "1" }, { "u": 481, "v": 1, "w": "1" }, { "u": 481, "v": 5, "w": "1" }, { "u": 481, "v": 11, "w": "1" }, { "u": 482, "v": 1, "w": "1" }, { "u": 482, "v": 2, "w": "1" }, { "u": 482, "v": 3, "w": "1" }, { "u": 483, "v": 1, "w": "1" }, { "u": 483, "v": 2, "w": "1" }, { "u": 484, "v": 1, "w": "1" }, { "u": 484, "v": 2, "w": "1" }, { "u": 484, "v": 6, "w": "1" }, { "u": 485, "v": 1, "w": "1" }, { "u": 485, "v": 2, "w": "1" }, { "u": 486, "v": 1, "w": "1" }, { "u": 486, "v": 5, "w": "1" }, { "u": 487, "v": 2, "w": "1" }, { "u": 487, "v": 3, "w": "1" }, { "u": 488, "v": 2, "w": "1" }, { "u": 488, "v": 11, "w": "1" }, { "u": 489, "v": 2, "w": "1" }, { "u": 489, "v": 5, "w": "1" }, { "u": 490, "v": 1, "w": "1" }, { "u": 490, "v": 11, "w": "1" }, { "u": 491, "v": 1, "w": "1" }, { "u": 491, "v": 6, "w": "1" }, { "u": 491, "v": 11, "w": "1" }, { "u": 492, "v": 1, "w": "1" }, { "u": 492, "v": 6, "w": "1" }, { "u": 493, "v": 1, "w": "1" }, { "u": 493, "v": 2, "w": "1" }, { "u": 493, "v": 11, "w": "1" }, { "u": 494, "v": 1, "w": "1" }, { "u": 494, "v": 2, "w": "1" }, { "u": 495, "v": 2, "w": "1" }, { "u": 496, "v": 2, "w": "1" }, { "u": 496, "v": 3, "w": "1" }, { "u": 497, "v": 1, "w": "1" }, { "u": 497, "v": 2, "w": "1" }, { "u": 497, "v": 11, "w": "1" }, { "u": 498, "v": 1, "w": "1" }, { "u": 498, "v": 2, "w": "1" }, { "u": 498, "v": 6, "w": "1" }, { "u": 499, "v": 1, "w": "1" }, { "u": 499, "v": 11, "w": "1" }, { "u": 500, "v": 1, "w": "1" }, { "u": 500, "v": 6, "w": "1" } ], "hash_sha256": "2075a9bd34e0bdb3d0ca82ff9e433bf05852da9650fbe8e5a28054812ad83b02", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "100", "101", "102", "103", "104", "105", "106", "107", "108", "109", "110", "111", "112", "113", "114", "115", "116", "117", "118", "119", "120", "121", "122", "123", "124", "125", "126", "127", "128", "129", "130", "131", "132", "133", "134", "135", "136", "137", "138", "139", "140", "141", "142", "143", "144", "145", "146", "147", "148", "149", "150", "151", "152", "153", "154", "155", "156", "157", "158", "159", "160", "161", "162", "163", "164", "165", "166", "167", "168", "169", "170", "171", "172", "173", "174", "175", "176", "177", "178", "179", "180", "181", "182", "183", "184", "185", "186", "187", "188", "189", "190", "191", "192", "193", "194", "195", "196", "197", "198", "199", "200", "201", "202", "203", "204", "205", "206", "207", "208", "209", "210", "211", "212", "213", "214", "215", "216", "217", "218", "219", "220", "221", "222", "223", "224", "225", "226", "227", "228", "229", "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", "240", "241", "242", "243", "244", "245", "246", "247", "248", "249", "250", "251", "252", "253", "254", "255", "256", "257", "258", "259", "260", "261", "262", "263", "264", "265", "266", "267", "268", "269", "270", "271", "272", "273", "274", "275", "276", "277", "278", "279", "280", "281", "282", "283", "284", "285", "286", "287", "288", "289", "290", "291", "292", "293", "294", "295", "296", "297", "298", "299", "300", "301", "302", "303", "304", "305", "306", "307", "308", "309", "310", "311", "312", "313", "314", "315", "316", "317", "318", "319", "320", "321", "322", "323", "324", "325", "326", "327", "328", "329", "330", "331", "332", "333", "334", "335", "336", "337", "338", "339", "340", "341", "342", "343", "344", "345", "346", "347", "348", "349", "350", "351", "352", "353", "354", "355", "356", "357", "358", "359", "360", "361", "362", "363", "364", "365", "366", "367", "368", "369", "370", "371", "372", "373", "374", "375", "376", "377", "378", "379", "380", "381", "382", "383", "384", "385", "386", "387", "388", "389", "390", "391", "392", "393", "394", "395", "396", "397", "398", "399", "400", "401", "402", "403", "404", "405", "406", "407", "408", "409", "410", "411", "412", "413", "414", "415", "416", "417", "418", "419", "420", "421", "422", "423", "424", "425", "426", "427", "428", "429", "430", "431", "432", "433", "434", "435", "436", "437", "438", "439", "440", "441", "442", "443", "444", "445", "446", "447", "448", "449", "450", "451", "452", "453", "454", "455", "456", "457", "458", "459", "460", "461", "462", "463", "464", "465", "466", "467", "468", "469", "470", "471", "472", "473", "474", "475", "476", "477", "478", "479", "480", "481", "482", "483", "484", "485", "486", "487", "488", "489", "490", "491", "492", "493", "494", "495", "496", "497", "498", "499", "500" ] }, "timings": { "load_ms": 10, "reload_ms": 13, "save_ms": 53, "total_ms": 81 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/Bernard_Killworth_Fraternity__FT5__IO__V5.json000066400000000000000000035371101524402210300321120ustar00rootroot00000000000000{ "counts": { "links_sna": 1934, "nodes": 58, "ties_graph": 1934 }, "dataset": { "filetype": 5, "name": "Bernard_Killworth_Fraternity.dl", "path": "src/data/Bernard_Killworth_Fraternity.dl" }, "graph": { "directed": true, "relations": 2, "weighted": true }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 5, "load_ms": 19, "load_msg": "", "net_name": "Bernard_Killworth_Fraternity.dl", "ok": true }, "metrics": { "density": "0.5849969751966122" }, "relations_bundle": [ { "index": 0, "name": "BKFRAB", "signature": { "edge_list": [ { "u": 1, "v": 3, "w": "2" }, { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 7, "w": "2" }, { "u": 1, "v": 11, "w": "1" }, { "u": 1, "v": 12, "w": "1" }, { "u": 1, "v": 13, "w": "2" }, { "u": 1, "v": 17, "w": "1" }, { "u": 1, "v": 19, "w": "1" }, { "u": 1, "v": 22, "w": "1" }, { "u": 1, "v": 29, "w": "2" }, { "u": 1, "v": 30, "w": "1" }, { "u": 1, "v": 31, "w": "1" }, { "u": 1, "v": 32, "w": "1" }, { "u": 1, "v": 34, "w": "2" }, { "u": 1, "v": 35, "w": "1" }, { "u": 1, "v": 36, "w": "2" }, { "u": 1, "v": 41, "w": "1" }, { "u": 1, "v": 49, "w": "1" }, { "u": 1, "v": 54, "w": "1" }, { "u": 1, "v": 55, "w": "1" }, { "u": 1, "v": 56, "w": "4" }, { "u": 1, "v": 57, "w": "1" }, { "u": 1, "v": 58, "w": "1" }, { "u": 2, "v": 3, "w": "10" }, { "u": 2, "v": 6, "w": "2" }, { "u": 2, "v": 7, "w": "1" }, { "u": 2, "v": 9, "w": "2" }, { "u": 2, "v": 13, "w": "6" }, { "u": 2, "v": 14, "w": "2" }, { "u": 2, "v": 16, "w": "1" }, { "u": 2, "v": 20, "w": "1" }, { "u": 2, "v": 22, "w": "10" }, { "u": 2, "v": 23, "w": "2" }, { "u": 2, "v": 25, "w": "4" }, { "u": 2, "v": 27, "w": "3" }, { "u": 2, "v": 29, "w": "1" }, { "u": 2, "v": 30, "w": "1" }, { "u": 2, "v": 35, "w": "5" }, { "u": 2, "v": 36, "w": "1" }, { "u": 2, "v": 38, "w": "4" }, { "u": 2, "v": 44, "w": "1" }, { "u": 2, "v": 45, "w": "1" }, { "u": 2, "v": 48, "w": "5" }, { "u": 2, "v": 49, "w": "3" }, { "u": 2, "v": 54, "w": "1" }, { "u": 2, "v": 56, "w": "1" }, { "u": 2, "v": 57, "w": "4" }, { "u": 3, "v": 1, "w": "2" }, { "u": 3, "v": 2, "w": "10" }, { "u": 3, "v": 4, "w": "6" }, { "u": 3, "v": 5, "w": "11" }, { "u": 3, "v": 6, "w": "14" }, { "u": 3, "v": 7, "w": "15" }, { "u": 3, "v": 8, "w": "4" }, { "u": 3, "v": 9, "w": "12" }, { "u": 3, "v": 11, "w": "5" }, { "u": 3, "v": 12, "w": "4" }, { "u": 3, "v": 13, "w": "3" }, { "u": 3, "v": 14, "w": "8" }, { "u": 3, "v": 15, "w": "10" }, { "u": 3, "v": 16, "w": "8" }, { "u": 3, "v": 17, "w": "11" }, { "u": 3, "v": 19, "w": "2" }, { "u": 3, "v": 20, "w": "19" }, { "u": 3, "v": 21, "w": "2" }, { "u": 3, "v": 22, "w": "15" }, { "u": 3, "v": 23, "w": "1" }, { "u": 3, "v": 24, "w": "2" }, { "u": 3, "v": 25, "w": "6" }, { "u": 3, "v": 26, "w": "1" }, { "u": 3, "v": 27, "w": "5" }, { "u": 3, "v": 29, "w": "12" }, { "u": 3, "v": 30, "w": "5" }, { "u": 3, "v": 31, "w": "4" }, { "u": 3, "v": 33, "w": "1" }, { "u": 3, "v": 34, "w": "4" }, { "u": 3, "v": 35, "w": "15" }, { "u": 3, "v": 36, "w": "3" }, { "u": 3, "v": 37, "w": "1" }, { "u": 3, "v": 38, "w": "3" }, { "u": 3, "v": 39, "w": "6" }, { "u": 3, "v": 41, "w": "2" }, { "u": 3, "v": 42, "w": "3" }, { "u": 3, "v": 44, "w": "9" }, { "u": 3, "v": 45, "w": "8" }, { "u": 3, "v": 46, "w": "2" }, { "u": 3, "v": 47, "w": "1" }, { "u": 3, "v": 48, "w": "3" }, { "u": 3, "v": 49, "w": "6" }, { "u": 3, "v": 50, "w": "2" }, { "u": 3, "v": 52, "w": "2" }, { "u": 3, "v": 53, "w": "2" }, { "u": 3, "v": 54, "w": "16" }, { "u": 3, "v": 55, "w": "4" }, { "u": 3, "v": 56, "w": "5" }, { "u": 3, "v": 57, "w": "19" }, { "u": 3, "v": 58, "w": "1" }, { "u": 4, "v": 1, "w": "1" }, { "u": 4, "v": 3, "w": "6" }, { "u": 4, "v": 5, "w": "2" }, { "u": 4, "v": 6, "w": "3" }, { "u": 4, "v": 7, "w": "9" }, { "u": 4, "v": 8, "w": "1" }, { "u": 4, "v": 9, "w": "8" }, { "u": 4, "v": 12, "w": "5" }, { "u": 4, "v": 15, "w": "2" }, { "u": 4, "v": 16, "w": "4" }, { "u": 4, "v": 17, "w": "3" }, { "u": 4, "v": 18, "w": "2" }, { "u": 4, "v": 19, "w": "2" }, { "u": 4, "v": 20, "w": "6" }, { "u": 4, "v": 22, "w": "1" }, { "u": 4, "v": 23, "w": "1" }, { "u": 4, "v": 24, "w": "3" }, { "u": 4, "v": 25, "w": "1" }, { "u": 4, "v": 27, "w": "5" }, { "u": 4, "v": 28, "w": "1" }, { "u": 4, "v": 29, "w": "1" }, { "u": 4, "v": 30, "w": "3" }, { "u": 4, "v": 32, "w": "1" }, { "u": 4, "v": 33, "w": "1" }, { "u": 4, "v": 34, "w": "4" }, { "u": 4, "v": 35, "w": "1" }, { "u": 4, "v": 37, "w": "1" }, { "u": 4, "v": 38, "w": "3" }, { "u": 4, "v": 39, "w": "2" }, { "u": 4, "v": 41, "w": "1" }, { "u": 4, "v": 44, "w": "1" }, { "u": 4, "v": 45, "w": "1" }, { "u": 4, "v": 46, "w": "1" }, { "u": 4, "v": 47, "w": "1" }, { "u": 4, "v": 48, "w": "2" }, { "u": 4, "v": 49, "w": "1" }, { "u": 4, "v": 50, "w": "3" }, { "u": 4, "v": 53, "w": "2" }, { "u": 4, "v": 54, "w": "1" }, { "u": 4, "v": 55, "w": "2" }, { "u": 4, "v": 56, "w": "2" }, { "u": 4, "v": 57, "w": "3" }, { "u": 4, "v": 58, "w": "5" }, { "u": 5, "v": 3, "w": "11" }, { "u": 5, "v": 4, "w": "2" }, { "u": 5, "v": 6, "w": "2" }, { "u": 5, "v": 7, "w": "8" }, { "u": 5, "v": 8, "w": "1" }, { "u": 5, "v": 9, "w": "1" }, { "u": 5, "v": 10, "w": "1" }, { "u": 5, "v": 13, "w": "2" }, { "u": 5, "v": 15, "w": "1" }, { "u": 5, "v": 16, "w": "1" }, { "u": 5, "v": 20, "w": "3" }, { "u": 5, "v": 27, "w": "8" }, { "u": 5, "v": 29, "w": "1" }, { "u": 5, "v": 30, "w": "5" }, { "u": 5, "v": 33, "w": "1" }, { "u": 5, "v": 39, "w": "9" }, { "u": 5, "v": 40, "w": "2" }, { "u": 5, "v": 41, "w": "1" }, { "u": 5, "v": 43, "w": "1" }, { "u": 5, "v": 44, "w": "8" }, { "u": 5, "v": 45, "w": "25" }, { "u": 5, "v": 53, "w": "1" }, { "u": 5, "v": 54, "w": "2" }, { "u": 5, "v": 57, "w": "4" }, { "u": 6, "v": 2, "w": "2" }, { "u": 6, "v": 3, "w": "14" }, { "u": 6, "v": 4, "w": "3" }, { "u": 6, "v": 5, "w": "2" }, { "u": 6, "v": 7, "w": "30" }, { "u": 6, "v": 8, "w": "2" }, { "u": 6, "v": 9, "w": "8" }, { "u": 6, "v": 11, "w": "4" }, { "u": 6, "v": 12, "w": "4" }, { "u": 6, "v": 13, "w": "1" }, { "u": 6, "v": 14, "w": "6" }, { "u": 6, "v": 15, "w": "2" }, { "u": 6, "v": 16, "w": "14" }, { "u": 6, "v": 17, "w": "9" }, { "u": 6, "v": 19, "w": "1" }, { "u": 6, "v": 20, "w": "51" }, { "u": 6, "v": 22, "w": "3" }, { "u": 6, "v": 23, "w": "2" }, { "u": 6, "v": 24, "w": "1" }, { "u": 6, "v": 26, "w": "1" }, { "u": 6, "v": 27, "w": "6" }, { "u": 6, "v": 29, "w": "3" }, { "u": 6, "v": 30, "w": "11" }, { "u": 6, "v": 31, "w": "2" }, { "u": 6, "v": 33, "w": "15" }, { "u": 6, "v": 34, "w": "5" }, { "u": 6, "v": 35, "w": "3" }, { "u": 6, "v": 36, "w": "1" }, { "u": 6, "v": 38, "w": "2" }, { "u": 6, "v": 39, "w": "2" }, { "u": 6, "v": 40, "w": "1" }, { "u": 6, "v": 41, "w": "3" }, { "u": 6, "v": 42, "w": "1" }, { "u": 6, "v": 44, "w": "3" }, { "u": 6, "v": 45, "w": "2" }, { "u": 6, "v": 46, "w": "2" }, { "u": 6, "v": 47, "w": "6" }, { "u": 6, "v": 48, "w": "1" }, { "u": 6, "v": 49, "w": "3" }, { "u": 6, "v": 50, "w": "4" }, { "u": 6, "v": 52, "w": "2" }, { "u": 6, "v": 53, "w": "8" }, { "u": 6, "v": 54, "w": "9" }, { "u": 6, "v": 55, "w": "3" }, { "u": 6, "v": 56, "w": "2" }, { "u": 6, "v": 57, "w": "18" }, { "u": 6, "v": 58, "w": "2" }, { "u": 7, "v": 1, "w": "2" }, { "u": 7, "v": 2, "w": "1" }, { "u": 7, "v": 3, "w": "15" }, { "u": 7, "v": 4, "w": "9" }, { "u": 7, "v": 5, "w": "8" }, { "u": 7, "v": 6, "w": "30" }, { "u": 7, "v": 8, "w": "10" }, { "u": 7, "v": 9, "w": "4" }, { "u": 7, "v": 10, "w": "2" }, { "u": 7, "v": 11, "w": "7" }, { "u": 7, "v": 12, "w": "3" }, { "u": 7, "v": 14, "w": "12" }, { "u": 7, "v": 15, "w": "9" }, { "u": 7, "v": 16, "w": "10" }, { "u": 7, "v": 17, "w": "9" }, { "u": 7, "v": 18, "w": "2" }, { "u": 7, "v": 19, "w": "3" }, { "u": 7, "v": 20, "w": "40" }, { "u": 7, "v": 21, "w": "2" }, { "u": 7, "v": 22, "w": "2" }, { "u": 7, "v": 23, "w": "5" }, { "u": 7, "v": 24, "w": "2" }, { "u": 7, "v": 26, "w": "1" }, { "u": 7, "v": 27, "w": "19" }, { "u": 7, "v": 28, "w": "1" }, { "u": 7, "v": 29, "w": "10" }, { "u": 7, "v": 30, "w": "14" }, { "u": 7, "v": 31, "w": "5" }, { "u": 7, "v": 32, "w": "3" }, { "u": 7, "v": 33, "w": "14" }, { "u": 7, "v": 34, "w": "7" }, { "u": 7, "v": 35, "w": "7" }, { "u": 7, "v": 36, "w": "5" }, { "u": 7, "v": 37, "w": "3" }, { "u": 7, "v": 38, "w": "4" }, { "u": 7, "v": 39, "w": "5" }, { "u": 7, "v": 40, "w": "7" }, { "u": 7, "v": 41, "w": "8" }, { "u": 7, "v": 42, "w": "5" }, { "u": 7, "v": 44, "w": "2" }, { "u": 7, "v": 45, "w": "4" }, { "u": 7, "v": 46, "w": "7" }, { "u": 7, "v": 47, "w": "3" }, { "u": 7, "v": 48, "w": "7" }, { "u": 7, "v": 49, "w": "7" }, { "u": 7, "v": 50, "w": "2" }, { "u": 7, "v": 53, "w": "6" }, { "u": 7, "v": 54, "w": "5" }, { "u": 7, "v": 55, "w": "14" }, { "u": 7, "v": 56, "w": "16" }, { "u": 7, "v": 57, "w": "20" }, { "u": 7, "v": 58, "w": "4" }, { "u": 8, "v": 3, "w": "4" }, { "u": 8, "v": 4, "w": "1" }, { "u": 8, "v": 5, "w": "1" }, { "u": 8, "v": 6, "w": "2" }, { "u": 8, "v": 7, "w": "10" }, { "u": 8, "v": 9, "w": "3" }, { "u": 8, "v": 11, "w": "2" }, { "u": 8, "v": 13, "w": "1" }, { "u": 8, "v": 14, "w": "3" }, { "u": 8, "v": 15, "w": "3" }, { "u": 8, "v": 16, "w": "3" }, { "u": 8, "v": 17, "w": "5" }, { "u": 8, "v": 20, "w": "6" }, { "u": 8, "v": 21, "w": "1" }, { "u": 8, "v": 23, "w": "2" }, { "u": 8, "v": 24, "w": "3" }, { "u": 8, "v": 26, "w": "1" }, { "u": 8, "v": 27, "w": "6" }, { "u": 8, "v": 29, "w": "2" }, { "u": 8, "v": 31, "w": "9" }, { "u": 8, "v": 32, "w": "1" }, { "u": 8, "v": 34, "w": "1" }, { "u": 8, "v": 35, "w": "2" }, { "u": 8, "v": 36, "w": "4" }, { "u": 8, "v": 37, "w": "2" }, { "u": 8, "v": 38, "w": "5" }, { "u": 8, "v": 39, "w": "1" }, { "u": 8, "v": 41, "w": "3" }, { "u": 8, "v": 42, "w": "5" }, { "u": 8, "v": 45, "w": "5" }, { "u": 8, "v": 47, "w": "1" }, { "u": 8, "v": 48, "w": "3" }, { "u": 8, "v": 49, "w": "1" }, { "u": 8, "v": 50, "w": "1" }, { "u": 8, "v": 52, "w": "1" }, { "u": 8, "v": 53, "w": "2" }, { "u": 8, "v": 54, "w": "5" }, { "u": 8, "v": 56, "w": "2" }, { "u": 8, "v": 57, "w": "4" }, { "u": 8, "v": 58, "w": "2" }, { "u": 9, "v": 2, "w": "2" }, { "u": 9, "v": 3, "w": "12" }, { "u": 9, "v": 4, "w": "8" }, { "u": 9, "v": 5, "w": "1" }, { "u": 9, "v": 6, "w": "8" }, { "u": 9, "v": 7, "w": "4" }, { "u": 9, "v": 8, "w": "3" }, { "u": 9, "v": 11, "w": "5" }, { "u": 9, "v": 12, "w": "5" }, { "u": 9, "v": 13, "w": "2" }, { "u": 9, "v": 14, "w": "2" }, { "u": 9, "v": 15, "w": "4" }, { "u": 9, "v": 16, "w": "5" }, { "u": 9, "v": 17, "w": "6" }, { "u": 9, "v": 18, "w": "1" }, { "u": 9, "v": 20, "w": "5" }, { "u": 9, "v": 22, "w": "5" }, { "u": 9, "v": 24, "w": "3" }, { "u": 9, "v": 25, "w": "3" }, { "u": 9, "v": 26, "w": "3" }, { "u": 9, "v": 27, "w": "3" }, { "u": 9, "v": 28, "w": "1" }, { "u": 9, "v": 29, "w": "2" }, { "u": 9, "v": 30, "w": "3" }, { "u": 9, "v": 31, "w": "1" }, { "u": 9, "v": 33, "w": "2" }, { "u": 9, "v": 34, "w": "4" }, { "u": 9, "v": 35, "w": "4" }, { "u": 9, "v": 36, "w": "3" }, { "u": 9, "v": 37, "w": "5" }, { "u": 9, "v": 38, "w": "1" }, { "u": 9, "v": 39, "w": "2" }, { "u": 9, "v": 41, "w": "1" }, { "u": 9, "v": 42, "w": "1" }, { "u": 9, "v": 43, "w": "1" }, { "u": 9, "v": 44, "w": "2" }, { "u": 9, "v": 47, "w": "4" }, { "u": 9, "v": 49, "w": "1" }, { "u": 9, "v": 50, "w": "4" }, { "u": 9, "v": 52, "w": "6" }, { "u": 9, "v": 53, "w": "1" }, { "u": 9, "v": 54, "w": "4" }, { "u": 9, "v": 55, "w": "3" }, { "u": 9, "v": 56, "w": "2" }, { "u": 9, "v": 57, "w": "7" }, { "u": 9, "v": 58, "w": "1" }, { "u": 10, "v": 5, "w": "1" }, { "u": 10, "v": 7, "w": "2" }, { "u": 10, "v": 15, "w": "1" }, { "u": 10, "v": 16, "w": "2" }, { "u": 10, "v": 27, "w": "6" }, { "u": 10, "v": 29, "w": "1" }, { "u": 10, "v": 31, "w": "1" }, { "u": 10, "v": 38, "w": "1" }, { "u": 10, "v": 39, "w": "2" }, { "u": 10, "v": 40, "w": "2" }, { "u": 10, "v": 46, "w": "1" }, { "u": 10, "v": 54, "w": "1" }, { "u": 11, "v": 1, "w": "1" }, { "u": 11, "v": 3, "w": "5" }, { "u": 11, "v": 6, "w": "4" }, { "u": 11, "v": 7, "w": "7" }, { "u": 11, "v": 8, "w": "2" }, { "u": 11, "v": 9, "w": "5" }, { "u": 11, "v": 14, "w": "1" }, { "u": 11, "v": 15, "w": "3" }, { "u": 11, "v": 16, "w": "3" }, { "u": 11, "v": 17, "w": "5" }, { "u": 11, "v": 18, "w": "3" }, { "u": 11, "v": 20, "w": "7" }, { "u": 11, "v": 21, "w": "4" }, { "u": 11, "v": 22, "w": "1" }, { "u": 11, "v": 24, "w": "3" }, { "u": 11, "v": 27, "w": "4" }, { "u": 11, "v": 29, "w": "5" }, { "u": 11, "v": 30, "w": "1" }, { "u": 11, "v": 31, "w": "3" }, { "u": 11, "v": 34, "w": "2" }, { "u": 11, "v": 35, "w": "2" }, { "u": 11, "v": 36, "w": "3" }, { "u": 11, "v": 37, "w": "5" }, { "u": 11, "v": 38, "w": "3" }, { "u": 11, "v": 39, "w": "2" }, { "u": 11, "v": 42, "w": "1" }, { "u": 11, "v": 44, "w": "2" }, { "u": 11, "v": 45, "w": "1" }, { "u": 11, "v": 46, "w": "4" }, { "u": 11, "v": 47, "w": "5" }, { "u": 11, "v": 48, "w": "2" }, { "u": 11, "v": 49, "w": "1" }, { "u": 11, "v": 54, "w": "4" }, { "u": 11, "v": 55, "w": "6" }, { "u": 11, "v": 56, "w": "6" }, { "u": 11, "v": 57, "w": "12" }, { "u": 12, "v": 1, "w": "1" }, { "u": 12, "v": 3, "w": "4" }, { "u": 12, "v": 4, "w": "5" }, { "u": 12, "v": 6, "w": "4" }, { "u": 12, "v": 7, "w": "3" }, { "u": 12, "v": 9, "w": "5" }, { "u": 12, "v": 20, "w": "3" }, { "u": 12, "v": 22, "w": "1" }, { "u": 12, "v": 24, "w": "1" }, { "u": 12, "v": 25, "w": "1" }, { "u": 12, "v": 29, "w": "2" }, { "u": 12, "v": 31, "w": "2" }, { "u": 12, "v": 33, "w": "1" }, { "u": 12, "v": 34, "w": "2" }, { "u": 12, "v": 35, "w": "3" }, { "u": 12, "v": 36, "w": "2" }, { "u": 12, "v": 37, "w": "2" }, { "u": 12, "v": 38, "w": "1" }, { "u": 12, "v": 42, "w": "1" }, { "u": 12, "v": 44, "w": "1" }, { "u": 12, "v": 45, "w": "1" }, { "u": 12, "v": 46, "w": "1" }, { "u": 12, "v": 49, "w": "1" }, { "u": 12, "v": 50, "w": "2" }, { "u": 12, "v": 53, "w": "1" }, { "u": 12, "v": 54, "w": "2" }, { "u": 12, "v": 56, "w": "7" }, { "u": 12, "v": 57, "w": "3" }, { "u": 12, "v": 58, "w": "3" }, { "u": 13, "v": 1, "w": "2" }, { "u": 13, "v": 2, "w": "6" }, { "u": 13, "v": 3, "w": "3" }, { "u": 13, "v": 5, "w": "2" }, { "u": 13, "v": 6, "w": "1" }, { "u": 13, "v": 8, "w": "1" }, { "u": 13, "v": 9, "w": "2" }, { "u": 13, "v": 14, "w": "2" }, { "u": 13, "v": 15, "w": "1" }, { "u": 13, "v": 16, "w": "3" }, { "u": 13, "v": 17, "w": "3" }, { "u": 13, "v": 19, "w": "1" }, { "u": 13, "v": 22, "w": "6" }, { "u": 13, "v": 23, "w": "2" }, { "u": 13, "v": 27, "w": "3" }, { "u": 13, "v": 29, "w": "1" }, { "u": 13, "v": 33, "w": "1" }, { "u": 13, "v": 34, "w": "1" }, { "u": 13, "v": 35, "w": "1" }, { "u": 13, "v": 38, "w": "1" }, { "u": 13, "v": 39, "w": "1" }, { "u": 13, "v": 40, "w": "1" }, { "u": 13, "v": 41, "w": "1" }, { "u": 13, "v": 42, "w": "1" }, { "u": 13, "v": 43, "w": "1" }, { "u": 13, "v": 45, "w": "2" }, { "u": 13, "v": 46, "w": "1" }, { "u": 13, "v": 49, "w": "2" }, { "u": 13, "v": 53, "w": "2" }, { "u": 13, "v": 54, "w": "4" }, { "u": 13, "v": 55, "w": "1" }, { "u": 14, "v": 2, "w": "2" }, { "u": 14, "v": 3, "w": "8" }, { "u": 14, "v": 6, "w": "6" }, { "u": 14, "v": 7, "w": "12" }, { "u": 14, "v": 8, "w": "3" }, { "u": 14, "v": 9, "w": "2" }, { "u": 14, "v": 11, "w": "1" }, { "u": 14, "v": 13, "w": "2" }, { "u": 14, "v": 15, "w": "3" }, { "u": 14, "v": 16, "w": "8" }, { "u": 14, "v": 17, "w": "11" }, { "u": 14, "v": 18, "w": "1" }, { "u": 14, "v": 19, "w": "4" }, { "u": 14, "v": 20, "w": "8" }, { "u": 14, "v": 22, "w": "1" }, { "u": 14, "v": 25, "w": "1" }, { "u": 14, "v": 26, "w": "1" }, { "u": 14, "v": 27, "w": "4" }, { "u": 14, "v": 29, "w": "8" }, { "u": 14, "v": 30, "w": "4" }, { "u": 14, "v": 31, "w": "6" }, { "u": 14, "v": 33, "w": "3" }, { "u": 14, "v": 34, "w": "1" }, { "u": 14, "v": 35, "w": "5" }, { "u": 14, "v": 36, "w": "1" }, { "u": 14, "v": 37, "w": "1" }, { "u": 14, "v": 41, "w": "1" }, { "u": 14, "v": 42, "w": "3" }, { "u": 14, "v": 44, "w": "2" }, { "u": 14, "v": 45, "w": "2" }, { "u": 14, "v": 46, "w": "1" }, { "u": 14, "v": 47, "w": "1" }, { "u": 14, "v": 48, "w": "1" }, { "u": 14, "v": 53, "w": "1" }, { "u": 14, "v": 55, "w": "2" }, { "u": 14, "v": 56, "w": "1" }, { "u": 14, "v": 57, "w": "5" }, { "u": 14, "v": 58, "w": "1" }, { "u": 15, "v": 3, "w": "10" }, { "u": 15, "v": 4, "w": "2" }, { "u": 15, "v": 5, "w": "1" }, { "u": 15, "v": 6, "w": "2" }, { "u": 15, "v": 7, "w": "9" }, { "u": 15, "v": 8, "w": "3" }, { "u": 15, "v": 9, "w": "4" }, { "u": 15, "v": 10, "w": "1" }, { "u": 15, "v": 11, "w": "3" }, { "u": 15, "v": 13, "w": "1" }, { "u": 15, "v": 14, "w": "3" }, { "u": 15, "v": 16, "w": "9" }, { "u": 15, "v": 17, "w": "14" }, { "u": 15, "v": 19, "w": "6" }, { "u": 15, "v": 20, "w": "9" }, { "u": 15, "v": 22, "w": "2" }, { "u": 15, "v": 23, "w": "1" }, { "u": 15, "v": 24, "w": "2" }, { "u": 15, "v": 25, "w": "1" }, { "u": 15, "v": 27, "w": "4" }, { "u": 15, "v": 29, "w": "3" }, { "u": 15, "v": 31, "w": "2" }, { "u": 15, "v": 32, "w": "1" }, { "u": 15, "v": 33, "w": "1" }, { "u": 15, "v": 34, "w": "4" }, { "u": 15, "v": 35, "w": "2" }, { "u": 15, "v": 36, "w": "3" }, { "u": 15, "v": 38, "w": "6" }, { "u": 15, "v": 39, "w": "1" }, { "u": 15, "v": 41, "w": "7" }, { "u": 15, "v": 42, "w": "1" }, { "u": 15, "v": 44, "w": "7" }, { "u": 15, "v": 45, "w": "1" }, { "u": 15, "v": 46, "w": "1" }, { "u": 15, "v": 49, "w": "1" }, { "u": 15, "v": 50, "w": "1" }, { "u": 15, "v": 53, "w": "7" }, { "u": 15, "v": 54, "w": "6" }, { "u": 15, "v": 55, "w": "4" }, { "u": 15, "v": 56, "w": "9" }, { "u": 15, "v": 57, "w": "4" }, { "u": 16, "v": 2, "w": "1" }, { "u": 16, "v": 3, "w": "8" }, { "u": 16, "v": 4, "w": "4" }, { "u": 16, "v": 5, "w": "1" }, { "u": 16, "v": 6, "w": "14" }, { "u": 16, "v": 7, "w": "10" }, { "u": 16, "v": 8, "w": "3" }, { "u": 16, "v": 9, "w": "5" }, { "u": 16, "v": 10, "w": "2" }, { "u": 16, "v": 11, "w": "3" }, { "u": 16, "v": 13, "w": "3" }, { "u": 16, "v": 14, "w": "8" }, { "u": 16, "v": 15, "w": "9" }, { "u": 16, "v": 17, "w": "26" }, { "u": 16, "v": 18, "w": "3" }, { "u": 16, "v": 19, "w": "1" }, { "u": 16, "v": 20, "w": "12" }, { "u": 16, "v": 22, "w": "2" }, { "u": 16, "v": 25, "w": "1" }, { "u": 16, "v": 27, "w": "7" }, { "u": 16, "v": 29, "w": "5" }, { "u": 16, "v": 30, "w": "6" }, { "u": 16, "v": 31, "w": "5" }, { "u": 16, "v": 32, "w": "4" }, { "u": 16, "v": 33, "w": "2" }, { "u": 16, "v": 34, "w": "2" }, { "u": 16, "v": 35, "w": "2" }, { "u": 16, "v": 36, "w": "2" }, { "u": 16, "v": 38, "w": "4" }, { "u": 16, "v": 39, "w": "4" }, { "u": 16, "v": 41, "w": "2" }, { "u": 16, "v": 42, "w": "5" }, { "u": 16, "v": 43, "w": "1" }, { "u": 16, "v": 44, "w": "3" }, { "u": 16, "v": 45, "w": "2" }, { "u": 16, "v": 46, "w": "1" }, { "u": 16, "v": 47, "w": "1" }, { "u": 16, "v": 48, "w": "4" }, { "u": 16, "v": 50, "w": "2" }, { "u": 16, "v": 53, "w": "8" }, { "u": 16, "v": 54, "w": "4" }, { "u": 16, "v": 55, "w": "2" }, { "u": 16, "v": 57, "w": "11" }, { "u": 16, "v": 58, "w": "3" }, { "u": 17, "v": 1, "w": "1" }, { "u": 17, "v": 3, "w": "11" }, { "u": 17, "v": 4, "w": "3" }, { "u": 17, "v": 6, "w": "9" }, { "u": 17, "v": 7, "w": "9" }, { "u": 17, "v": 8, "w": "5" }, { "u": 17, "v": 9, "w": "6" }, { "u": 17, "v": 11, "w": "5" }, { "u": 17, "v": 13, "w": "3" }, { "u": 17, "v": 14, "w": "11" }, { "u": 17, "v": 15, "w": "14" }, { "u": 17, "v": 16, "w": "26" }, { "u": 17, "v": 18, "w": "3" }, { "u": 17, "v": 20, "w": "9" }, { "u": 17, "v": 22, "w": "1" }, { "u": 17, "v": 25, "w": "1" }, { "u": 17, "v": 27, "w": "5" }, { "u": 17, "v": 29, "w": "5" }, { "u": 17, "v": 30, "w": "2" }, { "u": 17, "v": 31, "w": "2" }, { "u": 17, "v": 32, "w": "4" }, { "u": 17, "v": 33, "w": "2" }, { "u": 17, "v": 34, "w": "1" }, { "u": 17, "v": 35, "w": "4" }, { "u": 17, "v": 36, "w": "2" }, { "u": 17, "v": 38, "w": "1" }, { "u": 17, "v": 39, "w": "1" }, { "u": 17, "v": 40, "w": "1" }, { "u": 17, "v": 41, "w": "2" }, { "u": 17, "v": 42, "w": "3" }, { "u": 17, "v": 44, "w": "3" }, { "u": 17, "v": 45, "w": "1" }, { "u": 17, "v": 48, "w": "3" }, { "u": 17, "v": 49, "w": "1" }, { "u": 17, "v": 50, "w": "2" }, { "u": 17, "v": 53, "w": "7" }, { "u": 17, "v": 54, "w": "7" }, { "u": 17, "v": 55, "w": "4" }, { "u": 17, "v": 57, "w": "11" }, { "u": 18, "v": 4, "w": "2" }, { "u": 18, "v": 7, "w": "2" }, { "u": 18, "v": 9, "w": "1" }, { "u": 18, "v": 11, "w": "3" }, { "u": 18, "v": 14, "w": "1" }, { "u": 18, "v": 16, "w": "3" }, { "u": 18, "v": 17, "w": "3" }, { "u": 18, "v": 21, "w": "3" }, { "u": 18, "v": 29, "w": "1" }, { "u": 18, "v": 32, "w": "3" }, { "u": 18, "v": 34, "w": "1" }, { "u": 18, "v": 35, "w": "1" }, { "u": 18, "v": 36, "w": "1" }, { "u": 18, "v": 37, "w": "1" }, { "u": 18, "v": 39, "w": "1" }, { "u": 18, "v": 44, "w": "1" }, { "u": 18, "v": 46, "w": "2" }, { "u": 18, "v": 48, "w": "2" }, { "u": 18, "v": 55, "w": "2" }, { "u": 18, "v": 56, "w": "1" }, { "u": 18, "v": 58, "w": "1" }, { "u": 19, "v": 1, "w": "1" }, { "u": 19, "v": 3, "w": "2" }, { "u": 19, "v": 4, "w": "2" }, { "u": 19, "v": 6, "w": "1" }, { "u": 19, "v": 7, "w": "3" }, { "u": 19, "v": 13, "w": "1" }, { "u": 19, "v": 14, "w": "4" }, { "u": 19, "v": 15, "w": "6" }, { "u": 19, "v": 16, "w": "1" }, { "u": 19, "v": 20, "w": "5" }, { "u": 19, "v": 23, "w": "2" }, { "u": 19, "v": 24, "w": "1" }, { "u": 19, "v": 25, "w": "3" }, { "u": 19, "v": 30, "w": "1" }, { "u": 19, "v": 31, "w": "1" }, { "u": 19, "v": 34, "w": "1" }, { "u": 19, "v": 35, "w": "1" }, { "u": 19, "v": 36, "w": "1" }, { "u": 19, "v": 37, "w": "1" }, { "u": 19, "v": 38, "w": "2" }, { "u": 19, "v": 40, "w": "1" }, { "u": 19, "v": 41, "w": "14" }, { "u": 19, "v": 42, "w": "1" }, { "u": 19, "v": 44, "w": "1" }, { "u": 19, "v": 47, "w": "1" }, { "u": 19, "v": 49, "w": "3" }, { "u": 19, "v": 53, "w": "1" }, { "u": 19, "v": 56, "w": "3" }, { "u": 19, "v": 57, "w": "1" }, { "u": 19, "v": 58, "w": "2" }, { "u": 20, "v": 2, "w": "1" }, { "u": 20, "v": 3, "w": "19" }, { "u": 20, "v": 4, "w": "6" }, { "u": 20, "v": 5, "w": "3" }, { "u": 20, "v": 6, "w": "51" }, { "u": 20, "v": 7, "w": "40" }, { "u": 20, "v": 8, "w": "6" }, { "u": 20, "v": 9, "w": "5" }, { "u": 20, "v": 11, "w": "7" }, { "u": 20, "v": 12, "w": "3" }, { "u": 20, "v": 14, "w": "8" }, { "u": 20, "v": 15, "w": "9" }, { "u": 20, "v": 16, "w": "12" }, { "u": 20, "v": 17, "w": "9" }, { "u": 20, "v": 19, "w": "5" }, { "u": 20, "v": 21, "w": "3" }, { "u": 20, "v": 22, "w": "2" }, { "u": 20, "v": 23, "w": "3" }, { "u": 20, "v": 24, "w": "2" }, { "u": 20, "v": 25, "w": "1" }, { "u": 20, "v": 26, "w": "1" }, { "u": 20, "v": 27, "w": "7" }, { "u": 20, "v": 28, "w": "1" }, { "u": 20, "v": 29, "w": "10" }, { "u": 20, "v": 30, "w": "6" }, { "u": 20, "v": 31, "w": "6" }, { "u": 20, "v": 32, "w": "1" }, { "u": 20, "v": 33, "w": "13" }, { "u": 20, "v": 34, "w": "12" }, { "u": 20, "v": 35, "w": "9" }, { "u": 20, "v": 36, "w": "2" }, { "u": 20, "v": 37, "w": "1" }, { "u": 20, "v": 38, "w": "6" }, { "u": 20, "v": 39, "w": "2" }, { "u": 20, "v": 40, "w": "1" }, { "u": 20, "v": 41, "w": "10" }, { "u": 20, "v": 42, "w": "4" }, { "u": 20, "v": 44, "w": "2" }, { "u": 20, "v": 45, "w": "2" }, { "u": 20, "v": 46, "w": "1" }, { "u": 20, "v": 47, "w": "2" }, { "u": 20, "v": 48, "w": "1" }, { "u": 20, "v": 49, "w": "6" }, { "u": 20, "v": 50, "w": "1" }, { "u": 20, "v": 53, "w": "12" }, { "u": 20, "v": 54, "w": "17" }, { "u": 20, "v": 55, "w": "11" }, { "u": 20, "v": 56, "w": "9" }, { "u": 20, "v": 57, "w": "23" }, { "u": 20, "v": 58, "w": "5" }, { "u": 21, "v": 3, "w": "2" }, { "u": 21, "v": 7, "w": "2" }, { "u": 21, "v": 8, "w": "1" }, { "u": 21, "v": 11, "w": "4" }, { "u": 21, "v": 18, "w": "3" }, { "u": 21, "v": 20, "w": "3" }, { "u": 21, "v": 23, "w": "1" }, { "u": 21, "v": 29, "w": "2" }, { "u": 21, "v": 31, "w": "2" }, { "u": 21, "v": 34, "w": "1" }, { "u": 21, "v": 35, "w": "1" }, { "u": 21, "v": 36, "w": "1" }, { "u": 21, "v": 38, "w": "1" }, { "u": 21, "v": 41, "w": "1" }, { "u": 21, "v": 42, "w": "1" }, { "u": 21, "v": 46, "w": "5" }, { "u": 21, "v": 48, "w": "1" }, { "u": 21, "v": 49, "w": "1" }, { "u": 21, "v": 54, "w": "1" }, { "u": 21, "v": 55, "w": "2" }, { "u": 21, "v": 56, "w": "4" }, { "u": 21, "v": 57, "w": "2" }, { "u": 21, "v": 58, "w": "1" }, { "u": 22, "v": 1, "w": "1" }, { "u": 22, "v": 2, "w": "10" }, { "u": 22, "v": 3, "w": "15" }, { "u": 22, "v": 4, "w": "1" }, { "u": 22, "v": 6, "w": "3" }, { "u": 22, "v": 7, "w": "2" }, { "u": 22, "v": 9, "w": "5" }, { "u": 22, "v": 11, "w": "1" }, { "u": 22, "v": 12, "w": "1" }, { "u": 22, "v": 13, "w": "6" }, { "u": 22, "v": 14, "w": "1" }, { "u": 22, "v": 15, "w": "2" }, { "u": 22, "v": 16, "w": "2" }, { "u": 22, "v": 17, "w": "1" }, { "u": 22, "v": 20, "w": "2" }, { "u": 22, "v": 23, "w": "1" }, { "u": 22, "v": 24, "w": "1" }, { "u": 22, "v": 25, "w": "7" }, { "u": 22, "v": 26, "w": "2" }, { "u": 22, "v": 27, "w": "1" }, { "u": 22, "v": 29, "w": "3" }, { "u": 22, "v": 30, "w": "1" }, { "u": 22, "v": 35, "w": "1" }, { "u": 22, "v": 36, "w": "1" }, { "u": 22, "v": 37, "w": "1" }, { "u": 22, "v": 39, "w": "2" }, { "u": 22, "v": 44, "w": "1" }, { "u": 22, "v": 46, "w": "3" }, { "u": 22, "v": 49, "w": "2" }, { "u": 22, "v": 50, "w": "1" }, { "u": 22, "v": 54, "w": "2" }, { "u": 22, "v": 55, "w": "1" }, { "u": 22, "v": 56, "w": "1" }, { "u": 22, "v": 57, "w": "3" }, { "u": 23, "v": 2, "w": "2" }, { "u": 23, "v": 3, "w": "1" }, { "u": 23, "v": 4, "w": "1" }, { "u": 23, "v": 6, "w": "2" }, { "u": 23, "v": 7, "w": "5" }, { "u": 23, "v": 8, "w": "2" }, { "u": 23, "v": 13, "w": "2" }, { "u": 23, "v": 15, "w": "1" }, { "u": 23, "v": 19, "w": "2" }, { "u": 23, "v": 20, "w": "3" }, { "u": 23, "v": 21, "w": "1" }, { "u": 23, "v": 22, "w": "1" }, { "u": 23, "v": 25, "w": "1" }, { "u": 23, "v": 27, "w": "1" }, { "u": 23, "v": 29, "w": "2" }, { "u": 23, "v": 31, "w": "2" }, { "u": 23, "v": 33, "w": "3" }, { "u": 23, "v": 34, "w": "1" }, { "u": 23, "v": 35, "w": "2" }, { "u": 23, "v": 36, "w": "1" }, { "u": 23, "v": 37, "w": "2" }, { "u": 23, "v": 38, "w": "2" }, { "u": 23, "v": 39, "w": "2" }, { "u": 23, "v": 40, "w": "1" }, { "u": 23, "v": 41, "w": "7" }, { "u": 23, "v": 42, "w": "1" }, { "u": 23, "v": 44, "w": "1" }, { "u": 23, "v": 45, "w": "2" }, { "u": 23, "v": 47, "w": "2" }, { "u": 23, "v": 49, "w": "11" }, { "u": 23, "v": 50, "w": "1" }, { "u": 23, "v": 51, "w": "1" }, { "u": 23, "v": 53, "w": "1" }, { "u": 23, "v": 54, "w": "4" }, { "u": 23, "v": 55, "w": "1" }, { "u": 23, "v": 56, "w": "2" }, { "u": 23, "v": 57, "w": "3" }, { "u": 23, "v": 58, "w": "1" }, { "u": 24, "v": 3, "w": "2" }, { "u": 24, "v": 4, "w": "3" }, { "u": 24, "v": 6, "w": "1" }, { "u": 24, "v": 7, "w": "2" }, { "u": 24, "v": 8, "w": "3" }, { "u": 24, "v": 9, "w": "3" }, { "u": 24, "v": 11, "w": "3" }, { "u": 24, "v": 12, "w": "1" }, { "u": 24, "v": 15, "w": "2" }, { "u": 24, "v": 19, "w": "1" }, { "u": 24, "v": 20, "w": "2" }, { "u": 24, "v": 22, "w": "1" }, { "u": 24, "v": 26, "w": "1" }, { "u": 24, "v": 29, "w": "1" }, { "u": 24, "v": 30, "w": "1" }, { "u": 24, "v": 31, "w": "1" }, { "u": 24, "v": 34, "w": "2" }, { "u": 24, "v": 35, "w": "1" }, { "u": 24, "v": 36, "w": "1" }, { "u": 24, "v": 38, "w": "2" }, { "u": 24, "v": 44, "w": "1" }, { "u": 24, "v": 46, "w": "1" }, { "u": 24, "v": 48, "w": "1" }, { "u": 24, "v": 56, "w": "2" }, { "u": 24, "v": 57, "w": "1" }, { "u": 24, "v": 58, "w": "1" }, { "u": 25, "v": 2, "w": "4" }, { "u": 25, "v": 3, "w": "6" }, { "u": 25, "v": 4, "w": "1" }, { "u": 25, "v": 9, "w": "3" }, { "u": 25, "v": 12, "w": "1" }, { "u": 25, "v": 14, "w": "1" }, { "u": 25, "v": 15, "w": "1" }, { "u": 25, "v": 16, "w": "1" }, { "u": 25, "v": 17, "w": "1" }, { "u": 25, "v": 19, "w": "3" }, { "u": 25, "v": 20, "w": "1" }, { "u": 25, "v": 22, "w": "7" }, { "u": 25, "v": 23, "w": "1" }, { "u": 25, "v": 29, "w": "3" }, { "u": 25, "v": 30, "w": "1" }, { "u": 25, "v": 35, "w": "3" }, { "u": 25, "v": 37, "w": "1" }, { "u": 25, "v": 38, "w": "1" }, { "u": 25, "v": 41, "w": "4" }, { "u": 25, "v": 44, "w": "1" }, { "u": 25, "v": 53, "w": "2" }, { "u": 25, "v": 54, "w": "1" }, { "u": 25, "v": 55, "w": "1" }, { "u": 25, "v": 56, "w": "1" }, { "u": 25, "v": 57, "w": "5" }, { "u": 26, "v": 3, "w": "1" }, { "u": 26, "v": 6, "w": "1" }, { "u": 26, "v": 7, "w": "1" }, { "u": 26, "v": 8, "w": "1" }, { "u": 26, "v": 9, "w": "3" }, { "u": 26, "v": 14, "w": "1" }, { "u": 26, "v": 20, "w": "1" }, { "u": 26, "v": 22, "w": "2" }, { "u": 26, "v": 24, "w": "1" }, { "u": 26, "v": 27, "w": "1" }, { "u": 26, "v": 30, "w": "1" }, { "u": 26, "v": 35, "w": "1" }, { "u": 26, "v": 38, "w": "1" }, { "u": 26, "v": 39, "w": "3" }, { "u": 26, "v": 45, "w": "1" }, { "u": 26, "v": 48, "w": "1" }, { "u": 26, "v": 49, "w": "2" }, { "u": 26, "v": 52, "w": "2" }, { "u": 26, "v": 54, "w": "1" }, { "u": 26, "v": 55, "w": "1" }, { "u": 26, "v": 56, "w": "1" }, { "u": 26, "v": 57, "w": "2" }, { "u": 27, "v": 2, "w": "3" }, { "u": 27, "v": 3, "w": "5" }, { "u": 27, "v": 4, "w": "5" }, { "u": 27, "v": 5, "w": "8" }, { "u": 27, "v": 6, "w": "6" }, { "u": 27, "v": 7, "w": "19" }, { "u": 27, "v": 8, "w": "6" }, { "u": 27, "v": 9, "w": "3" }, { "u": 27, "v": 10, "w": "6" }, { "u": 27, "v": 11, "w": "4" }, { "u": 27, "v": 13, "w": "3" }, { "u": 27, "v": 14, "w": "4" }, { "u": 27, "v": 15, "w": "4" }, { "u": 27, "v": 16, "w": "7" }, { "u": 27, "v": 17, "w": "5" }, { "u": 27, "v": 20, "w": "7" }, { "u": 27, "v": 22, "w": "1" }, { "u": 27, "v": 23, "w": "1" }, { "u": 27, "v": 26, "w": "1" }, { "u": 27, "v": 29, "w": "6" }, { "u": 27, "v": 30, "w": "6" }, { "u": 27, "v": 31, "w": "2" }, { "u": 27, "v": 32, "w": "1" }, { "u": 27, "v": 33, "w": "1" }, { "u": 27, "v": 34, "w": "4" }, { "u": 27, "v": 36, "w": "1" }, { "u": 27, "v": 38, "w": "2" }, { "u": 27, "v": 39, "w": "4" }, { "u": 27, "v": 41, "w": "3" }, { "u": 27, "v": 42, "w": "2" }, { "u": 27, "v": 43, "w": "1" }, { "u": 27, "v": 44, "w": "1" }, { "u": 27, "v": 45, "w": "4" }, { "u": 27, "v": 46, "w": "1" }, { "u": 27, "v": 48, "w": "5" }, { "u": 27, "v": 49, "w": "2" }, { "u": 27, "v": 53, "w": "1" }, { "u": 27, "v": 54, "w": "2" }, { "u": 27, "v": 55, "w": "2" }, { "u": 27, "v": 56, "w": "4" }, { "u": 27, "v": 57, "w": "6" }, { "u": 27, "v": 58, "w": "2" }, { "u": 28, "v": 4, "w": "1" }, { "u": 28, "v": 7, "w": "1" }, { "u": 28, "v": 9, "w": "1" }, { "u": 28, "v": 20, "w": "1" }, { "u": 28, "v": 34, "w": "1" }, { "u": 28, "v": 53, "w": "1" }, { "u": 29, "v": 1, "w": "2" }, { "u": 29, "v": 2, "w": "1" }, { "u": 29, "v": 3, "w": "12" }, { "u": 29, "v": 4, "w": "1" }, { "u": 29, "v": 5, "w": "1" }, { "u": 29, "v": 6, "w": "3" }, { "u": 29, "v": 7, "w": "10" }, { "u": 29, "v": 8, "w": "2" }, { "u": 29, "v": 9, "w": "2" }, { "u": 29, "v": 10, "w": "1" }, { "u": 29, "v": 11, "w": "5" }, { "u": 29, "v": 12, "w": "2" }, { "u": 29, "v": 13, "w": "1" }, { "u": 29, "v": 14, "w": "8" }, { "u": 29, "v": 15, "w": "3" }, { "u": 29, "v": 16, "w": "5" }, { "u": 29, "v": 17, "w": "5" }, { "u": 29, "v": 18, "w": "1" }, { "u": 29, "v": 20, "w": "10" }, { "u": 29, "v": 21, "w": "2" }, { "u": 29, "v": 22, "w": "3" }, { "u": 29, "v": 23, "w": "2" }, { "u": 29, "v": 24, "w": "1" }, { "u": 29, "v": 25, "w": "3" }, { "u": 29, "v": 27, "w": "6" }, { "u": 29, "v": 30, "w": "3" }, { "u": 29, "v": 31, "w": "1" }, { "u": 29, "v": 35, "w": "20" }, { "u": 29, "v": 36, "w": "2" }, { "u": 29, "v": 37, "w": "2" }, { "u": 29, "v": 38, "w": "3" }, { "u": 29, "v": 39, "w": "3" }, { "u": 29, "v": 40, "w": "2" }, { "u": 29, "v": 41, "w": "1" }, { "u": 29, "v": 42, "w": "2" }, { "u": 29, "v": 44, "w": "3" }, { "u": 29, "v": 45, "w": "3" }, { "u": 29, "v": 47, "w": "1" }, { "u": 29, "v": 48, "w": "1" }, { "u": 29, "v": 49, "w": "1" }, { "u": 29, "v": 50, "w": "1" }, { "u": 29, "v": 54, "w": "7" }, { "u": 29, "v": 55, "w": "1" }, { "u": 29, "v": 56, "w": "2" }, { "u": 29, "v": 57, "w": "10" }, { "u": 29, "v": 58, "w": "1" }, { "u": 30, "v": 1, "w": "1" }, { "u": 30, "v": 2, "w": "1" }, { "u": 30, "v": 3, "w": "5" }, { "u": 30, "v": 4, "w": "3" }, { "u": 30, "v": 5, "w": "5" }, { "u": 30, "v": 6, "w": "11" }, { "u": 30, "v": 7, "w": "14" }, { "u": 30, "v": 9, "w": "3" }, { "u": 30, "v": 11, "w": "1" }, { "u": 30, "v": 14, "w": "4" }, { "u": 30, "v": 16, "w": "6" }, { "u": 30, "v": 17, "w": "2" }, { "u": 30, "v": 19, "w": "1" }, { "u": 30, "v": 20, "w": "6" }, { "u": 30, "v": 22, "w": "1" }, { "u": 30, "v": 24, "w": "1" }, { "u": 30, "v": 25, "w": "1" }, { "u": 30, "v": 26, "w": "1" }, { "u": 30, "v": 27, "w": "6" }, { "u": 30, "v": 29, "w": "3" }, { "u": 30, "v": 31, "w": "3" }, { "u": 30, "v": 33, "w": "1" }, { "u": 30, "v": 35, "w": "6" }, { "u": 30, "v": 36, "w": "1" }, { "u": 30, "v": 37, "w": "1" }, { "u": 30, "v": 38, "w": "1" }, { "u": 30, "v": 39, "w": "3" }, { "u": 30, "v": 40, "w": "1" }, { "u": 30, "v": 41, "w": "4" }, { "u": 30, "v": 42, "w": "1" }, { "u": 30, "v": 43, "w": "2" }, { "u": 30, "v": 45, "w": "1" }, { "u": 30, "v": 47, "w": "5" }, { "u": 30, "v": 48, "w": "1" }, { "u": 30, "v": 49, "w": "3" }, { "u": 30, "v": 50, "w": "1" }, { "u": 30, "v": 53, "w": "3" }, { "u": 30, "v": 54, "w": "2" }, { "u": 30, "v": 55, "w": "1" }, { "u": 30, "v": 56, "w": "6" }, { "u": 30, "v": 57, "w": "10" }, { "u": 30, "v": 58, "w": "2" }, { "u": 31, "v": 1, "w": "1" }, { "u": 31, "v": 3, "w": "4" }, { "u": 31, "v": 6, "w": "2" }, { "u": 31, "v": 7, "w": "5" }, { "u": 31, "v": 8, "w": "9" }, { "u": 31, "v": 9, "w": "1" }, { "u": 31, "v": 10, "w": "1" }, { "u": 31, "v": 11, "w": "3" }, { "u": 31, "v": 12, "w": "2" }, { "u": 31, "v": 14, "w": "6" }, { "u": 31, "v": 15, "w": "2" }, { "u": 31, "v": 16, "w": "5" }, { "u": 31, "v": 17, "w": "2" }, { "u": 31, "v": 19, "w": "1" }, { "u": 31, "v": 20, "w": "6" }, { "u": 31, "v": 21, "w": "2" }, { "u": 31, "v": 23, "w": "2" }, { "u": 31, "v": 24, "w": "1" }, { "u": 31, "v": 27, "w": "2" }, { "u": 31, "v": 29, "w": "1" }, { "u": 31, "v": 30, "w": "3" }, { "u": 31, "v": 32, "w": "4" }, { "u": 31, "v": 34, "w": "3" }, { "u": 31, "v": 35, "w": "1" }, { "u": 31, "v": 36, "w": "3" }, { "u": 31, "v": 38, "w": "1" }, { "u": 31, "v": 40, "w": "1" }, { "u": 31, "v": 41, "w": "3" }, { "u": 31, "v": 42, "w": "3" }, { "u": 31, "v": 45, "w": "1" }, { "u": 31, "v": 46, "w": "3" }, { "u": 31, "v": 48, "w": "2" }, { "u": 31, "v": 49, "w": "1" }, { "u": 31, "v": 53, "w": "1" }, { "u": 31, "v": 54, "w": "4" }, { "u": 31, "v": 55, "w": "1" }, { "u": 31, "v": 56, "w": "1" }, { "u": 31, "v": 57, "w": "3" }, { "u": 31, "v": 58, "w": "2" }, { "u": 32, "v": 1, "w": "1" }, { "u": 32, "v": 4, "w": "1" }, { "u": 32, "v": 7, "w": "3" }, { "u": 32, "v": 8, "w": "1" }, { "u": 32, "v": 15, "w": "1" }, { "u": 32, "v": 16, "w": "4" }, { "u": 32, "v": 17, "w": "4" }, { "u": 32, "v": 18, "w": "3" }, { "u": 32, "v": 20, "w": "1" }, { "u": 32, "v": 27, "w": "1" }, { "u": 32, "v": 31, "w": "4" }, { "u": 32, "v": 34, "w": "2" }, { "u": 32, "v": 41, "w": "1" }, { "u": 32, "v": 46, "w": "3" }, { "u": 32, "v": 48, "w": "6" }, { "u": 32, "v": 58, "w": "1" }, { "u": 33, "v": 3, "w": "1" }, { "u": 33, "v": 4, "w": "1" }, { "u": 33, "v": 5, "w": "1" }, { "u": 33, "v": 6, "w": "15" }, { "u": 33, "v": 7, "w": "14" }, { "u": 33, "v": 9, "w": "2" }, { "u": 33, "v": 12, "w": "1" }, { "u": 33, "v": 13, "w": "1" }, { "u": 33, "v": 14, "w": "3" }, { "u": 33, "v": 15, "w": "1" }, { "u": 33, "v": 16, "w": "2" }, { "u": 33, "v": 17, "w": "2" }, { "u": 33, "v": 20, "w": "13" }, { "u": 33, "v": 23, "w": "3" }, { "u": 33, "v": 27, "w": "1" }, { "u": 33, "v": 30, "w": "1" }, { "u": 33, "v": 34, "w": "1" }, { "u": 33, "v": 35, "w": "1" }, { "u": 33, "v": 36, "w": "1" }, { "u": 33, "v": 40, "w": "3" }, { "u": 33, "v": 41, "w": "1" }, { "u": 33, "v": 49, "w": "1" }, { "u": 33, "v": 52, "w": "2" }, { "u": 33, "v": 53, "w": "8" }, { "u": 33, "v": 54, "w": "1" }, { "u": 33, "v": 56, "w": "1" }, { "u": 33, "v": 57, "w": "3" }, { "u": 34, "v": 1, "w": "2" }, { "u": 34, "v": 3, "w": "4" }, { "u": 34, "v": 4, "w": "4" }, { "u": 34, "v": 6, "w": "5" }, { "u": 34, "v": 7, "w": "7" }, { "u": 34, "v": 8, "w": "1" }, { "u": 34, "v": 9, "w": "4" }, { "u": 34, "v": 11, "w": "2" }, { "u": 34, "v": 12, "w": "2" }, { "u": 34, "v": 13, "w": "1" }, { "u": 34, "v": 14, "w": "1" }, { "u": 34, "v": 15, "w": "4" }, { "u": 34, "v": 16, "w": "2" }, { "u": 34, "v": 17, "w": "1" }, { "u": 34, "v": 18, "w": "1" }, { "u": 34, "v": 19, "w": "1" }, { "u": 34, "v": 20, "w": "12" }, { "u": 34, "v": 21, "w": "1" }, { "u": 34, "v": 23, "w": "1" }, { "u": 34, "v": 24, "w": "2" }, { "u": 34, "v": 27, "w": "4" }, { "u": 34, "v": 28, "w": "1" }, { "u": 34, "v": 31, "w": "3" }, { "u": 34, "v": 32, "w": "2" }, { "u": 34, "v": 33, "w": "1" }, { "u": 34, "v": 35, "w": "3" }, { "u": 34, "v": 36, "w": "1" }, { "u": 34, "v": 39, "w": "1" }, { "u": 34, "v": 40, "w": "1" }, { "u": 34, "v": 41, "w": "2" }, { "u": 34, "v": 42, "w": "1" }, { "u": 34, "v": 46, "w": "3" }, { "u": 34, "v": 47, "w": "2" }, { "u": 34, "v": 48, "w": "2" }, { "u": 34, "v": 49, "w": "1" }, { "u": 34, "v": 50, "w": "3" }, { "u": 34, "v": 53, "w": "2" }, { "u": 34, "v": 54, "w": "4" }, { "u": 34, "v": 55, "w": "3" }, { "u": 34, "v": 56, "w": "4" }, { "u": 34, "v": 57, "w": "3" }, { "u": 34, "v": 58, "w": "6" }, { "u": 35, "v": 1, "w": "1" }, { "u": 35, "v": 2, "w": "5" }, { "u": 35, "v": 3, "w": "15" }, { "u": 35, "v": 4, "w": "1" }, { "u": 35, "v": 6, "w": "3" }, { "u": 35, "v": 7, "w": "7" }, { "u": 35, "v": 8, "w": "2" }, { "u": 35, "v": 9, "w": "4" }, { "u": 35, "v": 11, "w": "2" }, { "u": 35, "v": 12, "w": "3" }, { "u": 35, "v": 13, "w": "1" }, { "u": 35, "v": 14, "w": "5" }, { "u": 35, "v": 15, "w": "2" }, { "u": 35, "v": 16, "w": "2" }, { "u": 35, "v": 17, "w": "4" }, { "u": 35, "v": 18, "w": "1" }, { "u": 35, "v": 19, "w": "1" }, { "u": 35, "v": 20, "w": "9" }, { "u": 35, "v": 21, "w": "1" }, { "u": 35, "v": 22, "w": "1" }, { "u": 35, "v": 23, "w": "2" }, { "u": 35, "v": 24, "w": "1" }, { "u": 35, "v": 25, "w": "3" }, { "u": 35, "v": 26, "w": "1" }, { "u": 35, "v": 29, "w": "20" }, { "u": 35, "v": 30, "w": "6" }, { "u": 35, "v": 31, "w": "1" }, { "u": 35, "v": 33, "w": "1" }, { "u": 35, "v": 34, "w": "3" }, { "u": 35, "v": 36, "w": "2" }, { "u": 35, "v": 37, "w": "1" }, { "u": 35, "v": 38, "w": "3" }, { "u": 35, "v": 39, "w": "2" }, { "u": 35, "v": 40, "w": "2" }, { "u": 35, "v": 41, "w": "3" }, { "u": 35, "v": 42, "w": "4" }, { "u": 35, "v": 43, "w": "2" }, { "u": 35, "v": 44, "w": "2" }, { "u": 35, "v": 47, "w": "1" }, { "u": 35, "v": 49, "w": "6" }, { "u": 35, "v": 50, "w": "1" }, { "u": 35, "v": 53, "w": "1" }, { "u": 35, "v": 54, "w": "12" }, { "u": 35, "v": 55, "w": "2" }, { "u": 35, "v": 56, "w": "3" }, { "u": 35, "v": 57, "w": "6" }, { "u": 35, "v": 58, "w": "2" }, { "u": 36, "v": 1, "w": "2" }, { "u": 36, "v": 2, "w": "1" }, { "u": 36, "v": 3, "w": "3" }, { "u": 36, "v": 6, "w": "1" }, { "u": 36, "v": 7, "w": "5" }, { "u": 36, "v": 8, "w": "4" }, { "u": 36, "v": 9, "w": "3" }, { "u": 36, "v": 11, "w": "3" }, { "u": 36, "v": 12, "w": "2" }, { "u": 36, "v": 14, "w": "1" }, { "u": 36, "v": 15, "w": "3" }, { "u": 36, "v": 16, "w": "2" }, { "u": 36, "v": 17, "w": "2" }, { "u": 36, "v": 18, "w": "1" }, { "u": 36, "v": 19, "w": "1" }, { "u": 36, "v": 20, "w": "2" }, { "u": 36, "v": 21, "w": "1" }, { "u": 36, "v": 22, "w": "1" }, { "u": 36, "v": 23, "w": "1" }, { "u": 36, "v": 24, "w": "1" }, { "u": 36, "v": 27, "w": "1" }, { "u": 36, "v": 29, "w": "2" }, { "u": 36, "v": 30, "w": "1" }, { "u": 36, "v": 31, "w": "3" }, { "u": 36, "v": 33, "w": "1" }, { "u": 36, "v": 34, "w": "1" }, { "u": 36, "v": 35, "w": "2" }, { "u": 36, "v": 39, "w": "1" }, { "u": 36, "v": 41, "w": "1" }, { "u": 36, "v": 42, "w": "2" }, { "u": 36, "v": 44, "w": "1" }, { "u": 36, "v": 46, "w": "3" }, { "u": 36, "v": 49, "w": "3" }, { "u": 36, "v": 53, "w": "1" }, { "u": 36, "v": 55, "w": "2" }, { "u": 36, "v": 56, "w": "10" }, { "u": 36, "v": 57, "w": "1" }, { "u": 36, "v": 58, "w": "1" }, { "u": 37, "v": 3, "w": "1" }, { "u": 37, "v": 4, "w": "1" }, { "u": 37, "v": 7, "w": "3" }, { "u": 37, "v": 8, "w": "2" }, { "u": 37, "v": 9, "w": "5" }, { "u": 37, "v": 11, "w": "5" }, { "u": 37, "v": 12, "w": "2" }, { "u": 37, "v": 14, "w": "1" }, { "u": 37, "v": 18, "w": "1" }, { "u": 37, "v": 19, "w": "1" }, { "u": 37, "v": 20, "w": "1" }, { "u": 37, "v": 22, "w": "1" }, { "u": 37, "v": 23, "w": "2" }, { "u": 37, "v": 25, "w": "1" }, { "u": 37, "v": 29, "w": "2" }, { "u": 37, "v": 30, "w": "1" }, { "u": 37, "v": 35, "w": "1" }, { "u": 37, "v": 39, "w": "3" }, { "u": 37, "v": 41, "w": "1" }, { "u": 37, "v": 45, "w": "1" }, { "u": 37, "v": 47, "w": "4" }, { "u": 37, "v": 49, "w": "2" }, { "u": 37, "v": 51, "w": "1" }, { "u": 37, "v": 53, "w": "2" }, { "u": 37, "v": 54, "w": "1" }, { "u": 37, "v": 56, "w": "1" }, { "u": 37, "v": 57, "w": "3" }, { "u": 38, "v": 2, "w": "4" }, { "u": 38, "v": 3, "w": "3" }, { "u": 38, "v": 4, "w": "3" }, { "u": 38, "v": 6, "w": "2" }, { "u": 38, "v": 7, "w": "4" }, { "u": 38, "v": 8, "w": "5" }, { "u": 38, "v": 9, "w": "1" }, { "u": 38, "v": 10, "w": "1" }, { "u": 38, "v": 11, "w": "3" }, { "u": 38, "v": 12, "w": "1" }, { "u": 38, "v": 13, "w": "1" }, { "u": 38, "v": 15, "w": "6" }, { "u": 38, "v": 16, "w": "4" }, { "u": 38, "v": 17, "w": "1" }, { "u": 38, "v": 19, "w": "2" }, { "u": 38, "v": 20, "w": "6" }, { "u": 38, "v": 21, "w": "1" }, { "u": 38, "v": 23, "w": "2" }, { "u": 38, "v": 24, "w": "2" }, { "u": 38, "v": 25, "w": "1" }, { "u": 38, "v": 26, "w": "1" }, { "u": 38, "v": 27, "w": "2" }, { "u": 38, "v": 29, "w": "3" }, { "u": 38, "v": 30, "w": "1" }, { "u": 38, "v": 31, "w": "1" }, { "u": 38, "v": 35, "w": "3" }, { "u": 38, "v": 40, "w": "1" }, { "u": 38, "v": 41, "w": "2" }, { "u": 38, "v": 42, "w": "1" }, { "u": 38, "v": 45, "w": "1" }, { "u": 38, "v": 47, "w": "2" }, { "u": 38, "v": 50, "w": "1" }, { "u": 38, "v": 53, "w": "1" }, { "u": 38, "v": 54, "w": "6" }, { "u": 38, "v": 55, "w": "1" }, { "u": 38, "v": 56, "w": "1" }, { "u": 38, "v": 57, "w": "4" }, { "u": 38, "v": 58, "w": "2" }, { "u": 39, "v": 3, "w": "6" }, { "u": 39, "v": 4, "w": "2" }, { "u": 39, "v": 5, "w": "9" }, { "u": 39, "v": 6, "w": "2" }, { "u": 39, "v": 7, "w": "5" }, { "u": 39, "v": 8, "w": "1" }, { "u": 39, "v": 9, "w": "2" }, { "u": 39, "v": 10, "w": "2" }, { "u": 39, "v": 11, "w": "2" }, { "u": 39, "v": 13, "w": "1" }, { "u": 39, "v": 15, "w": "1" }, { "u": 39, "v": 16, "w": "4" }, { "u": 39, "v": 17, "w": "1" }, { "u": 39, "v": 18, "w": "1" }, { "u": 39, "v": 20, "w": "2" }, { "u": 39, "v": 22, "w": "2" }, { "u": 39, "v": 23, "w": "2" }, { "u": 39, "v": 26, "w": "3" }, { "u": 39, "v": 27, "w": "4" }, { "u": 39, "v": 29, "w": "3" }, { "u": 39, "v": 30, "w": "3" }, { "u": 39, "v": 34, "w": "1" }, { "u": 39, "v": 35, "w": "2" }, { "u": 39, "v": 36, "w": "1" }, { "u": 39, "v": 37, "w": "3" }, { "u": 39, "v": 40, "w": "1" }, { "u": 39, "v": 44, "w": "4" }, { "u": 39, "v": 45, "w": "9" }, { "u": 39, "v": 46, "w": "2" }, { "u": 39, "v": 47, "w": "1" }, { "u": 39, "v": 48, "w": "2" }, { "u": 39, "v": 49, "w": "5" }, { "u": 39, "v": 50, "w": "4" }, { "u": 39, "v": 51, "w": "3" }, { "u": 39, "v": 54, "w": "2" }, { "u": 39, "v": 55, "w": "2" }, { "u": 39, "v": 56, "w": "1" }, { "u": 39, "v": 57, "w": "2" }, { "u": 40, "v": 5, "w": "2" }, { "u": 40, "v": 6, "w": "1" }, { "u": 40, "v": 7, "w": "7" }, { "u": 40, "v": 10, "w": "2" }, { "u": 40, "v": 13, "w": "1" }, { "u": 40, "v": 17, "w": "1" }, { "u": 40, "v": 19, "w": "1" }, { "u": 40, "v": 20, "w": "1" }, { "u": 40, "v": 23, "w": "1" }, { "u": 40, "v": 29, "w": "2" }, { "u": 40, "v": 30, "w": "1" }, { "u": 40, "v": 31, "w": "1" }, { "u": 40, "v": 33, "w": "3" }, { "u": 40, "v": 34, "w": "1" }, { "u": 40, "v": 35, "w": "2" }, { "u": 40, "v": 38, "w": "1" }, { "u": 40, "v": 39, "w": "1" }, { "u": 40, "v": 47, "w": "1" }, { "u": 40, "v": 53, "w": "1" }, { "u": 40, "v": 54, "w": "2" }, { "u": 40, "v": 57, "w": "2" }, { "u": 41, "v": 1, "w": "1" }, { "u": 41, "v": 3, "w": "2" }, { "u": 41, "v": 4, "w": "1" }, { "u": 41, "v": 5, "w": "1" }, { "u": 41, "v": 6, "w": "3" }, { "u": 41, "v": 7, "w": "8" }, { "u": 41, "v": 8, "w": "3" }, { "u": 41, "v": 9, "w": "1" }, { "u": 41, "v": 13, "w": "1" }, { "u": 41, "v": 14, "w": "1" }, { "u": 41, "v": 15, "w": "7" }, { "u": 41, "v": 16, "w": "2" }, { "u": 41, "v": 17, "w": "2" }, { "u": 41, "v": 19, "w": "14" }, { "u": 41, "v": 20, "w": "10" }, { "u": 41, "v": 21, "w": "1" }, { "u": 41, "v": 23, "w": "7" }, { "u": 41, "v": 25, "w": "4" }, { "u": 41, "v": 27, "w": "3" }, { "u": 41, "v": 29, "w": "1" }, { "u": 41, "v": 30, "w": "4" }, { "u": 41, "v": 31, "w": "3" }, { "u": 41, "v": 32, "w": "1" }, { "u": 41, "v": 33, "w": "1" }, { "u": 41, "v": 34, "w": "2" }, { "u": 41, "v": 35, "w": "3" }, { "u": 41, "v": 36, "w": "1" }, { "u": 41, "v": 37, "w": "1" }, { "u": 41, "v": 38, "w": "2" }, { "u": 41, "v": 42, "w": "1" }, { "u": 41, "v": 43, "w": "1" }, { "u": 41, "v": 44, "w": "1" }, { "u": 41, "v": 45, "w": "1" }, { "u": 41, "v": 49, "w": "9" }, { "u": 41, "v": 53, "w": "4" }, { "u": 41, "v": 54, "w": "1" }, { "u": 41, "v": 55, "w": "1" }, { "u": 41, "v": 56, "w": "5" }, { "u": 41, "v": 57, "w": "1" }, { "u": 41, "v": 58, "w": "2" }, { "u": 42, "v": 3, "w": "3" }, { "u": 42, "v": 6, "w": "1" }, { "u": 42, "v": 7, "w": "5" }, { "u": 42, "v": 8, "w": "5" }, { "u": 42, "v": 9, "w": "1" }, { "u": 42, "v": 11, "w": "1" }, { "u": 42, "v": 12, "w": "1" }, { "u": 42, "v": 13, "w": "1" }, { "u": 42, "v": 14, "w": "3" }, { "u": 42, "v": 15, "w": "1" }, { "u": 42, "v": 16, "w": "5" }, { "u": 42, "v": 17, "w": "3" }, { "u": 42, "v": 19, "w": "1" }, { "u": 42, "v": 20, "w": "4" }, { "u": 42, "v": 21, "w": "1" }, { "u": 42, "v": 23, "w": "1" }, { "u": 42, "v": 27, "w": "2" }, { "u": 42, "v": 29, "w": "2" }, { "u": 42, "v": 30, "w": "1" }, { "u": 42, "v": 31, "w": "3" }, { "u": 42, "v": 34, "w": "1" }, { "u": 42, "v": 35, "w": "4" }, { "u": 42, "v": 36, "w": "2" }, { "u": 42, "v": 38, "w": "1" }, { "u": 42, "v": 41, "w": "1" }, { "u": 42, "v": 43, "w": "1" }, { "u": 42, "v": 44, "w": "1" }, { "u": 42, "v": 45, "w": "1" }, { "u": 42, "v": 46, "w": "1" }, { "u": 42, "v": 47, "w": "1" }, { "u": 42, "v": 48, "w": "1" }, { "u": 42, "v": 49, "w": "1" }, { "u": 42, "v": 53, "w": "2" }, { "u": 42, "v": 54, "w": "1" }, { "u": 42, "v": 55, "w": "1" }, { "u": 42, "v": 57, "w": "3" }, { "u": 42, "v": 58, "w": "1" }, { "u": 43, "v": 5, "w": "1" }, { "u": 43, "v": 9, "w": "1" }, { "u": 43, "v": 13, "w": "1" }, { "u": 43, "v": 16, "w": "1" }, { "u": 43, "v": 27, "w": "1" }, { "u": 43, "v": 30, "w": "2" }, { "u": 43, "v": 35, "w": "2" }, { "u": 43, "v": 41, "w": "1" }, { "u": 43, "v": 42, "w": "1" }, { "u": 43, "v": 55, "w": "1" }, { "u": 44, "v": 2, "w": "1" }, { "u": 44, "v": 3, "w": "9" }, { "u": 44, "v": 4, "w": "1" }, { "u": 44, "v": 5, "w": "8" }, { "u": 44, "v": 6, "w": "3" }, { "u": 44, "v": 7, "w": "2" }, { "u": 44, "v": 9, "w": "2" }, { "u": 44, "v": 11, "w": "2" }, { "u": 44, "v": 12, "w": "1" }, { "u": 44, "v": 14, "w": "2" }, { "u": 44, "v": 15, "w": "7" }, { "u": 44, "v": 16, "w": "3" }, { "u": 44, "v": 17, "w": "3" }, { "u": 44, "v": 18, "w": "1" }, { "u": 44, "v": 19, "w": "1" }, { "u": 44, "v": 20, "w": "2" }, { "u": 44, "v": 22, "w": "1" }, { "u": 44, "v": 23, "w": "1" }, { "u": 44, "v": 24, "w": "1" }, { "u": 44, "v": 25, "w": "1" }, { "u": 44, "v": 27, "w": "1" }, { "u": 44, "v": 29, "w": "3" }, { "u": 44, "v": 35, "w": "2" }, { "u": 44, "v": 36, "w": "1" }, { "u": 44, "v": 39, "w": "4" }, { "u": 44, "v": 41, "w": "1" }, { "u": 44, "v": 42, "w": "1" }, { "u": 44, "v": 45, "w": "2" }, { "u": 44, "v": 47, "w": "1" }, { "u": 44, "v": 49, "w": "2" }, { "u": 44, "v": 50, "w": "1" }, { "u": 44, "v": 54, "w": "2" }, { "u": 44, "v": 55, "w": "1" }, { "u": 44, "v": 56, "w": "2" }, { "u": 44, "v": 57, "w": "3" }, { "u": 45, "v": 2, "w": "1" }, { "u": 45, "v": 3, "w": "8" }, { "u": 45, "v": 4, "w": "1" }, { "u": 45, "v": 5, "w": "25" }, { "u": 45, "v": 6, "w": "2" }, { "u": 45, "v": 7, "w": "4" }, { "u": 45, "v": 8, "w": "5" }, { "u": 45, "v": 11, "w": "1" }, { "u": 45, "v": 12, "w": "1" }, { "u": 45, "v": 13, "w": "2" }, { "u": 45, "v": 14, "w": "2" }, { "u": 45, "v": 15, "w": "1" }, { "u": 45, "v": 16, "w": "2" }, { "u": 45, "v": 17, "w": "1" }, { "u": 45, "v": 20, "w": "2" }, { "u": 45, "v": 23, "w": "2" }, { "u": 45, "v": 26, "w": "1" }, { "u": 45, "v": 27, "w": "4" }, { "u": 45, "v": 29, "w": "3" }, { "u": 45, "v": 30, "w": "1" }, { "u": 45, "v": 31, "w": "1" }, { "u": 45, "v": 37, "w": "1" }, { "u": 45, "v": 38, "w": "1" }, { "u": 45, "v": 39, "w": "9" }, { "u": 45, "v": 41, "w": "1" }, { "u": 45, "v": 42, "w": "1" }, { "u": 45, "v": 44, "w": "2" }, { "u": 45, "v": 47, "w": "1" }, { "u": 45, "v": 48, "w": "2" }, { "u": 45, "v": 49, "w": "4" }, { "u": 45, "v": 50, "w": "1" }, { "u": 45, "v": 51, "w": "1" }, { "u": 45, "v": 54, "w": "4" }, { "u": 45, "v": 57, "w": "1" }, { "u": 46, "v": 3, "w": "2" }, { "u": 46, "v": 4, "w": "1" }, { "u": 46, "v": 6, "w": "2" }, { "u": 46, "v": 7, "w": "7" }, { "u": 46, "v": 10, "w": "1" }, { "u": 46, "v": 11, "w": "4" }, { "u": 46, "v": 12, "w": "1" }, { "u": 46, "v": 13, "w": "1" }, { "u": 46, "v": 14, "w": "1" }, { "u": 46, "v": 15, "w": "1" }, { "u": 46, "v": 16, "w": "1" }, { "u": 46, "v": 18, "w": "2" }, { "u": 46, "v": 20, "w": "1" }, { "u": 46, "v": 21, "w": "5" }, { "u": 46, "v": 22, "w": "3" }, { "u": 46, "v": 24, "w": "1" }, { "u": 46, "v": 27, "w": "1" }, { "u": 46, "v": 31, "w": "3" }, { "u": 46, "v": 32, "w": "3" }, { "u": 46, "v": 34, "w": "3" }, { "u": 46, "v": 36, "w": "3" }, { "u": 46, "v": 39, "w": "2" }, { "u": 46, "v": 42, "w": "1" }, { "u": 46, "v": 48, "w": "5" }, { "u": 46, "v": 49, "w": "1" }, { "u": 46, "v": 55, "w": "1" }, { "u": 46, "v": 56, "w": "2" }, { "u": 46, "v": 57, "w": "4" }, { "u": 46, "v": 58, "w": "1" }, { "u": 47, "v": 3, "w": "1" }, { "u": 47, "v": 4, "w": "1" }, { "u": 47, "v": 6, "w": "6" }, { "u": 47, "v": 7, "w": "3" }, { "u": 47, "v": 8, "w": "1" }, { "u": 47, "v": 9, "w": "4" }, { "u": 47, "v": 11, "w": "5" }, { "u": 47, "v": 14, "w": "1" }, { "u": 47, "v": 16, "w": "1" }, { "u": 47, "v": 19, "w": "1" }, { "u": 47, "v": 20, "w": "2" }, { "u": 47, "v": 23, "w": "2" }, { "u": 47, "v": 29, "w": "1" }, { "u": 47, "v": 30, "w": "5" }, { "u": 47, "v": 34, "w": "2" }, { "u": 47, "v": 35, "w": "1" }, { "u": 47, "v": 37, "w": "4" }, { "u": 47, "v": 38, "w": "2" }, { "u": 47, "v": 39, "w": "1" }, { "u": 47, "v": 40, "w": "1" }, { "u": 47, "v": 42, "w": "1" }, { "u": 47, "v": 44, "w": "1" }, { "u": 47, "v": 45, "w": "1" }, { "u": 47, "v": 48, "w": "1" }, { "u": 47, "v": 49, "w": "2" }, { "u": 47, "v": 51, "w": "2" }, { "u": 47, "v": 53, "w": "3" }, { "u": 47, "v": 56, "w": "2" }, { "u": 47, "v": 57, "w": "6" }, { "u": 47, "v": 58, "w": "1" }, { "u": 48, "v": 2, "w": "5" }, { "u": 48, "v": 3, "w": "3" }, { "u": 48, "v": 4, "w": "2" }, { "u": 48, "v": 6, "w": "1" }, { "u": 48, "v": 7, "w": "7" }, { "u": 48, "v": 8, "w": "3" }, { "u": 48, "v": 11, "w": "2" }, { "u": 48, "v": 14, "w": "1" }, { "u": 48, "v": 16, "w": "4" }, { "u": 48, "v": 17, "w": "3" }, { "u": 48, "v": 18, "w": "2" }, { "u": 48, "v": 20, "w": "1" }, { "u": 48, "v": 21, "w": "1" }, { "u": 48, "v": 24, "w": "1" }, { "u": 48, "v": 26, "w": "1" }, { "u": 48, "v": 27, "w": "5" }, { "u": 48, "v": 29, "w": "1" }, { "u": 48, "v": 30, "w": "1" }, { "u": 48, "v": 31, "w": "2" }, { "u": 48, "v": 32, "w": "6" }, { "u": 48, "v": 34, "w": "2" }, { "u": 48, "v": 39, "w": "2" }, { "u": 48, "v": 42, "w": "1" }, { "u": 48, "v": 45, "w": "2" }, { "u": 48, "v": 46, "w": "5" }, { "u": 48, "v": 47, "w": "1" }, { "u": 48, "v": 49, "w": "3" }, { "u": 48, "v": 50, "w": "2" }, { "u": 48, "v": 54, "w": "2" }, { "u": 48, "v": 55, "w": "1" }, { "u": 48, "v": 57, "w": "2" }, { "u": 49, "v": 1, "w": "1" }, { "u": 49, "v": 2, "w": "3" }, { "u": 49, "v": 3, "w": "6" }, { "u": 49, "v": 4, "w": "1" }, { "u": 49, "v": 6, "w": "3" }, { "u": 49, "v": 7, "w": "7" }, { "u": 49, "v": 8, "w": "1" }, { "u": 49, "v": 9, "w": "1" }, { "u": 49, "v": 11, "w": "1" }, { "u": 49, "v": 12, "w": "1" }, { "u": 49, "v": 13, "w": "2" }, { "u": 49, "v": 15, "w": "1" }, { "u": 49, "v": 17, "w": "1" }, { "u": 49, "v": 19, "w": "3" }, { "u": 49, "v": 20, "w": "6" }, { "u": 49, "v": 21, "w": "1" }, { "u": 49, "v": 22, "w": "2" }, { "u": 49, "v": 23, "w": "11" }, { "u": 49, "v": 26, "w": "2" }, { "u": 49, "v": 27, "w": "2" }, { "u": 49, "v": 29, "w": "1" }, { "u": 49, "v": 30, "w": "3" }, { "u": 49, "v": 31, "w": "1" }, { "u": 49, "v": 33, "w": "1" }, { "u": 49, "v": 34, "w": "1" }, { "u": 49, "v": 35, "w": "6" }, { "u": 49, "v": 36, "w": "3" }, { "u": 49, "v": 37, "w": "2" }, { "u": 49, "v": 39, "w": "5" }, { "u": 49, "v": 41, "w": "9" }, { "u": 49, "v": 42, "w": "1" }, { "u": 49, "v": 44, "w": "2" }, { "u": 49, "v": 45, "w": "4" }, { "u": 49, "v": 46, "w": "1" }, { "u": 49, "v": 47, "w": "2" }, { "u": 49, "v": 48, "w": "3" }, { "u": 49, "v": 50, "w": "4" }, { "u": 49, "v": 52, "w": "1" }, { "u": 49, "v": 53, "w": "4" }, { "u": 49, "v": 54, "w": "4" }, { "u": 49, "v": 55, "w": "2" }, { "u": 49, "v": 56, "w": "2" }, { "u": 49, "v": 57, "w": "3" }, { "u": 49, "v": 58, "w": "1" }, { "u": 50, "v": 3, "w": "2" }, { "u": 50, "v": 4, "w": "3" }, { "u": 50, "v": 6, "w": "4" }, { "u": 50, "v": 7, "w": "2" }, { "u": 50, "v": 8, "w": "1" }, { "u": 50, "v": 9, "w": "4" }, { "u": 50, "v": 12, "w": "2" }, { "u": 50, "v": 15, "w": "1" }, { "u": 50, "v": 16, "w": "2" }, { "u": 50, "v": 17, "w": "2" }, { "u": 50, "v": 20, "w": "1" }, { "u": 50, "v": 22, "w": "1" }, { "u": 50, "v": 23, "w": "1" }, { "u": 50, "v": 29, "w": "1" }, { "u": 50, "v": 30, "w": "1" }, { "u": 50, "v": 34, "w": "3" }, { "u": 50, "v": 35, "w": "1" }, { "u": 50, "v": 38, "w": "1" }, { "u": 50, "v": 39, "w": "4" }, { "u": 50, "v": 44, "w": "1" }, { "u": 50, "v": 45, "w": "1" }, { "u": 50, "v": 48, "w": "2" }, { "u": 50, "v": 49, "w": "4" }, { "u": 50, "v": 51, "w": "1" }, { "u": 50, "v": 54, "w": "1" }, { "u": 50, "v": 55, "w": "1" }, { "u": 50, "v": 56, "w": "1" }, { "u": 50, "v": 58, "w": "3" }, { "u": 51, "v": 23, "w": "1" }, { "u": 51, "v": 37, "w": "1" }, { "u": 51, "v": 39, "w": "3" }, { "u": 51, "v": 45, "w": "1" }, { "u": 51, "v": 47, "w": "2" }, { "u": 51, "v": 50, "w": "1" }, { "u": 52, "v": 3, "w": "2" }, { "u": 52, "v": 6, "w": "2" }, { "u": 52, "v": 8, "w": "1" }, { "u": 52, "v": 9, "w": "6" }, { "u": 52, "v": 26, "w": "2" }, { "u": 52, "v": 33, "w": "2" }, { "u": 52, "v": 49, "w": "1" }, { "u": 53, "v": 3, "w": "2" }, { "u": 53, "v": 4, "w": "2" }, { "u": 53, "v": 5, "w": "1" }, { "u": 53, "v": 6, "w": "8" }, { "u": 53, "v": 7, "w": "6" }, { "u": 53, "v": 8, "w": "2" }, { "u": 53, "v": 9, "w": "1" }, { "u": 53, "v": 12, "w": "1" }, { "u": 53, "v": 13, "w": "2" }, { "u": 53, "v": 14, "w": "1" }, { "u": 53, "v": 15, "w": "7" }, { "u": 53, "v": 16, "w": "8" }, { "u": 53, "v": 17, "w": "7" }, { "u": 53, "v": 19, "w": "1" }, { "u": 53, "v": 20, "w": "12" }, { "u": 53, "v": 23, "w": "1" }, { "u": 53, "v": 25, "w": "2" }, { "u": 53, "v": 27, "w": "1" }, { "u": 53, "v": 28, "w": "1" }, { "u": 53, "v": 30, "w": "3" }, { "u": 53, "v": 31, "w": "1" }, { "u": 53, "v": 33, "w": "8" }, { "u": 53, "v": 34, "w": "2" }, { "u": 53, "v": 35, "w": "1" }, { "u": 53, "v": 36, "w": "1" }, { "u": 53, "v": 37, "w": "2" }, { "u": 53, "v": 38, "w": "1" }, { "u": 53, "v": 40, "w": "1" }, { "u": 53, "v": 41, "w": "4" }, { "u": 53, "v": 42, "w": "2" }, { "u": 53, "v": 47, "w": "3" }, { "u": 53, "v": 49, "w": "4" }, { "u": 53, "v": 54, "w": "5" }, { "u": 53, "v": 55, "w": "1" }, { "u": 53, "v": 56, "w": "2" }, { "u": 53, "v": 57, "w": "4" }, { "u": 53, "v": 58, "w": "3" }, { "u": 54, "v": 1, "w": "1" }, { "u": 54, "v": 2, "w": "1" }, { "u": 54, "v": 3, "w": "16" }, { "u": 54, "v": 4, "w": "1" }, { "u": 54, "v": 5, "w": "2" }, { "u": 54, "v": 6, "w": "9" }, { "u": 54, "v": 7, "w": "5" }, { "u": 54, "v": 8, "w": "5" }, { "u": 54, "v": 9, "w": "4" }, { "u": 54, "v": 10, "w": "1" }, { "u": 54, "v": 11, "w": "4" }, { "u": 54, "v": 12, "w": "2" }, { "u": 54, "v": 13, "w": "4" }, { "u": 54, "v": 15, "w": "6" }, { "u": 54, "v": 16, "w": "4" }, { "u": 54, "v": 17, "w": "7" }, { "u": 54, "v": 20, "w": "17" }, { "u": 54, "v": 21, "w": "1" }, { "u": 54, "v": 22, "w": "2" }, { "u": 54, "v": 23, "w": "4" }, { "u": 54, "v": 25, "w": "1" }, { "u": 54, "v": 26, "w": "1" }, { "u": 54, "v": 27, "w": "2" }, { "u": 54, "v": 29, "w": "7" }, { "u": 54, "v": 30, "w": "2" }, { "u": 54, "v": 31, "w": "4" }, { "u": 54, "v": 33, "w": "1" }, { "u": 54, "v": 34, "w": "4" }, { "u": 54, "v": 35, "w": "12" }, { "u": 54, "v": 37, "w": "1" }, { "u": 54, "v": 38, "w": "6" }, { "u": 54, "v": 39, "w": "2" }, { "u": 54, "v": 40, "w": "2" }, { "u": 54, "v": 41, "w": "1" }, { "u": 54, "v": 42, "w": "1" }, { "u": 54, "v": 44, "w": "2" }, { "u": 54, "v": 45, "w": "4" }, { "u": 54, "v": 48, "w": "2" }, { "u": 54, "v": 49, "w": "4" }, { "u": 54, "v": 50, "w": "1" }, { "u": 54, "v": 53, "w": "5" }, { "u": 54, "v": 55, "w": "5" }, { "u": 54, "v": 56, "w": "3" }, { "u": 54, "v": 57, "w": "10" }, { "u": 55, "v": 1, "w": "1" }, { "u": 55, "v": 3, "w": "4" }, { "u": 55, "v": 4, "w": "2" }, { "u": 55, "v": 6, "w": "3" }, { "u": 55, "v": 7, "w": "14" }, { "u": 55, "v": 9, "w": "3" }, { "u": 55, "v": 11, "w": "6" }, { "u": 55, "v": 13, "w": "1" }, { "u": 55, "v": 14, "w": "2" }, { "u": 55, "v": 15, "w": "4" }, { "u": 55, "v": 16, "w": "2" }, { "u": 55, "v": 17, "w": "4" }, { "u": 55, "v": 18, "w": "2" }, { "u": 55, "v": 20, "w": "11" }, { "u": 55, "v": 21, "w": "2" }, { "u": 55, "v": 22, "w": "1" }, { "u": 55, "v": 23, "w": "1" }, { "u": 55, "v": 25, "w": "1" }, { "u": 55, "v": 26, "w": "1" }, { "u": 55, "v": 27, "w": "2" }, { "u": 55, "v": 29, "w": "1" }, { "u": 55, "v": 30, "w": "1" }, { "u": 55, "v": 31, "w": "1" }, { "u": 55, "v": 34, "w": "3" }, { "u": 55, "v": 35, "w": "2" }, { "u": 55, "v": 36, "w": "2" }, { "u": 55, "v": 38, "w": "1" }, { "u": 55, "v": 39, "w": "2" }, { "u": 55, "v": 41, "w": "1" }, { "u": 55, "v": 42, "w": "1" }, { "u": 55, "v": 43, "w": "1" }, { "u": 55, "v": 44, "w": "1" }, { "u": 55, "v": 46, "w": "1" }, { "u": 55, "v": 48, "w": "1" }, { "u": 55, "v": 49, "w": "2" }, { "u": 55, "v": 50, "w": "1" }, { "u": 55, "v": 53, "w": "1" }, { "u": 55, "v": 54, "w": "5" }, { "u": 55, "v": 56, "w": "12" }, { "u": 55, "v": 57, "w": "7" }, { "u": 55, "v": 58, "w": "1" }, { "u": 56, "v": 1, "w": "4" }, { "u": 56, "v": 2, "w": "1" }, { "u": 56, "v": 3, "w": "5" }, { "u": 56, "v": 4, "w": "2" }, { "u": 56, "v": 6, "w": "2" }, { "u": 56, "v": 7, "w": "16" }, { "u": 56, "v": 8, "w": "2" }, { "u": 56, "v": 9, "w": "2" }, { "u": 56, "v": 11, "w": "6" }, { "u": 56, "v": 12, "w": "7" }, { "u": 56, "v": 14, "w": "1" }, { "u": 56, "v": 15, "w": "9" }, { "u": 56, "v": 18, "w": "1" }, { "u": 56, "v": 19, "w": "3" }, { "u": 56, "v": 20, "w": "9" }, { "u": 56, "v": 21, "w": "4" }, { "u": 56, "v": 22, "w": "1" }, { "u": 56, "v": 23, "w": "2" }, { "u": 56, "v": 24, "w": "2" }, { "u": 56, "v": 25, "w": "1" }, { "u": 56, "v": 26, "w": "1" }, { "u": 56, "v": 27, "w": "4" }, { "u": 56, "v": 29, "w": "2" }, { "u": 56, "v": 30, "w": "6" }, { "u": 56, "v": 31, "w": "1" }, { "u": 56, "v": 33, "w": "1" }, { "u": 56, "v": 34, "w": "4" }, { "u": 56, "v": 35, "w": "3" }, { "u": 56, "v": 36, "w": "10" }, { "u": 56, "v": 37, "w": "1" }, { "u": 56, "v": 38, "w": "1" }, { "u": 56, "v": 39, "w": "1" }, { "u": 56, "v": 41, "w": "5" }, { "u": 56, "v": 44, "w": "2" }, { "u": 56, "v": 46, "w": "2" }, { "u": 56, "v": 47, "w": "2" }, { "u": 56, "v": 49, "w": "2" }, { "u": 56, "v": 50, "w": "1" }, { "u": 56, "v": 53, "w": "2" }, { "u": 56, "v": 54, "w": "3" }, { "u": 56, "v": 55, "w": "12" }, { "u": 56, "v": 57, "w": "12" }, { "u": 57, "v": 1, "w": "1" }, { "u": 57, "v": 2, "w": "4" }, { "u": 57, "v": 3, "w": "19" }, { "u": 57, "v": 4, "w": "3" }, { "u": 57, "v": 5, "w": "4" }, { "u": 57, "v": 6, "w": "18" }, { "u": 57, "v": 7, "w": "20" }, { "u": 57, "v": 8, "w": "4" }, { "u": 57, "v": 9, "w": "7" }, { "u": 57, "v": 11, "w": "12" }, { "u": 57, "v": 12, "w": "3" }, { "u": 57, "v": 14, "w": "5" }, { "u": 57, "v": 15, "w": "4" }, { "u": 57, "v": 16, "w": "11" }, { "u": 57, "v": 17, "w": "11" }, { "u": 57, "v": 19, "w": "1" }, { "u": 57, "v": 20, "w": "23" }, { "u": 57, "v": 21, "w": "2" }, { "u": 57, "v": 22, "w": "3" }, { "u": 57, "v": 23, "w": "3" }, { "u": 57, "v": 24, "w": "1" }, { "u": 57, "v": 25, "w": "5" }, { "u": 57, "v": 26, "w": "2" }, { "u": 57, "v": 27, "w": "6" }, { "u": 57, "v": 29, "w": "10" }, { "u": 57, "v": 30, "w": "10" }, { "u": 57, "v": 31, "w": "3" }, { "u": 57, "v": 33, "w": "3" }, { "u": 57, "v": 34, "w": "3" }, { "u": 57, "v": 35, "w": "6" }, { "u": 57, "v": 36, "w": "1" }, { "u": 57, "v": 37, "w": "3" }, { "u": 57, "v": 38, "w": "4" }, { "u": 57, "v": 39, "w": "2" }, { "u": 57, "v": 40, "w": "2" }, { "u": 57, "v": 41, "w": "1" }, { "u": 57, "v": 42, "w": "3" }, { "u": 57, "v": 44, "w": "3" }, { "u": 57, "v": 45, "w": "1" }, { "u": 57, "v": 46, "w": "4" }, { "u": 57, "v": 47, "w": "6" }, { "u": 57, "v": 48, "w": "2" }, { "u": 57, "v": 49, "w": "3" }, { "u": 57, "v": 53, "w": "4" }, { "u": 57, "v": 54, "w": "10" }, { "u": 57, "v": 55, "w": "7" }, { "u": 57, "v": 56, "w": "12" }, { "u": 57, "v": 58, "w": "1" }, { "u": 58, "v": 1, "w": "1" }, { "u": 58, "v": 3, "w": "1" }, { "u": 58, "v": 4, "w": "5" }, { "u": 58, "v": 6, "w": "2" }, { "u": 58, "v": 7, "w": "4" }, { "u": 58, "v": 8, "w": "2" }, { "u": 58, "v": 9, "w": "1" }, { "u": 58, "v": 12, "w": "3" }, { "u": 58, "v": 14, "w": "1" }, { "u": 58, "v": 16, "w": "3" }, { "u": 58, "v": 18, "w": "1" }, { "u": 58, "v": 19, "w": "2" }, { "u": 58, "v": 20, "w": "5" }, { "u": 58, "v": 21, "w": "1" }, { "u": 58, "v": 23, "w": "1" }, { "u": 58, "v": 24, "w": "1" }, { "u": 58, "v": 27, "w": "2" }, { "u": 58, "v": 29, "w": "1" }, { "u": 58, "v": 30, "w": "2" }, { "u": 58, "v": 31, "w": "2" }, { "u": 58, "v": 32, "w": "1" }, { "u": 58, "v": 34, "w": "6" }, { "u": 58, "v": 35, "w": "2" }, { "u": 58, "v": 36, "w": "1" }, { "u": 58, "v": 38, "w": "2" }, { "u": 58, "v": 41, "w": "2" }, { "u": 58, "v": 42, "w": "1" }, { "u": 58, "v": 46, "w": "1" }, { "u": 58, "v": 47, "w": "1" }, { "u": 58, "v": 49, "w": "1" }, { "u": 58, "v": 50, "w": "3" }, { "u": 58, "v": 53, "w": "3" }, { "u": 58, "v": 55, "w": "1" }, { "u": 58, "v": 57, "w": "1" } ], "hash_sha256": "cda3fa08638d0d85a096125c88fc3a0bb100c436145f2d05c4e9cdd2ebe1e49c", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] }, "ties_graph": 1934 }, { "index": 1, "name": "BKFRAC", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "4" }, { "u": 1, "v": 3, "w": "4" }, { "u": 1, "v": 4, "w": "5" }, { "u": 1, "v": 5, "w": "4" }, { "u": 1, "v": 6, "w": "4" }, { "u": 1, "v": 7, "w": "5" }, { "u": 1, "v": 8, "w": "5" }, { "u": 1, "v": 9, "w": "4" }, { "u": 1, "v": 10, "w": "5" }, { "u": 1, "v": 11, "w": "5" }, { "u": 1, "v": 12, "w": "4" }, { "u": 1, "v": 13, "w": "4" }, { "u": 1, "v": 14, "w": "5" }, { "u": 1, "v": 15, "w": "5" }, { "u": 1, "v": 16, "w": "4" }, { "u": 1, "v": 17, "w": "4" }, { "u": 1, "v": 18, "w": "5" }, { "u": 1, "v": 19, "w": "5" }, { "u": 1, "v": 20, "w": "5" }, { "u": 1, "v": 21, "w": "5" }, { "u": 1, "v": 22, "w": "4" }, { "u": 1, "v": 23, "w": "5" }, { "u": 1, "v": 24, "w": "4" }, { "u": 1, "v": 25, "w": "4" }, { "u": 1, "v": 26, "w": "5" }, { "u": 1, "v": 27, "w": "5" }, { "u": 1, "v": 28, "w": "4" }, { "u": 1, "v": 29, "w": "4" }, { "u": 1, "v": 30, "w": "5" }, { "u": 1, "v": 31, "w": "5" }, { "u": 1, "v": 32, "w": "5" }, { "u": 1, "v": 33, "w": "4" }, { "u": 1, "v": 34, "w": "4" }, { "u": 1, "v": 35, "w": "4" }, { "u": 1, "v": 36, "w": "5" }, { "u": 1, "v": 37, "w": "4" }, { "u": 1, "v": 38, "w": "5" }, { "u": 1, "v": 39, "w": "4" }, { "u": 1, "v": 40, "w": "4" }, { "u": 1, "v": 41, "w": "5" }, { "u": 1, "v": 42, "w": "4" }, { "u": 1, "v": 43, "w": "5" }, { "u": 1, "v": 44, "w": "5" }, { "u": 1, "v": 45, "w": "4" }, { "u": 1, "v": 46, "w": "5" }, { "u": 1, "v": 47, "w": "5" }, { "u": 1, "v": 48, "w": "5" }, { "u": 1, "v": 49, "w": "4" }, { "u": 1, "v": 50, "w": "5" }, { "u": 1, "v": 51, "w": "4" }, { "u": 1, "v": 52, "w": "5" }, { "u": 1, "v": 53, "w": "4" }, { "u": 1, "v": 54, "w": "4" }, { "u": 1, "v": 55, "w": "5" }, { "u": 1, "v": 56, "w": "5" }, { "u": 1, "v": 57, "w": "5" }, { "u": 1, "v": 58, "w": "5" }, { "u": 2, "v": 1, "w": "3" }, { "u": 2, "v": 3, "w": "2" }, { "u": 2, "v": 4, "w": "3" }, { "u": 2, "v": 5, "w": "4" }, { "u": 2, "v": 6, "w": "2" }, { "u": 2, "v": 7, "w": "2" }, { "u": 2, "v": 8, "w": "2" }, { "u": 2, "v": 9, "w": "3" }, { "u": 2, "v": 10, "w": "2" }, { "u": 2, "v": 11, "w": "3" }, { "u": 2, "v": 12, "w": "3" }, { "u": 2, "v": 13, "w": "5" }, { "u": 2, "v": 14, "w": "4" }, { "u": 2, "v": 15, "w": "3" }, { "u": 2, "v": 16, "w": "3" }, { "u": 2, "v": 17, "w": "3" }, { "u": 2, "v": 18, "w": "2" }, { "u": 2, "v": 19, "w": "3" }, { "u": 2, "v": 20, "w": "3" }, { "u": 2, "v": 21, "w": "2" }, { "u": 2, "v": 22, "w": "5" }, { "u": 2, "v": 23, "w": "4" }, { "u": 2, "v": 24, "w": "2" }, { "u": 2, "v": 25, "w": "5" }, { "u": 2, "v": 26, "w": "3" }, { "u": 2, "v": 27, "w": "2" }, { "u": 2, "v": 28, "w": "2" }, { "u": 2, "v": 29, "w": "3" }, { "u": 2, "v": 30, "w": "4" }, { "u": 2, "v": 31, "w": "4" }, { "u": 2, "v": 32, "w": "2" }, { "u": 2, "v": 33, "w": "4" }, { "u": 2, "v": 34, "w": "2" }, { "u": 2, "v": 35, "w": "5" }, { "u": 2, "v": 36, "w": "3" }, { "u": 2, "v": 37, "w": "3" }, { "u": 2, "v": 38, "w": "5" }, { "u": 2, "v": 39, "w": "3" }, { "u": 2, "v": 40, "w": "2" }, { "u": 2, "v": 41, "w": "3" }, { "u": 2, "v": 42, "w": "3" }, { "u": 2, "v": 43, "w": "3" }, { "u": 2, "v": 44, "w": "2" }, { "u": 2, "v": 45, "w": "4" }, { "u": 2, "v": 46, "w": "2" }, { "u": 2, "v": 47, "w": "4" }, { "u": 2, "v": 48, "w": "3" }, { "u": 2, "v": 49, "w": "3" }, { "u": 2, "v": 50, "w": "3" }, { "u": 2, "v": 51, "w": "2" }, { "u": 2, "v": 52, "w": "3" }, { "u": 2, "v": 53, "w": "4" }, { "u": 2, "v": 54, "w": "4" }, { "u": 2, "v": 55, "w": "2" }, { "u": 2, "v": 56, "w": "3" }, { "u": 2, "v": 57, "w": "2" }, { "u": 2, "v": 58, "w": "2" }, { "u": 3, "v": 1, "w": "2" }, { "u": 3, "v": 2, "w": "2" }, { "u": 3, "v": 4, "w": "4" }, { "u": 3, "v": 5, "w": "5" }, { "u": 3, "v": 6, "w": "4" }, { "u": 3, "v": 7, "w": "4" }, { "u": 3, "v": 8, "w": "1" }, { "u": 3, "v": 9, "w": "4" }, { "u": 3, "v": 10, "w": "3" }, { "u": 3, "v": 11, "w": "3" }, { "u": 3, "v": 12, "w": "3" }, { "u": 3, "v": 13, "w": "3" }, { "u": 3, "v": 14, "w": "3" }, { "u": 3, "v": 15, "w": "4" }, { "u": 3, "v": 16, "w": "4" }, { "u": 3, "v": 17, "w": "4" }, { "u": 3, "v": 18, "w": "2" }, { "u": 3, "v": 19, "w": "2" }, { "u": 3, "v": 20, "w": "5" }, { "u": 3, "v": 21, "w": "2" }, { "u": 3, "v": 22, "w": "5" }, { "u": 3, "v": 23, "w": "3" }, { "u": 3, "v": 24, "w": "1" }, { "u": 3, "v": 25, "w": "4" }, { "u": 3, "v": 26, "w": "3" }, { "u": 3, "v": 27, "w": "3" }, { "u": 3, "v": 28, "w": "2" }, { "u": 3, "v": 29, "w": "4" }, { "u": 3, "v": 30, "w": "5" }, { "u": 3, "v": 31, "w": "4" }, { "u": 3, "v": 32, "w": "2" }, { "u": 3, "v": 33, "w": "3" }, { "u": 3, "v": 34, "w": "3" }, { "u": 3, "v": 35, "w": "5" }, { "u": 3, "v": 36, "w": "1" }, { "u": 3, "v": 37, "w": "2" }, { "u": 3, "v": 38, "w": "3" }, { "u": 3, "v": 39, "w": "3" }, { "u": 3, "v": 40, "w": "1" }, { "u": 3, "v": 41, "w": "2" }, { "u": 3, "v": 42, "w": "4" }, { "u": 3, "v": 43, "w": "4" }, { "u": 3, "v": 44, "w": "3" }, { "u": 3, "v": 45, "w": "4" }, { "u": 3, "v": 46, "w": "2" }, { "u": 3, "v": 47, "w": "2" }, { "u": 3, "v": 48, "w": "2" }, { "u": 3, "v": 49, "w": "2" }, { "u": 3, "v": 50, "w": "2" }, { "u": 3, "v": 51, "w": "1" }, { "u": 3, "v": 52, "w": "3" }, { "u": 3, "v": 53, "w": "2" }, { "u": 3, "v": 54, "w": "5" }, { "u": 3, "v": 55, "w": "3" }, { "u": 3, "v": 56, "w": "2" }, { "u": 3, "v": 57, "w": "5" }, { "u": 3, "v": 58, "w": "2" }, { "u": 4, "v": 1, "w": "4" }, { "u": 4, "v": 2, "w": "4" }, { "u": 4, "v": 3, "w": "5" }, { "u": 4, "v": 5, "w": "5" }, { "u": 4, "v": 6, "w": "5" }, { "u": 4, "v": 7, "w": "4" }, { "u": 4, "v": 8, "w": "3" }, { "u": 4, "v": 9, "w": "5" }, { "u": 4, "v": 10, "w": "4" }, { "u": 4, "v": 11, "w": "4" }, { "u": 4, "v": 12, "w": "5" }, { "u": 4, "v": 13, "w": "3" }, { "u": 4, "v": 14, "w": "4" }, { "u": 4, "v": 15, "w": "4" }, { "u": 4, "v": 16, "w": "4" }, { "u": 4, "v": 17, "w": "4" }, { "u": 4, "v": 18, "w": "3" }, { "u": 4, "v": 19, "w": "5" }, { "u": 4, "v": 20, "w": "5" }, { "u": 4, "v": 21, "w": "3" }, { "u": 4, "v": 22, "w": "4" }, { "u": 4, "v": 23, "w": "5" }, { "u": 4, "v": 24, "w": "5" }, { "u": 4, "v": 25, "w": "4" }, { "u": 4, "v": 26, "w": "4" }, { "u": 4, "v": 27, "w": "5" }, { "u": 4, "v": 28, "w": "4" }, { "u": 4, "v": 29, "w": "4" }, { "u": 4, "v": 30, "w": "4" }, { "u": 4, "v": 31, "w": "5" }, { "u": 4, "v": 32, "w": "4" }, { "u": 4, "v": 33, "w": "5" }, { "u": 4, "v": 34, "w": "5" }, { "u": 4, "v": 35, "w": "4" }, { "u": 4, "v": 36, "w": "3" }, { "u": 4, "v": 37, "w": "3" }, { "u": 4, "v": 38, "w": "4" }, { "u": 4, "v": 39, "w": "4" }, { "u": 4, "v": 40, "w": "3" }, { "u": 4, "v": 41, "w": "5" }, { "u": 4, "v": 42, "w": "4" }, { "u": 4, "v": 43, "w": "4" }, { "u": 4, "v": 44, "w": "4" }, { "u": 4, "v": 45, "w": "4" }, { "u": 4, "v": 46, "w": "3" }, { "u": 4, "v": 47, "w": "4" }, { "u": 4, "v": 48, "w": "4" }, { "u": 4, "v": 49, "w": "5" }, { "u": 4, "v": 50, "w": "4" }, { "u": 4, "v": 51, "w": "2" }, { "u": 4, "v": 52, "w": "4" }, { "u": 4, "v": 53, "w": "5" }, { "u": 4, "v": 54, "w": "5" }, { "u": 4, "v": 55, "w": "4" }, { "u": 4, "v": 56, "w": "4" }, { "u": 4, "v": 57, "w": "5" }, { "u": 4, "v": 58, "w": "5" }, { "u": 5, "v": 1, "w": "2" }, { "u": 5, "v": 2, "w": "3" }, { "u": 5, "v": 3, "w": "5" }, { "u": 5, "v": 4, "w": "5" }, { "u": 5, "v": 6, "w": "3" }, { "u": 5, "v": 7, "w": "2" }, { "u": 5, "v": 8, "w": "3" }, { "u": 5, "v": 9, "w": "3" }, { "u": 5, "v": 10, "w": "4" }, { "u": 5, "v": 11, "w": "4" }, { "u": 5, "v": 12, "w": "1" }, { "u": 5, "v": 13, "w": "4" }, { "u": 5, "v": 14, "w": "2" }, { "u": 5, "v": 15, "w": "4" }, { "u": 5, "v": 16, "w": "2" }, { "u": 5, "v": 17, "w": "2" }, { "u": 5, "v": 18, "w": "1" }, { "u": 5, "v": 19, "w": "2" }, { "u": 5, "v": 20, "w": "2" }, { "u": 5, "v": 21, "w": "1" }, { "u": 5, "v": 22, "w": "3" }, { "u": 5, "v": 23, "w": "4" }, { "u": 5, "v": 24, "w": "1" }, { "u": 5, "v": 25, "w": "2" }, { "u": 5, "v": 26, "w": "5" }, { "u": 5, "v": 27, "w": "4" }, { "u": 5, "v": 28, "w": "1" }, { "u": 5, "v": 29, "w": "3" }, { "u": 5, "v": 30, "w": "1" }, { "u": 5, "v": 31, "w": "3" }, { "u": 5, "v": 32, "w": "1" }, { "u": 5, "v": 33, "w": "1" }, { "u": 5, "v": 34, "w": "1" }, { "u": 5, "v": 35, "w": "1" }, { "u": 5, "v": 36, "w": "1" }, { "u": 5, "v": 37, "w": "2" }, { "u": 5, "v": 38, "w": "2" }, { "u": 5, "v": 39, "w": "5" }, { "u": 5, "v": 40, "w": "4" }, { "u": 5, "v": 41, "w": "2" }, { "u": 5, "v": 42, "w": "2" }, { "u": 5, "v": 43, "w": "2" }, { "u": 5, "v": 44, "w": "5" }, { "u": 5, "v": 45, "w": "5" }, { "u": 5, "v": 46, "w": "1" }, { "u": 5, "v": 47, "w": "3" }, { "u": 5, "v": 48, "w": "2" }, { "u": 5, "v": 49, "w": "5" }, { "u": 5, "v": 50, "w": "2" }, { "u": 5, "v": 51, "w": "3" }, { "u": 5, "v": 52, "w": "2" }, { "u": 5, "v": 53, "w": "1" }, { "u": 5, "v": 54, "w": "2" }, { "u": 5, "v": 55, "w": "2" }, { "u": 5, "v": 56, "w": "2" }, { "u": 5, "v": 57, "w": "4" }, { "u": 5, "v": 58, "w": "1" }, { "u": 6, "v": 1, "w": "3" }, { "u": 6, "v": 2, "w": "2" }, { "u": 6, "v": 3, "w": "5" }, { "u": 6, "v": 4, "w": "5" }, { "u": 6, "v": 5, "w": "2" }, { "u": 6, "v": 7, "w": "5" }, { "u": 6, "v": 8, "w": "3" }, { "u": 6, "v": 9, "w": "4" }, { "u": 6, "v": 10, "w": "3" }, { "u": 6, "v": 11, "w": "4" }, { "u": 6, "v": 12, "w": "3" }, { "u": 6, "v": 13, "w": "3" }, { "u": 6, "v": 14, "w": "5" }, { "u": 6, "v": 15, "w": "4" }, { "u": 6, "v": 16, "w": "5" }, { "u": 6, "v": 17, "w": "5" }, { "u": 6, "v": 18, "w": "2" }, { "u": 6, "v": 19, "w": "2" }, { "u": 6, "v": 20, "w": "5" }, { "u": 6, "v": 21, "w": "3" }, { "u": 6, "v": 22, "w": "2" }, { "u": 6, "v": 23, "w": "3" }, { "u": 6, "v": 24, "w": "1" }, { "u": 6, "v": 25, "w": "1" }, { "u": 6, "v": 26, "w": "1" }, { "u": 6, "v": 27, "w": "3" }, { "u": 6, "v": 28, "w": "1" }, { "u": 6, "v": 29, "w": "4" }, { "u": 6, "v": 30, "w": "4" }, { "u": 6, "v": 31, "w": "4" }, { "u": 6, "v": 32, "w": "1" }, { "u": 6, "v": 33, "w": "4" }, { "u": 6, "v": 34, "w": "3" }, { "u": 6, "v": 35, "w": "3" }, { "u": 6, "v": 36, "w": "2" }, { "u": 6, "v": 37, "w": "1" }, { "u": 6, "v": 38, "w": "2" }, { "u": 6, "v": 39, "w": "2" }, { "u": 6, "v": 40, "w": "2" }, { "u": 6, "v": 41, "w": "2" }, { "u": 6, "v": 42, "w": "3" }, { "u": 6, "v": 43, "w": "1" }, { "u": 6, "v": 44, "w": "2" }, { "u": 6, "v": 45, "w": "2" }, { "u": 6, "v": 46, "w": "3" }, { "u": 6, "v": 47, "w": "5" }, { "u": 6, "v": 48, "w": "3" }, { "u": 6, "v": 49, "w": "3" }, { "u": 6, "v": 50, "w": "3" }, { "u": 6, "v": 51, "w": "1" }, { "u": 6, "v": 52, "w": "2" }, { "u": 6, "v": 53, "w": "4" }, { "u": 6, "v": 54, "w": "4" }, { "u": 6, "v": 55, "w": "3" }, { "u": 6, "v": 56, "w": "3" }, { "u": 6, "v": 57, "w": "5" }, { "u": 6, "v": 58, "w": "4" }, { "u": 7, "v": 1, "w": "2" }, { "u": 7, "v": 2, "w": "1" }, { "u": 7, "v": 3, "w": "3" }, { "u": 7, "v": 4, "w": "4" }, { "u": 7, "v": 5, "w": "2" }, { "u": 7, "v": 6, "w": "5" }, { "u": 7, "v": 8, "w": "2" }, { "u": 7, "v": 9, "w": "2" }, { "u": 7, "v": 10, "w": "2" }, { "u": 7, "v": 11, "w": "2" }, { "u": 7, "v": 12, "w": "2" }, { "u": 7, "v": 13, "w": "1" }, { "u": 7, "v": 14, "w": "5" }, { "u": 7, "v": 15, "w": "5" }, { "u": 7, "v": 16, "w": "5" }, { "u": 7, "v": 17, "w": "4" }, { "u": 7, "v": 18, "w": "3" }, { "u": 7, "v": 19, "w": "3" }, { "u": 7, "v": 20, "w": "5" }, { "u": 7, "v": 21, "w": "3" }, { "u": 7, "v": 22, "w": "2" }, { "u": 7, "v": 23, "w": "2" }, { "u": 7, "v": 24, "w": "2" }, { "u": 7, "v": 25, "w": "2" }, { "u": 7, "v": 26, "w": "2" }, { "u": 7, "v": 27, "w": "3" }, { "u": 7, "v": 28, "w": "1" }, { "u": 7, "v": 29, "w": "4" }, { "u": 7, "v": 30, "w": "3" }, { "u": 7, "v": 31, "w": "3" }, { "u": 7, "v": 32, "w": "2" }, { "u": 7, "v": 33, "w": "5" }, { "u": 7, "v": 34, "w": "4" }, { "u": 7, "v": 35, "w": "3" }, { "u": 7, "v": 36, "w": "4" }, { "u": 7, "v": 37, "w": "2" }, { "u": 7, "v": 38, "w": "2" }, { "u": 7, "v": 39, "w": "2" }, { "u": 7, "v": 40, "w": "2" }, { "u": 7, "v": 41, "w": "3" }, { "u": 7, "v": 42, "w": "2" }, { "u": 7, "v": 43, "w": "2" }, { "u": 7, "v": 44, "w": "3" }, { "u": 7, "v": 45, "w": "2" }, { "u": 7, "v": 46, "w": "2" }, { "u": 7, "v": 47, "w": "2" }, { "u": 7, "v": 48, "w": "2" }, { "u": 7, "v": 49, "w": "3" }, { "u": 7, "v": 50, "w": "3" }, { "u": 7, "v": 51, "w": "1" }, { "u": 7, "v": 52, "w": "2" }, { "u": 7, "v": 53, "w": "3" }, { "u": 7, "v": 54, "w": "3" }, { "u": 7, "v": 55, "w": "3" }, { "u": 7, "v": 56, "w": "4" }, { "u": 7, "v": 57, "w": "5" }, { "u": 7, "v": 58, "w": "3" }, { "u": 8, "v": 1, "w": "5" }, { "u": 8, "v": 2, "w": "3" }, { "u": 8, "v": 3, "w": "3" }, { "u": 8, "v": 4, "w": "3" }, { "u": 8, "v": 5, "w": "2" }, { "u": 8, "v": 6, "w": "2" }, { "u": 8, "v": 7, "w": "3" }, { "u": 8, "v": 9, "w": "3" }, { "u": 8, "v": 10, "w": "3" }, { "u": 8, "v": 11, "w": "4" }, { "u": 8, "v": 12, "w": "3" }, { "u": 8, "v": 13, "w": "3" }, { "u": 8, "v": 14, "w": "3" }, { "u": 8, "v": 15, "w": "3" }, { "u": 8, "v": 16, "w": "3" }, { "u": 8, "v": 17, "w": "2" }, { "u": 8, "v": 18, "w": "3" }, { "u": 8, "v": 19, "w": "3" }, { "u": 8, "v": 20, "w": "3" }, { "u": 8, "v": 21, "w": "2" }, { "u": 8, "v": 22, "w": "2" }, { "u": 8, "v": 23, "w": "3" }, { "u": 8, "v": 24, "w": "5" }, { "u": 8, "v": 25, "w": "2" }, { "u": 8, "v": 26, "w": "2" }, { "u": 8, "v": 27, "w": "3" }, { "u": 8, "v": 28, "w": "2" }, { "u": 8, "v": 29, "w": "2" }, { "u": 8, "v": 30, "w": "3" }, { "u": 8, "v": 31, "w": "5" }, { "u": 8, "v": 32, "w": "4" }, { "u": 8, "v": 33, "w": "3" }, { "u": 8, "v": 34, "w": "5" }, { "u": 8, "v": 35, "w": "3" }, { "u": 8, "v": 36, "w": "4" }, { "u": 8, "v": 37, "w": "3" }, { "u": 8, "v": 38, "w": "3" }, { "u": 8, "v": 39, "w": "3" }, { "u": 8, "v": 40, "w": "2" }, { "u": 8, "v": 41, "w": "3" }, { "u": 8, "v": 42, "w": "2" }, { "u": 8, "v": 43, "w": "2" }, { "u": 8, "v": 44, "w": "3" }, { "u": 8, "v": 45, "w": "2" }, { "u": 8, "v": 46, "w": "3" }, { "u": 8, "v": 47, "w": "2" }, { "u": 8, "v": 48, "w": "3" }, { "u": 8, "v": 49, "w": "4" }, { "u": 8, "v": 50, "w": "3" }, { "u": 8, "v": 51, "w": "2" }, { "u": 8, "v": 52, "w": "2" }, { "u": 8, "v": 53, "w": "3" }, { "u": 8, "v": 54, "w": "2" }, { "u": 8, "v": 55, "w": "2" }, { "u": 8, "v": 56, "w": "3" }, { "u": 8, "v": 57, "w": "3" }, { "u": 8, "v": 58, "w": "4" }, { "u": 9, "v": 1, "w": "2" }, { "u": 9, "v": 2, "w": "2" }, { "u": 9, "v": 3, "w": "5" }, { "u": 9, "v": 4, "w": "4" }, { "u": 9, "v": 5, "w": "2" }, { "u": 9, "v": 6, "w": "3" }, { "u": 9, "v": 7, "w": "3" }, { "u": 9, "v": 8, "w": "2" }, { "u": 9, "v": 10, "w": "1" }, { "u": 9, "v": 11, "w": "3" }, { "u": 9, "v": 12, "w": "5" }, { "u": 9, "v": 13, "w": "2" }, { "u": 9, "v": 14, "w": "3" }, { "u": 9, "v": 15, "w": "3" }, { "u": 9, "v": 16, "w": "3" }, { "u": 9, "v": 17, "w": "3" }, { "u": 9, "v": 18, "w": "1" }, { "u": 9, "v": 19, "w": "3" }, { "u": 9, "v": 20, "w": "4" }, { "u": 9, "v": 21, "w": "2" }, { "u": 9, "v": 22, "w": "4" }, { "u": 9, "v": 23, "w": "3" }, { "u": 9, "v": 24, "w": "3" }, { "u": 9, "v": 25, "w": "4" }, { "u": 9, "v": 26, "w": "5" }, { "u": 9, "v": 27, "w": "2" }, { "u": 9, "v": 28, "w": "1" }, { "u": 9, "v": 29, "w": "3" }, { "u": 9, "v": 30, "w": "3" }, { "u": 9, "v": 31, "w": "2" }, { "u": 9, "v": 32, "w": "2" }, { "u": 9, "v": 33, "w": "3" }, { "u": 9, "v": 34, "w": "3" }, { "u": 9, "v": 35, "w": "4" }, { "u": 9, "v": 36, "w": "3" }, { "u": 9, "v": 37, "w": "3" }, { "u": 9, "v": 38, "w": "2" }, { "u": 9, "v": 39, "w": "2" }, { "u": 9, "v": 40, "w": "2" }, { "u": 9, "v": 41, "w": "2" }, { "u": 9, "v": 42, "w": "2" }, { "u": 9, "v": 43, "w": "2" }, { "u": 9, "v": 44, "w": "2" }, { "u": 9, "v": 45, "w": "2" }, { "u": 9, "v": 46, "w": "2" }, { "u": 9, "v": 47, "w": "4" }, { "u": 9, "v": 48, "w": "2" }, { "u": 9, "v": 49, "w": "3" }, { "u": 9, "v": 50, "w": "2" }, { "u": 9, "v": 51, "w": "1" }, { "u": 9, "v": 52, "w": "5" }, { "u": 9, "v": 53, "w": "2" }, { "u": 9, "v": 54, "w": "4" }, { "u": 9, "v": 55, "w": "2" }, { "u": 9, "v": 56, "w": "3" }, { "u": 9, "v": 57, "w": "4" }, { "u": 9, "v": 58, "w": "4" }, { "u": 10, "v": 1, "w": "3" }, { "u": 10, "v": 2, "w": "3" }, { "u": 10, "v": 3, "w": "5" }, { "u": 10, "v": 4, "w": "4" }, { "u": 10, "v": 5, "w": "4" }, { "u": 10, "v": 6, "w": "4" }, { "u": 10, "v": 7, "w": "3" }, { "u": 10, "v": 8, "w": "3" }, { "u": 10, "v": 9, "w": "2" }, { "u": 10, "v": 11, "w": "3" }, { "u": 10, "v": 12, "w": "4" }, { "u": 10, "v": 13, "w": "2" }, { "u": 10, "v": 14, "w": "4" }, { "u": 10, "v": 15, "w": "5" }, { "u": 10, "v": 16, "w": "4" }, { "u": 10, "v": 17, "w": "4" }, { "u": 10, "v": 18, "w": "1" }, { "u": 10, "v": 19, "w": "2" }, { "u": 10, "v": 20, "w": "4" }, { "u": 10, "v": 21, "w": "2" }, { "u": 10, "v": 22, "w": "4" }, { "u": 10, "v": 23, "w": "2" }, { "u": 10, "v": 24, "w": "2" }, { "u": 10, "v": 25, "w": "3" }, { "u": 10, "v": 26, "w": "2" }, { "u": 10, "v": 27, "w": "5" }, { "u": 10, "v": 28, "w": "1" }, { "u": 10, "v": 29, "w": "5" }, { "u": 10, "v": 30, "w": "2" }, { "u": 10, "v": 31, "w": "3" }, { "u": 10, "v": 32, "w": "1" }, { "u": 10, "v": 33, "w": "2" }, { "u": 10, "v": 34, "w": "3" }, { "u": 10, "v": 35, "w": "4" }, { "u": 10, "v": 36, "w": "1" }, { "u": 10, "v": 37, "w": "1" }, { "u": 10, "v": 38, "w": "2" }, { "u": 10, "v": 39, "w": "4" }, { "u": 10, "v": 40, "w": "5" }, { "u": 10, "v": 41, "w": "3" }, { "u": 10, "v": 42, "w": "3" }, { "u": 10, "v": 43, "w": "2" }, { "u": 10, "v": 44, "w": "1" }, { "u": 10, "v": 45, "w": "3" }, { "u": 10, "v": 46, "w": "4" }, { "u": 10, "v": 47, "w": "1" }, { "u": 10, "v": 48, "w": "3" }, { "u": 10, "v": 49, "w": "2" }, { "u": 10, "v": 50, "w": "4" }, { "u": 10, "v": 51, "w": "1" }, { "u": 10, "v": 52, "w": "2" }, { "u": 10, "v": 53, "w": "2" }, { "u": 10, "v": 54, "w": "5" }, { "u": 10, "v": 55, "w": "4" }, { "u": 10, "v": 56, "w": "1" }, { "u": 10, "v": 57, "w": "3" }, { "u": 10, "v": 58, "w": "3" }, { "u": 11, "v": 1, "w": "3" }, { "u": 11, "v": 2, "w": "3" }, { "u": 11, "v": 3, "w": "3" }, { "u": 11, "v": 4, "w": "3" }, { "u": 11, "v": 5, "w": "3" }, { "u": 11, "v": 6, "w": "3" }, { "u": 11, "v": 7, "w": "3" }, { "u": 11, "v": 8, "w": "3" }, { "u": 11, "v": 9, "w": "3" }, { "u": 11, "v": 10, "w": "3" }, { "u": 11, "v": 12, "w": "3" }, { "u": 11, "v": 13, "w": "3" }, { "u": 11, "v": 14, "w": "3" }, { "u": 11, "v": 15, "w": "3" }, { "u": 11, "v": 16, "w": "3" }, { "u": 11, "v": 17, "w": "3" }, { "u": 11, "v": 18, "w": "3" }, { "u": 11, "v": 19, "w": "3" }, { "u": 11, "v": 20, "w": "3" }, { "u": 11, "v": 21, "w": "5" }, { "u": 11, "v": 22, "w": "3" }, { "u": 11, "v": 23, "w": "3" }, { "u": 11, "v": 24, "w": "3" }, { "u": 11, "v": 25, "w": "3" }, { "u": 11, "v": 26, "w": "3" }, { "u": 11, "v": 27, "w": "3" }, { "u": 11, "v": 28, "w": "3" }, { "u": 11, "v": 29, "w": "3" }, { "u": 11, "v": 30, "w": "3" }, { "u": 11, "v": 31, "w": "5" }, { "u": 11, "v": 32, "w": "5" }, { "u": 11, "v": 33, "w": "3" }, { "u": 11, "v": 34, "w": "3" }, { "u": 11, "v": 35, "w": "3" }, { "u": 11, "v": 36, "w": "3" }, { "u": 11, "v": 37, "w": "3" }, { "u": 11, "v": 38, "w": "3" }, { "u": 11, "v": 39, "w": "3" }, { "u": 11, "v": 40, "w": "3" }, { "u": 11, "v": 41, "w": "3" }, { "u": 11, "v": 42, "w": "3" }, { "u": 11, "v": 43, "w": "3" }, { "u": 11, "v": 44, "w": "3" }, { "u": 11, "v": 45, "w": "3" }, { "u": 11, "v": 46, "w": "5" }, { "u": 11, "v": 47, "w": "3" }, { "u": 11, "v": 48, "w": "5" }, { "u": 11, "v": 49, "w": "3" }, { "u": 11, "v": 50, "w": "3" }, { "u": 11, "v": 51, "w": "2" }, { "u": 11, "v": 52, "w": "3" }, { "u": 11, "v": 53, "w": "3" }, { "u": 11, "v": 54, "w": "3" }, { "u": 11, "v": 55, "w": "3" }, { "u": 11, "v": 56, "w": "3" }, { "u": 11, "v": 57, "w": "3" }, { "u": 11, "v": 58, "w": "3" }, { "u": 12, "v": 1, "w": "2" }, { "u": 12, "v": 2, "w": "3" }, { "u": 12, "v": 3, "w": "4" }, { "u": 12, "v": 4, "w": "4" }, { "u": 12, "v": 5, "w": "2" }, { "u": 12, "v": 6, "w": "3" }, { "u": 12, "v": 7, "w": "2" }, { "u": 12, "v": 8, "w": "2" }, { "u": 12, "v": 9, "w": "5" }, { "u": 12, "v": 10, "w": "3" }, { "u": 12, "v": 11, "w": "3" }, { "u": 12, "v": 13, "w": "2" }, { "u": 12, "v": 14, "w": "3" }, { "u": 12, "v": 15, "w": "2" }, { "u": 12, "v": 16, "w": "3" }, { "u": 12, "v": 17, "w": "3" }, { "u": 12, "v": 18, "w": "2" }, { "u": 12, "v": 19, "w": "3" }, { "u": 12, "v": 20, "w": "4" }, { "u": 12, "v": 21, "w": "2" }, { "u": 12, "v": 22, "w": "4" }, { "u": 12, "v": 23, "w": "2" }, { "u": 12, "v": 24, "w": "2" }, { "u": 12, "v": 25, "w": "3" }, { "u": 12, "v": 26, "w": "3" }, { "u": 12, "v": 27, "w": "3" }, { "u": 12, "v": 28, "w": "1" }, { "u": 12, "v": 29, "w": "2" }, { "u": 12, "v": 30, "w": "2" }, { "u": 12, "v": 31, "w": "3" }, { "u": 12, "v": 32, "w": "1" }, { "u": 12, "v": 33, "w": "2" }, { "u": 12, "v": 34, "w": "4" }, { "u": 12, "v": 35, "w": "3" }, { "u": 12, "v": 36, "w": "4" }, { "u": 12, "v": 37, "w": "2" }, { "u": 12, "v": 38, "w": "2" }, { "u": 12, "v": 39, "w": "4" }, { "u": 12, "v": 40, "w": "2" }, { "u": 12, "v": 41, "w": "2" }, { "u": 12, "v": 42, "w": "2" }, { "u": 12, "v": 43, "w": "3" }, { "u": 12, "v": 44, "w": "2" }, { "u": 12, "v": 45, "w": "1" }, { "u": 12, "v": 46, "w": "3" }, { "u": 12, "v": 47, "w": "1" }, { "u": 12, "v": 48, "w": "2" }, { "u": 12, "v": 49, "w": "3" }, { "u": 12, "v": 50, "w": "2" }, { "u": 12, "v": 51, "w": "2" }, { "u": 12, "v": 52, "w": "2" }, { "u": 12, "v": 53, "w": "2" }, { "u": 12, "v": 54, "w": "4" }, { "u": 12, "v": 55, "w": "2" }, { "u": 12, "v": 56, "w": "4" }, { "u": 12, "v": 57, "w": "3" }, { "u": 12, "v": 58, "w": "4" }, { "u": 13, "v": 1, "w": "2" }, { "u": 13, "v": 2, "w": "5" }, { "u": 13, "v": 3, "w": "4" }, { "u": 13, "v": 4, "w": "2" }, { "u": 13, "v": 5, "w": "4" }, { "u": 13, "v": 6, "w": "3" }, { "u": 13, "v": 7, "w": "2" }, { "u": 13, "v": 8, "w": "2" }, { "u": 13, "v": 9, "w": "2" }, { "u": 13, "v": 10, "w": "2" }, { "u": 13, "v": 11, "w": "2" }, { "u": 13, "v": 12, "w": "2" }, { "u": 13, "v": 14, "w": "3" }, { "u": 13, "v": 15, "w": "2" }, { "u": 13, "v": 16, "w": "3" }, { "u": 13, "v": 17, "w": "2" }, { "u": 13, "v": 18, "w": "2" }, { "u": 13, "v": 19, "w": "2" }, { "u": 13, "v": 20, "w": "3" }, { "u": 13, "v": 21, "w": "1" }, { "u": 13, "v": 22, "w": "5" }, { "u": 13, "v": 23, "w": "3" }, { "u": 13, "v": 24, "w": "2" }, { "u": 13, "v": 25, "w": "5" }, { "u": 13, "v": 26, "w": "2" }, { "u": 13, "v": 27, "w": "3" }, { "u": 13, "v": 28, "w": "2" }, { "u": 13, "v": 29, "w": "4" }, { "u": 13, "v": 30, "w": "3" }, { "u": 13, "v": 31, "w": "3" }, { "u": 13, "v": 32, "w": "2" }, { "u": 13, "v": 33, "w": "3" }, { "u": 13, "v": 34, "w": "3" }, { "u": 13, "v": 35, "w": "3" }, { "u": 13, "v": 36, "w": "3" }, { "u": 13, "v": 37, "w": "2" }, { "u": 13, "v": 38, "w": "4" }, { "u": 13, "v": 39, "w": "4" }, { "u": 13, "v": 40, "w": "5" }, { "u": 13, "v": 41, "w": "2" }, { "u": 13, "v": 42, "w": "4" }, { "u": 13, "v": 43, "w": "3" }, { "u": 13, "v": 44, "w": "4" }, { "u": 13, "v": 45, "w": "4" }, { "u": 13, "v": 46, "w": "2" }, { "u": 13, "v": 47, "w": "3" }, { "u": 13, "v": 48, "w": "2" }, { "u": 13, "v": 49, "w": "3" }, { "u": 13, "v": 50, "w": "3" }, { "u": 13, "v": 51, "w": "3" }, { "u": 13, "v": 52, "w": "2" }, { "u": 13, "v": 53, "w": "3" }, { "u": 13, "v": 54, "w": "4" }, { "u": 13, "v": 55, "w": "3" }, { "u": 13, "v": 56, "w": "3" }, { "u": 13, "v": 57, "w": "3" }, { "u": 13, "v": 58, "w": "2" }, { "u": 14, "v": 1, "w": "3" }, { "u": 14, "v": 2, "w": "2" }, { "u": 14, "v": 3, "w": "5" }, { "u": 14, "v": 4, "w": "4" }, { "u": 14, "v": 5, "w": "3" }, { "u": 14, "v": 6, "w": "5" }, { "u": 14, "v": 7, "w": "5" }, { "u": 14, "v": 8, "w": "2" }, { "u": 14, "v": 9, "w": "4" }, { "u": 14, "v": 10, "w": "3" }, { "u": 14, "v": 11, "w": "3" }, { "u": 14, "v": 12, "w": "3" }, { "u": 14, "v": 13, "w": "3" }, { "u": 14, "v": 15, "w": "4" }, { "u": 14, "v": 16, "w": "5" }, { "u": 14, "v": 17, "w": "4" }, { "u": 14, "v": 18, "w": "3" }, { "u": 14, "v": 19, "w": "4" }, { "u": 14, "v": 20, "w": "4" }, { "u": 14, "v": 21, "w": "3" }, { "u": 14, "v": 22, "w": "3" }, { "u": 14, "v": 23, "w": "2" }, { "u": 14, "v": 24, "w": "2" }, { "u": 14, "v": 25, "w": "3" }, { "u": 14, "v": 26, "w": "3" }, { "u": 14, "v": 27, "w": "4" }, { "u": 14, "v": 28, "w": "2" }, { "u": 14, "v": 29, "w": "5" }, { "u": 14, "v": 30, "w": "3" }, { "u": 14, "v": 31, "w": "4" }, { "u": 14, "v": 32, "w": "3" }, { "u": 14, "v": 33, "w": "4" }, { "u": 14, "v": 34, "w": "3" }, { "u": 14, "v": 35, "w": "4" }, { "u": 14, "v": 36, "w": "3" }, { "u": 14, "v": 37, "w": "2" }, { "u": 14, "v": 38, "w": "3" }, { "u": 14, "v": 39, "w": "4" }, { "u": 14, "v": 40, "w": "3" }, { "u": 14, "v": 41, "w": "4" }, { "u": 14, "v": 42, "w": "4" }, { "u": 14, "v": 43, "w": "3" }, { "u": 14, "v": 44, "w": "4" }, { "u": 14, "v": 45, "w": "4" }, { "u": 14, "v": 46, "w": "3" }, { "u": 14, "v": 47, "w": "3" }, { "u": 14, "v": 48, "w": "3" }, { "u": 14, "v": 49, "w": "3" }, { "u": 14, "v": 50, "w": "3" }, { "u": 14, "v": 51, "w": "2" }, { "u": 14, "v": 52, "w": "2" }, { "u": 14, "v": 53, "w": "4" }, { "u": 14, "v": 54, "w": "4" }, { "u": 14, "v": 55, "w": "4" }, { "u": 14, "v": 56, "w": "4" }, { "u": 14, "v": 57, "w": "5" }, { "u": 14, "v": 58, "w": "3" }, { "u": 15, "v": 1, "w": "2" }, { "u": 15, "v": 2, "w": "1" }, { "u": 15, "v": 3, "w": "3" }, { "u": 15, "v": 4, "w": "3" }, { "u": 15, "v": 5, "w": "4" }, { "u": 15, "v": 6, "w": "4" }, { "u": 15, "v": 7, "w": "5" }, { "u": 15, "v": 8, "w": "2" }, { "u": 15, "v": 9, "w": "3" }, { "u": 15, "v": 10, "w": "2" }, { "u": 15, "v": 11, "w": "2" }, { "u": 15, "v": 12, "w": "1" }, { "u": 15, "v": 13, "w": "1" }, { "u": 15, "v": 14, "w": "3" }, { "u": 15, "v": 16, "w": "4" }, { "u": 15, "v": 17, "w": "5" }, { "u": 15, "v": 18, "w": "1" }, { "u": 15, "v": 19, "w": "5" }, { "u": 15, "v": 20, "w": "2" }, { "u": 15, "v": 21, "w": "2" }, { "u": 15, "v": 22, "w": "2" }, { "u": 15, "v": 23, "w": "3" }, { "u": 15, "v": 24, "w": "2" }, { "u": 15, "v": 25, "w": "1" }, { "u": 15, "v": 26, "w": "1" }, { "u": 15, "v": 27, "w": "4" }, { "u": 15, "v": 28, "w": "1" }, { "u": 15, "v": 29, "w": "3" }, { "u": 15, "v": 30, "w": "2" }, { "u": 15, "v": 31, "w": "3" }, { "u": 15, "v": 32, "w": "3" }, { "u": 15, "v": 33, "w": "3" }, { "u": 15, "v": 34, "w": "4" }, { "u": 15, "v": 35, "w": "4" }, { "u": 15, "v": 36, "w": "3" }, { "u": 15, "v": 37, "w": "1" }, { "u": 15, "v": 38, "w": "4" }, { "u": 15, "v": 39, "w": "2" }, { "u": 15, "v": 40, "w": "1" }, { "u": 15, "v": 41, "w": "5" }, { "u": 15, "v": 42, "w": "3" }, { "u": 15, "v": 43, "w": "1" }, { "u": 15, "v": 44, "w": "5" }, { "u": 15, "v": 45, "w": "3" }, { "u": 15, "v": 46, "w": "2" }, { "u": 15, "v": 47, "w": "2" }, { "u": 15, "v": 48, "w": "1" }, { "u": 15, "v": 49, "w": "4" }, { "u": 15, "v": 50, "w": "1" }, { "u": 15, "v": 51, "w": "1" }, { "u": 15, "v": 52, "w": "1" }, { "u": 15, "v": 53, "w": "3" }, { "u": 15, "v": 54, "w": "3" }, { "u": 15, "v": 55, "w": "3" }, { "u": 15, "v": 56, "w": "5" }, { "u": 15, "v": 57, "w": "5" }, { "u": 15, "v": 58, "w": "5" }, { "u": 16, "v": 1, "w": "3" }, { "u": 16, "v": 2, "w": "3" }, { "u": 16, "v": 3, "w": "4" }, { "u": 16, "v": 4, "w": "4" }, { "u": 16, "v": 5, "w": "3" }, { "u": 16, "v": 6, "w": "5" }, { "u": 16, "v": 7, "w": "5" }, { "u": 16, "v": 8, "w": "2" }, { "u": 16, "v": 9, "w": "3" }, { "u": 16, "v": 10, "w": "2" }, { "u": 16, "v": 11, "w": "3" }, { "u": 16, "v": 12, "w": "3" }, { "u": 16, "v": 13, "w": "4" }, { "u": 16, "v": 14, "w": "4" }, { "u": 16, "v": 15, "w": "5" }, { "u": 16, "v": 17, "w": "5" }, { "u": 16, "v": 18, "w": "3" }, { "u": 16, "v": 19, "w": "5" }, { "u": 16, "v": 20, "w": "5" }, { "u": 16, "v": 21, "w": "2" }, { "u": 16, "v": 22, "w": "3" }, { "u": 16, "v": 23, "w": "2" }, { "u": 16, "v": 24, "w": "2" }, { "u": 16, "v": 25, "w": "2" }, { "u": 16, "v": 26, "w": "1" }, { "u": 16, "v": 27, "w": "2" }, { "u": 16, "v": 28, "w": "2" }, { "u": 16, "v": 29, "w": "4" }, { "u": 16, "v": 30, "w": "4" }, { "u": 16, "v": 31, "w": "3" }, { "u": 16, "v": 32, "w": "2" }, { "u": 16, "v": 33, "w": "3" }, { "u": 16, "v": 34, "w": "3" }, { "u": 16, "v": 35, "w": "3" }, { "u": 16, "v": 36, "w": "3" }, { "u": 16, "v": 37, "w": "2" }, { "u": 16, "v": 38, "w": "3" }, { "u": 16, "v": 39, "w": "4" }, { "u": 16, "v": 40, "w": "3" }, { "u": 16, "v": 41, "w": "4" }, { "u": 16, "v": 42, "w": "5" }, { "u": 16, "v": 43, "w": "3" }, { "u": 16, "v": 44, "w": "3" }, { "u": 16, "v": 45, "w": "4" }, { "u": 16, "v": 46, "w": "4" }, { "u": 16, "v": 47, "w": "3" }, { "u": 16, "v": 48, "w": "4" }, { "u": 16, "v": 49, "w": "3" }, { "u": 16, "v": 50, "w": "3" }, { "u": 16, "v": 51, "w": "2" }, { "u": 16, "v": 52, "w": "1" }, { "u": 16, "v": 53, "w": "5" }, { "u": 16, "v": 54, "w": "4" }, { "u": 16, "v": 55, "w": "2" }, { "u": 16, "v": 56, "w": "4" }, { "u": 16, "v": 57, "w": "3" }, { "u": 16, "v": 58, "w": "3" }, { "u": 17, "v": 1, "w": "2" }, { "u": 17, "v": 2, "w": "2" }, { "u": 17, "v": 3, "w": "5" }, { "u": 17, "v": 4, "w": "3" }, { "u": 17, "v": 5, "w": "3" }, { "u": 17, "v": 6, "w": "4" }, { "u": 17, "v": 7, "w": "4" }, { "u": 17, "v": 8, "w": "2" }, { "u": 17, "v": 9, "w": "3" }, { "u": 17, "v": 10, "w": "3" }, { "u": 17, "v": 11, "w": "3" }, { "u": 17, "v": 12, "w": "3" }, { "u": 17, "v": 13, "w": "2" }, { "u": 17, "v": 14, "w": "4" }, { "u": 17, "v": 15, "w": "5" }, { "u": 17, "v": 16, "w": "5" }, { "u": 17, "v": 18, "w": "2" }, { "u": 17, "v": 19, "w": "4" }, { "u": 17, "v": 20, "w": "5" }, { "u": 17, "v": 21, "w": "2" }, { "u": 17, "v": 22, "w": "3" }, { "u": 17, "v": 23, "w": "2" }, { "u": 17, "v": 24, "w": "2" }, { "u": 17, "v": 25, "w": "3" }, { "u": 17, "v": 26, "w": "1" }, { "u": 17, "v": 27, "w": "2" }, { "u": 17, "v": 28, "w": "3" }, { "u": 17, "v": 29, "w": "4" }, { "u": 17, "v": 30, "w": "3" }, { "u": 17, "v": 31, "w": "2" }, { "u": 17, "v": 32, "w": "2" }, { "u": 17, "v": 33, "w": "4" }, { "u": 17, "v": 34, "w": "2" }, { "u": 17, "v": 35, "w": "5" }, { "u": 17, "v": 36, "w": "2" }, { "u": 17, "v": 37, "w": "1" }, { "u": 17, "v": 38, "w": "4" }, { "u": 17, "v": 39, "w": "3" }, { "u": 17, "v": 40, "w": "2" }, { "u": 17, "v": 41, "w": "3" }, { "u": 17, "v": 42, "w": "5" }, { "u": 17, "v": 43, "w": "3" }, { "u": 17, "v": 44, "w": "5" }, { "u": 17, "v": 45, "w": "3" }, { "u": 17, "v": 46, "w": "2" }, { "u": 17, "v": 47, "w": "2" }, { "u": 17, "v": 48, "w": "3" }, { "u": 17, "v": 49, "w": "3" }, { "u": 17, "v": 50, "w": "3" }, { "u": 17, "v": 51, "w": "1" }, { "u": 17, "v": 52, "w": "1" }, { "u": 17, "v": 53, "w": "4" }, { "u": 17, "v": 54, "w": "5" }, { "u": 17, "v": 55, "w": "3" }, { "u": 17, "v": 56, "w": "4" }, { "u": 17, "v": 57, "w": "5" }, { "u": 17, "v": 58, "w": "3" }, { "u": 18, "v": 1, "w": "5" }, { "u": 18, "v": 2, "w": "3" }, { "u": 18, "v": 3, "w": "2" }, { "u": 18, "v": 4, "w": "5" }, { "u": 18, "v": 5, "w": "3" }, { "u": 18, "v": 6, "w": "3" }, { "u": 18, "v": 7, "w": "5" }, { "u": 18, "v": 8, "w": "5" }, { "u": 18, "v": 9, "w": "3" }, { "u": 18, "v": 10, "w": "2" }, { "u": 18, "v": 11, "w": "5" }, { "u": 18, "v": 12, "w": "5" }, { "u": 18, "v": 13, "w": "4" }, { "u": 18, "v": 14, "w": "4" }, { "u": 18, "v": 15, "w": "3" }, { "u": 18, "v": 16, "w": "5" }, { "u": 18, "v": 17, "w": "4" }, { "u": 18, "v": 19, "w": "2" }, { "u": 18, "v": 20, "w": "1" }, { "u": 18, "v": 21, "w": "5" }, { "u": 18, "v": 22, "w": "3" }, { "u": 18, "v": 23, "w": "3" }, { "u": 18, "v": 24, "w": "3" }, { "u": 18, "v": 25, "w": "3" }, { "u": 18, "v": 26, "w": "2" }, { "u": 18, "v": 27, "w": "2" }, { "u": 18, "v": 28, "w": "1" }, { "u": 18, "v": 29, "w": "2" }, { "u": 18, "v": 30, "w": "5" }, { "u": 18, "v": 31, "w": "5" }, { "u": 18, "v": 32, "w": "5" }, { "u": 18, "v": 33, "w": "3" }, { "u": 18, "v": 34, "w": "5" }, { "u": 18, "v": 35, "w": "4" }, { "u": 18, "v": 36, "w": "4" }, { "u": 18, "v": 37, "w": "2" }, { "u": 18, "v": 38, "w": "2" }, { "u": 18, "v": 39, "w": "3" }, { "u": 18, "v": 40, "w": "3" }, { "u": 18, "v": 41, "w": "3" }, { "u": 18, "v": 42, "w": "3" }, { "u": 18, "v": 43, "w": "3" }, { "u": 18, "v": 44, "w": "5" }, { "u": 18, "v": 45, "w": "5" }, { "u": 18, "v": 46, "w": "5" }, { "u": 18, "v": 47, "w": "2" }, { "u": 18, "v": 48, "w": "5" }, { "u": 18, "v": 49, "w": "3" }, { "u": 18, "v": 50, "w": "5" }, { "u": 18, "v": 51, "w": "2" }, { "u": 18, "v": 52, "w": "1" }, { "u": 18, "v": 53, "w": "2" }, { "u": 18, "v": 54, "w": "3" }, { "u": 18, "v": 55, "w": "4" }, { "u": 18, "v": 56, "w": "5" }, { "u": 18, "v": 57, "w": "5" }, { "u": 18, "v": 58, "w": "5" }, { "u": 19, "v": 1, "w": "3" }, { "u": 19, "v": 2, "w": "3" }, { "u": 19, "v": 3, "w": "4" }, { "u": 19, "v": 4, "w": "4" }, { "u": 19, "v": 5, "w": "2" }, { "u": 19, "v": 6, "w": "2" }, { "u": 19, "v": 7, "w": "3" }, { "u": 19, "v": 8, "w": "2" }, { "u": 19, "v": 9, "w": "4" }, { "u": 19, "v": 10, "w": "3" }, { "u": 19, "v": 11, "w": "3" }, { "u": 19, "v": 12, "w": "3" }, { "u": 19, "v": 13, "w": "3" }, { "u": 19, "v": 14, "w": "4" }, { "u": 19, "v": 15, "w": "5" }, { "u": 19, "v": 16, "w": "5" }, { "u": 19, "v": 17, "w": "5" }, { "u": 19, "v": 18, "w": "2" }, { "u": 19, "v": 20, "w": "4" }, { "u": 19, "v": 21, "w": "2" }, { "u": 19, "v": 22, "w": "4" }, { "u": 19, "v": 23, "w": "5" }, { "u": 19, "v": 24, "w": "4" }, { "u": 19, "v": 25, "w": "5" }, { "u": 19, "v": 26, "w": "2" }, { "u": 19, "v": 27, "w": "3" }, { "u": 19, "v": 28, "w": "3" }, { "u": 19, "v": 29, "w": "4" }, { "u": 19, "v": 30, "w": "4" }, { "u": 19, "v": 31, "w": "3" }, { "u": 19, "v": 32, "w": "3" }, { "u": 19, "v": 33, "w": "4" }, { "u": 19, "v": 34, "w": "4" }, { "u": 19, "v": 35, "w": "2" }, { "u": 19, "v": 36, "w": "4" }, { "u": 19, "v": 37, "w": "4" }, { "u": 19, "v": 38, "w": "3" }, { "u": 19, "v": 39, "w": "1" }, { "u": 19, "v": 40, "w": "2" }, { "u": 19, "v": 41, "w": "5" }, { "u": 19, "v": 42, "w": "4" }, { "u": 19, "v": 43, "w": "3" }, { "u": 19, "v": 44, "w": "3" }, { "u": 19, "v": 45, "w": "3" }, { "u": 19, "v": 46, "w": "2" }, { "u": 19, "v": 47, "w": "4" }, { "u": 19, "v": 48, "w": "2" }, { "u": 19, "v": 49, "w": "5" }, { "u": 19, "v": 50, "w": "3" }, { "u": 19, "v": 51, "w": "3" }, { "u": 19, "v": 52, "w": "2" }, { "u": 19, "v": 53, "w": "3" }, { "u": 19, "v": 54, "w": "3" }, { "u": 19, "v": 55, "w": "3" }, { "u": 19, "v": 56, "w": "4" }, { "u": 19, "v": 57, "w": "4" }, { "u": 19, "v": 58, "w": "3" }, { "u": 20, "v": 1, "w": "3" }, { "u": 20, "v": 2, "w": "2" }, { "u": 20, "v": 3, "w": "4" }, { "u": 20, "v": 4, "w": "4" }, { "u": 20, "v": 5, "w": "2" }, { "u": 20, "v": 6, "w": "5" }, { "u": 20, "v": 7, "w": "5" }, { "u": 20, "v": 8, "w": "2" }, { "u": 20, "v": 9, "w": "3" }, { "u": 20, "v": 10, "w": "3" }, { "u": 20, "v": 11, "w": "3" }, { "u": 20, "v": 12, "w": "3" }, { "u": 20, "v": 13, "w": "2" }, { "u": 20, "v": 14, "w": "4" }, { "u": 20, "v": 15, "w": "3" }, { "u": 20, "v": 16, "w": "4" }, { "u": 20, "v": 17, "w": "3" }, { "u": 20, "v": 18, "w": "2" }, { "u": 20, "v": 19, "w": "3" }, { "u": 20, "v": 21, "w": "2" }, { "u": 20, "v": 22, "w": "3" }, { "u": 20, "v": 23, "w": "3" }, { "u": 20, "v": 24, "w": "1" }, { "u": 20, "v": 25, "w": "1" }, { "u": 20, "v": 26, "w": "1" }, { "u": 20, "v": 27, "w": "2" }, { "u": 20, "v": 28, "w": "2" }, { "u": 20, "v": 29, "w": "3" }, { "u": 20, "v": 30, "w": "3" }, { "u": 20, "v": 31, "w": "3" }, { "u": 20, "v": 32, "w": "2" }, { "u": 20, "v": 33, "w": "5" }, { "u": 20, "v": 34, "w": "3" }, { "u": 20, "v": 35, "w": "3" }, { "u": 20, "v": 36, "w": "3" }, { "u": 20, "v": 37, "w": "2" }, { "u": 20, "v": 38, "w": "2" }, { "u": 20, "v": 39, "w": "2" }, { "u": 20, "v": 40, "w": "2" }, { "u": 20, "v": 41, "w": "3" }, { "u": 20, "v": 42, "w": "2" }, { "u": 20, "v": 43, "w": "2" }, { "u": 20, "v": 44, "w": "3" }, { "u": 20, "v": 45, "w": "2" }, { "u": 20, "v": 46, "w": "2" }, { "u": 20, "v": 47, "w": "1" }, { "u": 20, "v": 48, "w": "2" }, { "u": 20, "v": 49, "w": "3" }, { "u": 20, "v": 50, "w": "3" }, { "u": 20, "v": 51, "w": "1" }, { "u": 20, "v": 52, "w": "1" }, { "u": 20, "v": 53, "w": "5" }, { "u": 20, "v": 54, "w": "5" }, { "u": 20, "v": 55, "w": "3" }, { "u": 20, "v": 56, "w": "2" }, { "u": 20, "v": 57, "w": "5" }, { "u": 20, "v": 58, "w": "3" }, { "u": 21, "v": 1, "w": "3" }, { "u": 21, "v": 2, "w": "1" }, { "u": 21, "v": 3, "w": "2" }, { "u": 21, "v": 4, "w": "2" }, { "u": 21, "v": 5, "w": "1" }, { "u": 21, "v": 6, "w": "2" }, { "u": 21, "v": 7, "w": "3" }, { "u": 21, "v": 8, "w": "1" }, { "u": 21, "v": 9, "w": "2" }, { "u": 21, "v": 10, "w": "2" }, { "u": 21, "v": 11, "w": "5" }, { "u": 21, "v": 12, "w": "2" }, { "u": 21, "v": 13, "w": "1" }, { "u": 21, "v": 14, "w": "2" }, { "u": 21, "v": 15, "w": "2" }, { "u": 21, "v": 16, "w": "1" }, { "u": 21, "v": 17, "w": "1" }, { "u": 21, "v": 18, "w": "4" }, { "u": 21, "v": 19, "w": "1" }, { "u": 21, "v": 20, "w": "3" }, { "u": 21, "v": 22, "w": "1" }, { "u": 21, "v": 23, "w": "2" }, { "u": 21, "v": 24, "w": "2" }, { "u": 21, "v": 25, "w": "2" }, { "u": 21, "v": 26, "w": "1" }, { "u": 21, "v": 27, "w": "1" }, { "u": 21, "v": 28, "w": "1" }, { "u": 21, "v": 29, "w": "1" }, { "u": 21, "v": 30, "w": "2" }, { "u": 21, "v": 31, "w": "5" }, { "u": 21, "v": 32, "w": "4" }, { "u": 21, "v": 33, "w": "1" }, { "u": 21, "v": 34, "w": "2" }, { "u": 21, "v": 35, "w": "2" }, { "u": 21, "v": 36, "w": "2" }, { "u": 21, "v": 37, "w": "1" }, { "u": 21, "v": 38, "w": "1" }, { "u": 21, "v": 39, "w": "1" }, { "u": 21, "v": 40, "w": "1" }, { "u": 21, "v": 41, "w": "2" }, { "u": 21, "v": 42, "w": "1" }, { "u": 21, "v": 43, "w": "1" }, { "u": 21, "v": 44, "w": "3" }, { "u": 21, "v": 45, "w": "1" }, { "u": 21, "v": 46, "w": "5" }, { "u": 21, "v": 47, "w": "2" }, { "u": 21, "v": 48, "w": "5" }, { "u": 21, "v": 49, "w": "3" }, { "u": 21, "v": 50, "w": "4" }, { "u": 21, "v": 51, "w": "1" }, { "u": 21, "v": 52, "w": "1" }, { "u": 21, "v": 53, "w": "1" }, { "u": 21, "v": 54, "w": "2" }, { "u": 21, "v": 55, "w": "2" }, { "u": 21, "v": 56, "w": "2" }, { "u": 21, "v": 57, "w": "3" }, { "u": 21, "v": 58, "w": "2" }, { "u": 22, "v": 1, "w": "2" }, { "u": 22, "v": 2, "w": "5" }, { "u": 22, "v": 3, "w": "5" }, { "u": 22, "v": 4, "w": "3" }, { "u": 22, "v": 5, "w": "3" }, { "u": 22, "v": 6, "w": "3" }, { "u": 22, "v": 7, "w": "2" }, { "u": 22, "v": 8, "w": "2" }, { "u": 22, "v": 9, "w": "4" }, { "u": 22, "v": 10, "w": "2" }, { "u": 22, "v": 11, "w": "2" }, { "u": 22, "v": 12, "w": "3" }, { "u": 22, "v": 13, "w": "4" }, { "u": 22, "v": 14, "w": "3" }, { "u": 22, "v": 15, "w": "3" }, { "u": 22, "v": 16, "w": "2" }, { "u": 22, "v": 17, "w": "3" }, { "u": 22, "v": 18, "w": "2" }, { "u": 22, "v": 19, "w": "3" }, { "u": 22, "v": 20, "w": "4" }, { "u": 22, "v": 21, "w": "3" }, { "u": 22, "v": 23, "w": "3" }, { "u": 22, "v": 24, "w": "3" }, { "u": 22, "v": 25, "w": "5" }, { "u": 22, "v": 26, "w": "3" }, { "u": 22, "v": 27, "w": "3" }, { "u": 22, "v": 28, "w": "1" }, { "u": 22, "v": 29, "w": "4" }, { "u": 22, "v": 30, "w": "3" }, { "u": 22, "v": 31, "w": "3" }, { "u": 22, "v": 32, "w": "3" }, { "u": 22, "v": 33, "w": "3" }, { "u": 22, "v": 34, "w": "3" }, { "u": 22, "v": 35, "w": "4" }, { "u": 22, "v": 36, "w": "3" }, { "u": 22, "v": 37, "w": "2" }, { "u": 22, "v": 38, "w": "3" }, { "u": 22, "v": 39, "w": "3" }, { "u": 22, "v": 40, "w": "3" }, { "u": 22, "v": 41, "w": "4" }, { "u": 22, "v": 42, "w": "3" }, { "u": 22, "v": 43, "w": "3" }, { "u": 22, "v": 44, "w": "3" }, { "u": 22, "v": 45, "w": "3" }, { "u": 22, "v": 46, "w": "4" }, { "u": 22, "v": 47, "w": "3" }, { "u": 22, "v": 48, "w": "3" }, { "u": 22, "v": 49, "w": "4" }, { "u": 22, "v": 50, "w": "3" }, { "u": 22, "v": 51, "w": "1" }, { "u": 22, "v": 52, "w": "3" }, { "u": 22, "v": 53, "w": "2" }, { "u": 22, "v": 54, "w": "4" }, { "u": 22, "v": 55, "w": "3" }, { "u": 22, "v": 56, "w": "3" }, { "u": 22, "v": 57, "w": "4" }, { "u": 22, "v": 58, "w": "3" }, { "u": 23, "v": 1, "w": "2" }, { "u": 23, "v": 2, "w": "3" }, { "u": 23, "v": 3, "w": "4" }, { "u": 23, "v": 4, "w": "4" }, { "u": 23, "v": 5, "w": "5" }, { "u": 23, "v": 6, "w": "3" }, { "u": 23, "v": 7, "w": "4" }, { "u": 23, "v": 8, "w": "3" }, { "u": 23, "v": 9, "w": "4" }, { "u": 23, "v": 10, "w": "2" }, { "u": 23, "v": 11, "w": "2" }, { "u": 23, "v": 12, "w": "3" }, { "u": 23, "v": 13, "w": "2" }, { "u": 23, "v": 14, "w": "4" }, { "u": 23, "v": 15, "w": "4" }, { "u": 23, "v": 16, "w": "2" }, { "u": 23, "v": 17, "w": "2" }, { "u": 23, "v": 18, "w": "2" }, { "u": 23, "v": 19, "w": "5" }, { "u": 23, "v": 20, "w": "3" }, { "u": 23, "v": 21, "w": "1" }, { "u": 23, "v": 22, "w": "2" }, { "u": 23, "v": 24, "w": "2" }, { "u": 23, "v": 25, "w": "4" }, { "u": 23, "v": 26, "w": "3" }, { "u": 23, "v": 27, "w": "2" }, { "u": 23, "v": 28, "w": "1" }, { "u": 23, "v": 29, "w": "3" }, { "u": 23, "v": 30, "w": "1" }, { "u": 23, "v": 31, "w": "2" }, { "u": 23, "v": 32, "w": "1" }, { "u": 23, "v": 33, "w": "4" }, { "u": 23, "v": 34, "w": "5" }, { "u": 23, "v": 35, "w": "2" }, { "u": 23, "v": 36, "w": "1" }, { "u": 23, "v": 37, "w": "4" }, { "u": 23, "v": 38, "w": "4" }, { "u": 23, "v": 39, "w": "5" }, { "u": 23, "v": 40, "w": "2" }, { "u": 23, "v": 41, "w": "5" }, { "u": 23, "v": 42, "w": "2" }, { "u": 23, "v": 43, "w": "1" }, { "u": 23, "v": 44, "w": "5" }, { "u": 23, "v": 45, "w": "5" }, { "u": 23, "v": 46, "w": "1" }, { "u": 23, "v": 47, "w": "5" }, { "u": 23, "v": 48, "w": "1" }, { "u": 23, "v": 49, "w": "5" }, { "u": 23, "v": 50, "w": "1" }, { "u": 23, "v": 51, "w": "4" }, { "u": 23, "v": 52, "w": "2" }, { "u": 23, "v": 53, "w": "5" }, { "u": 23, "v": 54, "w": "2" }, { "u": 23, "v": 55, "w": "3" }, { "u": 23, "v": 56, "w": "2" }, { "u": 23, "v": 57, "w": "4" }, { "u": 23, "v": 58, "w": "5" }, { "u": 24, "v": 1, "w": "4" }, { "u": 24, "v": 2, "w": "1" }, { "u": 24, "v": 3, "w": "2" }, { "u": 24, "v": 4, "w": "5" }, { "u": 24, "v": 5, "w": "2" }, { "u": 24, "v": 6, "w": "2" }, { "u": 24, "v": 7, "w": "3" }, { "u": 24, "v": 8, "w": "5" }, { "u": 24, "v": 9, "w": "4" }, { "u": 24, "v": 10, "w": "3" }, { "u": 24, "v": 11, "w": "4" }, { "u": 24, "v": 12, "w": "3" }, { "u": 24, "v": 13, "w": "2" }, { "u": 24, "v": 14, "w": "2" }, { "u": 24, "v": 15, "w": "3" }, { "u": 24, "v": 16, "w": "4" }, { "u": 24, "v": 17, "w": "2" }, { "u": 24, "v": 18, "w": "2" }, { "u": 24, "v": 19, "w": "4" }, { "u": 24, "v": 20, "w": "2" }, { "u": 24, "v": 21, "w": "1" }, { "u": 24, "v": 22, "w": "3" }, { "u": 24, "v": 23, "w": "2" }, { "u": 24, "v": 25, "w": "2" }, { "u": 24, "v": 26, "w": "4" }, { "u": 24, "v": 27, "w": "4" }, { "u": 24, "v": 28, "w": "1" }, { "u": 24, "v": 29, "w": "1" }, { "u": 24, "v": 30, "w": "2" }, { "u": 24, "v": 31, "w": "5" }, { "u": 24, "v": 32, "w": "2" }, { "u": 24, "v": 33, "w": "2" }, { "u": 24, "v": 34, "w": "5" }, { "u": 24, "v": 35, "w": "2" }, { "u": 24, "v": 36, "w": "4" }, { "u": 24, "v": 37, "w": "1" }, { "u": 24, "v": 38, "w": "1" }, { "u": 24, "v": 39, "w": "1" }, { "u": 24, "v": 40, "w": "1" }, { "u": 24, "v": 41, "w": "4" }, { "u": 24, "v": 42, "w": "1" }, { "u": 24, "v": 43, "w": "2" }, { "u": 24, "v": 44, "w": "3" }, { "u": 24, "v": 45, "w": "1" }, { "u": 24, "v": 46, "w": "4" }, { "u": 24, "v": 47, "w": "4" }, { "u": 24, "v": 48, "w": "2" }, { "u": 24, "v": 49, "w": "2" }, { "u": 24, "v": 50, "w": "5" }, { "u": 24, "v": 51, "w": "1" }, { "u": 24, "v": 52, "w": "3" }, { "u": 24, "v": 53, "w": "1" }, { "u": 24, "v": 54, "w": "1" }, { "u": 24, "v": 55, "w": "3" }, { "u": 24, "v": 56, "w": "4" }, { "u": 24, "v": 57, "w": "2" }, { "u": 24, "v": 58, "w": "5" }, { "u": 25, "v": 1, "w": "2" }, { "u": 25, "v": 2, "w": "4" }, { "u": 25, "v": 3, "w": "5" }, { "u": 25, "v": 4, "w": "3" }, { "u": 25, "v": 5, "w": "3" }, { "u": 25, "v": 6, "w": "2" }, { "u": 25, "v": 7, "w": "2" }, { "u": 25, "v": 8, "w": "2" }, { "u": 25, "v": 9, "w": "3" }, { "u": 25, "v": 10, "w": "1" }, { "u": 25, "v": 11, "w": "3" }, { "u": 25, "v": 12, "w": "4" }, { "u": 25, "v": 13, "w": "4" }, { "u": 25, "v": 14, "w": "2" }, { "u": 25, "v": 15, "w": "2" }, { "u": 25, "v": 16, "w": "1" }, { "u": 25, "v": 17, "w": "3" }, { "u": 25, "v": 18, "w": "1" }, { "u": 25, "v": 19, "w": "4" }, { "u": 25, "v": 20, "w": "2" }, { "u": 25, "v": 21, "w": "2" }, { "u": 25, "v": 22, "w": "5" }, { "u": 25, "v": 23, "w": "4" }, { "u": 25, "v": 24, "w": "2" }, { "u": 25, "v": 26, "w": "3" }, { "u": 25, "v": 27, "w": "2" }, { "u": 25, "v": 28, "w": "1" }, { "u": 25, "v": 29, "w": "3" }, { "u": 25, "v": 30, "w": "4" }, { "u": 25, "v": 31, "w": "2" }, { "u": 25, "v": 32, "w": "1" }, { "u": 25, "v": 33, "w": "2" }, { "u": 25, "v": 34, "w": "3" }, { "u": 25, "v": 35, "w": "3" }, { "u": 25, "v": 36, "w": "2" }, { "u": 25, "v": 37, "w": "2" }, { "u": 25, "v": 38, "w": "2" }, { "u": 25, "v": 39, "w": "3" }, { "u": 25, "v": 40, "w": "3" }, { "u": 25, "v": 41, "w": "2" }, { "u": 25, "v": 42, "w": "3" }, { "u": 25, "v": 43, "w": "4" }, { "u": 25, "v": 44, "w": "2" }, { "u": 25, "v": 45, "w": "2" }, { "u": 25, "v": 46, "w": "1" }, { "u": 25, "v": 47, "w": "1" }, { "u": 25, "v": 48, "w": "2" }, { "u": 25, "v": 49, "w": "3" }, { "u": 25, "v": 50, "w": "2" }, { "u": 25, "v": 51, "w": "1" }, { "u": 25, "v": 52, "w": "2" }, { "u": 25, "v": 53, "w": "2" }, { "u": 25, "v": 54, "w": "2" }, { "u": 25, "v": 55, "w": "1" }, { "u": 25, "v": 56, "w": "3" }, { "u": 25, "v": 57, "w": "4" }, { "u": 25, "v": 58, "w": "2" }, { "u": 26, "v": 1, "w": "3" }, { "u": 26, "v": 2, "w": "3" }, { "u": 26, "v": 3, "w": "4" }, { "u": 26, "v": 4, "w": "4" }, { "u": 26, "v": 5, "w": "4" }, { "u": 26, "v": 6, "w": "2" }, { "u": 26, "v": 7, "w": "2" }, { "u": 26, "v": 8, "w": "2" }, { "u": 26, "v": 9, "w": "5" }, { "u": 26, "v": 10, "w": "2" }, { "u": 26, "v": 11, "w": "3" }, { "u": 26, "v": 12, "w": "3" }, { "u": 26, "v": 13, "w": "2" }, { "u": 26, "v": 14, "w": "2" }, { "u": 26, "v": 15, "w": "2" }, { "u": 26, "v": 16, "w": "2" }, { "u": 26, "v": 17, "w": "2" }, { "u": 26, "v": 18, "w": "2" }, { "u": 26, "v": 19, "w": "3" }, { "u": 26, "v": 20, "w": "1" }, { "u": 26, "v": 21, "w": "2" }, { "u": 26, "v": 22, "w": "4" }, { "u": 26, "v": 23, "w": "3" }, { "u": 26, "v": 24, "w": "3" }, { "u": 26, "v": 25, "w": "4" }, { "u": 26, "v": 27, "w": "2" }, { "u": 26, "v": 28, "w": "2" }, { "u": 26, "v": 29, "w": "2" }, { "u": 26, "v": 30, "w": "2" }, { "u": 26, "v": 31, "w": "2" }, { "u": 26, "v": 32, "w": "2" }, { "u": 26, "v": 33, "w": "2" }, { "u": 26, "v": 34, "w": "3" }, { "u": 26, "v": 35, "w": "4" }, { "u": 26, "v": 36, "w": "2" }, { "u": 26, "v": 37, "w": "4" }, { "u": 26, "v": 38, "w": "2" }, { "u": 26, "v": 39, "w": "2" }, { "u": 26, "v": 40, "w": "2" }, { "u": 26, "v": 41, "w": "2" }, { "u": 26, "v": 42, "w": "4" }, { "u": 26, "v": 43, "w": "4" }, { "u": 26, "v": 44, "w": "3" }, { "u": 26, "v": 45, "w": "4" }, { "u": 26, "v": 46, "w": "2" }, { "u": 26, "v": 47, "w": "3" }, { "u": 26, "v": 48, "w": "4" }, { "u": 26, "v": 49, "w": "3" }, { "u": 26, "v": 50, "w": "3" }, { "u": 26, "v": 51, "w": "3" }, { "u": 26, "v": 52, "w": "5" }, { "u": 26, "v": 53, "w": "2" }, { "u": 26, "v": 54, "w": "3" }, { "u": 26, "v": 55, "w": "2" }, { "u": 26, "v": 56, "w": "2" }, { "u": 26, "v": 57, "w": "3" }, { "u": 26, "v": 58, "w": "2" }, { "u": 27, "v": 1, "w": "4" }, { "u": 27, "v": 2, "w": "2" }, { "u": 27, "v": 3, "w": "5" }, { "u": 27, "v": 4, "w": "5" }, { "u": 27, "v": 5, "w": "5" }, { "u": 27, "v": 6, "w": "3" }, { "u": 27, "v": 7, "w": "5" }, { "u": 27, "v": 8, "w": "3" }, { "u": 27, "v": 9, "w": "1" }, { "u": 27, "v": 10, "w": "5" }, { "u": 27, "v": 11, "w": "5" }, { "u": 27, "v": 12, "w": "3" }, { "u": 27, "v": 13, "w": "1" }, { "u": 27, "v": 14, "w": "4" }, { "u": 27, "v": 15, "w": "4" }, { "u": 27, "v": 16, "w": "2" }, { "u": 27, "v": 17, "w": "1" }, { "u": 27, "v": 18, "w": "2" }, { "u": 27, "v": 19, "w": "1" }, { "u": 27, "v": 20, "w": "5" }, { "u": 27, "v": 21, "w": "2" }, { "u": 27, "v": 22, "w": "4" }, { "u": 27, "v": 23, "w": "3" }, { "u": 27, "v": 24, "w": "4" }, { "u": 27, "v": 25, "w": "1" }, { "u": 27, "v": 26, "w": "1" }, { "u": 27, "v": 28, "w": "1" }, { "u": 27, "v": 29, "w": "5" }, { "u": 27, "v": 30, "w": "4" }, { "u": 27, "v": 31, "w": "5" }, { "u": 27, "v": 32, "w": "3" }, { "u": 27, "v": 33, "w": "4" }, { "u": 27, "v": 34, "w": "5" }, { "u": 27, "v": 35, "w": "4" }, { "u": 27, "v": 36, "w": "3" }, { "u": 27, "v": 37, "w": "1" }, { "u": 27, "v": 38, "w": "2" }, { "u": 27, "v": 39, "w": "1" }, { "u": 27, "v": 40, "w": "2" }, { "u": 27, "v": 41, "w": "1" }, { "u": 27, "v": 42, "w": "4" }, { "u": 27, "v": 43, "w": "1" }, { "u": 27, "v": 44, "w": "2" }, { "u": 27, "v": 45, "w": "3" }, { "u": 27, "v": 46, "w": "1" }, { "u": 27, "v": 47, "w": "1" }, { "u": 27, "v": 48, "w": "4" }, { "u": 27, "v": 49, "w": "2" }, { "u": 27, "v": 50, "w": "4" }, { "u": 27, "v": 51, "w": "1" }, { "u": 27, "v": 52, "w": "1" }, { "u": 27, "v": 53, "w": "4" }, { "u": 27, "v": 54, "w": "1" }, { "u": 27, "v": 55, "w": "2" }, { "u": 27, "v": 56, "w": "5" }, { "u": 27, "v": 57, "w": "5" }, { "u": 27, "v": 58, "w": "3" }, { "u": 28, "v": 1, "w": "3" }, { "u": 28, "v": 2, "w": "3" }, { "u": 28, "v": 3, "w": "3" }, { "u": 28, "v": 4, "w": "3" }, { "u": 28, "v": 5, "w": "3" }, { "u": 28, "v": 6, "w": "3" }, { "u": 28, "v": 7, "w": "3" }, { "u": 28, "v": 8, "w": "3" }, { "u": 28, "v": 9, "w": "3" }, { "u": 28, "v": 10, "w": "3" }, { "u": 28, "v": 11, "w": "3" }, { "u": 28, "v": 12, "w": "3" }, { "u": 28, "v": 13, "w": "3" }, { "u": 28, "v": 14, "w": "3" }, { "u": 28, "v": 15, "w": "3" }, { "u": 28, "v": 16, "w": "3" }, { "u": 28, "v": 17, "w": "3" }, { "u": 28, "v": 18, "w": "3" }, { "u": 28, "v": 19, "w": "3" }, { "u": 28, "v": 20, "w": "3" }, { "u": 28, "v": 21, "w": "3" }, { "u": 28, "v": 22, "w": "3" }, { "u": 28, "v": 23, "w": "3" }, { "u": 28, "v": 24, "w": "3" }, { "u": 28, "v": 25, "w": "3" }, { "u": 28, "v": 26, "w": "3" }, { "u": 28, "v": 27, "w": "3" }, { "u": 28, "v": 29, "w": "3" }, { "u": 28, "v": 30, "w": "3" }, { "u": 28, "v": 31, "w": "3" }, { "u": 28, "v": 32, "w": "3" }, { "u": 28, "v": 33, "w": "3" }, { "u": 28, "v": 34, "w": "3" }, { "u": 28, "v": 35, "w": "3" }, { "u": 28, "v": 36, "w": "3" }, { "u": 28, "v": 37, "w": "3" }, { "u": 28, "v": 38, "w": "3" }, { "u": 28, "v": 39, "w": "3" }, { "u": 28, "v": 40, "w": "3" }, { "u": 28, "v": 41, "w": "3" }, { "u": 28, "v": 42, "w": "3" }, { "u": 28, "v": 43, "w": "3" }, { "u": 28, "v": 44, "w": "3" }, { "u": 28, "v": 45, "w": "3" }, { "u": 28, "v": 46, "w": "3" }, { "u": 28, "v": 47, "w": "3" }, { "u": 28, "v": 48, "w": "3" }, { "u": 28, "v": 49, "w": "3" }, { "u": 28, "v": 50, "w": "3" }, { "u": 28, "v": 51, "w": "3" }, { "u": 28, "v": 52, "w": "3" }, { "u": 28, "v": 53, "w": "3" }, { "u": 28, "v": 54, "w": "3" }, { "u": 28, "v": 55, "w": "3" }, { "u": 28, "v": 56, "w": "3" }, { "u": 28, "v": 57, "w": "3" }, { "u": 28, "v": 58, "w": "3" }, { "u": 29, "v": 1, "w": "2" }, { "u": 29, "v": 2, "w": "3" }, { "u": 29, "v": 3, "w": "5" }, { "u": 29, "v": 4, "w": "3" }, { "u": 29, "v": 5, "w": "3" }, { "u": 29, "v": 6, "w": "3" }, { "u": 29, "v": 7, "w": "4" }, { "u": 29, "v": 8, "w": "2" }, { "u": 29, "v": 9, "w": "3" }, { "u": 29, "v": 10, "w": "4" }, { "u": 29, "v": 11, "w": "3" }, { "u": 29, "v": 12, "w": "2" }, { "u": 29, "v": 13, "w": "3" }, { "u": 29, "v": 14, "w": "5" }, { "u": 29, "v": 15, "w": "4" }, { "u": 29, "v": 16, "w": "4" }, { "u": 29, "v": 17, "w": "4" }, { "u": 29, "v": 18, "w": "1" }, { "u": 29, "v": 19, "w": "3" }, { "u": 29, "v": 20, "w": "3" }, { "u": 29, "v": 21, "w": "1" }, { "u": 29, "v": 22, "w": "4" }, { "u": 29, "v": 23, "w": "3" }, { "u": 29, "v": 24, "w": "1" }, { "u": 29, "v": 25, "w": "4" }, { "u": 29, "v": 26, "w": "1" }, { "u": 29, "v": 27, "w": "5" }, { "u": 29, "v": 28, "w": "2" }, { "u": 29, "v": 30, "w": "2" }, { "u": 29, "v": 31, "w": "3" }, { "u": 29, "v": 32, "w": "2" }, { "u": 29, "v": 33, "w": "3" }, { "u": 29, "v": 34, "w": "2" }, { "u": 29, "v": 35, "w": "5" }, { "u": 29, "v": 36, "w": "3" }, { "u": 29, "v": 37, "w": "2" }, { "u": 29, "v": 38, "w": "4" }, { "u": 29, "v": 39, "w": "4" }, { "u": 29, "v": 40, "w": "3" }, { "u": 29, "v": 41, "w": "2" }, { "u": 29, "v": 42, "w": "4" }, { "u": 29, "v": 43, "w": "4" }, { "u": 29, "v": 44, "w": "3" }, { "u": 29, "v": 45, "w": "2" }, { "u": 29, "v": 46, "w": "1" }, { "u": 29, "v": 47, "w": "2" }, { "u": 29, "v": 48, "w": "1" }, { "u": 29, "v": 49, "w": "2" }, { "u": 29, "v": 50, "w": "1" }, { "u": 29, "v": 51, "w": "1" }, { "u": 29, "v": 52, "w": "2" }, { "u": 29, "v": 53, "w": "3" }, { "u": 29, "v": 54, "w": "5" }, { "u": 29, "v": 55, "w": "3" }, { "u": 29, "v": 56, "w": "3" }, { "u": 29, "v": 57, "w": "5" }, { "u": 29, "v": 58, "w": "2" }, { "u": 30, "v": 1, "w": "5" }, { "u": 30, "v": 2, "w": "2" }, { "u": 30, "v": 3, "w": "5" }, { "u": 30, "v": 4, "w": "5" }, { "u": 30, "v": 5, "w": "1" }, { "u": 30, "v": 6, "w": "3" }, { "u": 30, "v": 7, "w": "2" }, { "u": 30, "v": 8, "w": "3" }, { "u": 30, "v": 9, "w": "3" }, { "u": 30, "v": 10, "w": "1" }, { "u": 30, "v": 11, "w": "5" }, { "u": 30, "v": 12, "w": "1" }, { "u": 30, "v": 13, "w": "2" }, { "u": 30, "v": 14, "w": "2" }, { "u": 30, "v": 15, "w": "3" }, { "u": 30, "v": 16, "w": "3" }, { "u": 30, "v": 17, "w": "1" }, { "u": 30, "v": 18, "w": "1" }, { "u": 30, "v": 19, "w": "2" }, { "u": 30, "v": 20, "w": "5" }, { "u": 30, "v": 21, "w": "2" }, { "u": 30, "v": 22, "w": "2" }, { "u": 30, "v": 23, "w": "1" }, { "u": 30, "v": 24, "w": "3" }, { "u": 30, "v": 25, "w": "3" }, { "u": 30, "v": 26, "w": "1" }, { "u": 30, "v": 27, "w": "3" }, { "u": 30, "v": 28, "w": "1" }, { "u": 30, "v": 29, "w": "3" }, { "u": 30, "v": 31, "w": "2" }, { "u": 30, "v": 32, "w": "2" }, { "u": 30, "v": 33, "w": "2" }, { "u": 30, "v": 34, "w": "3" }, { "u": 30, "v": 35, "w": "4" }, { "u": 30, "v": 36, "w": "2" }, { "u": 30, "v": 37, "w": "4" }, { "u": 30, "v": 38, "w": "3" }, { "u": 30, "v": 39, "w": "1" }, { "u": 30, "v": 40, "w": "1" }, { "u": 30, "v": 41, "w": "3" }, { "u": 30, "v": 42, "w": "4" }, { "u": 30, "v": 43, "w": "3" }, { "u": 30, "v": 44, "w": "3" }, { "u": 30, "v": 45, "w": "1" }, { "u": 30, "v": 46, "w": "1" }, { "u": 30, "v": 47, "w": "5" }, { "u": 30, "v": 48, "w": "2" }, { "u": 30, "v": 49, "w": "1" }, { "u": 30, "v": 50, "w": "2" }, { "u": 30, "v": 51, "w": "1" }, { "u": 30, "v": 52, "w": "2" }, { "u": 30, "v": 53, "w": "3" }, { "u": 30, "v": 54, "w": "3" }, { "u": 30, "v": 55, "w": "3" }, { "u": 30, "v": 56, "w": "3" }, { "u": 30, "v": 57, "w": "5" }, { "u": 30, "v": 58, "w": "3" }, { "u": 31, "v": 1, "w": "4" }, { "u": 31, "v": 2, "w": "2" }, { "u": 31, "v": 3, "w": "4" }, { "u": 31, "v": 4, "w": "4" }, { "u": 31, "v": 5, "w": "2" }, { "u": 31, "v": 6, "w": "4" }, { "u": 31, "v": 7, "w": "5" }, { "u": 31, "v": 8, "w": "5" }, { "u": 31, "v": 9, "w": "3" }, { "u": 31, "v": 10, "w": "3" }, { "u": 31, "v": 11, "w": "5" }, { "u": 31, "v": 12, "w": "3" }, { "u": 31, "v": 13, "w": "2" }, { "u": 31, "v": 14, "w": "3" }, { "u": 31, "v": 15, "w": "4" }, { "u": 31, "v": 16, "w": "3" }, { "u": 31, "v": 17, "w": "3" }, { "u": 31, "v": 18, "w": "3" }, { "u": 31, "v": 19, "w": "3" }, { "u": 31, "v": 20, "w": "4" }, { "u": 31, "v": 21, "w": "4" }, { "u": 31, "v": 22, "w": "3" }, { "u": 31, "v": 23, "w": "4" }, { "u": 31, "v": 24, "w": "5" }, { "u": 31, "v": 25, "w": "3" }, { "u": 31, "v": 26, "w": "2" }, { "u": 31, "v": 27, "w": "4" }, { "u": 31, "v": 28, "w": "2" }, { "u": 31, "v": 29, "w": "4" }, { "u": 31, "v": 30, "w": "2" }, { "u": 31, "v": 32, "w": "5" }, { "u": 31, "v": 33, "w": "3" }, { "u": 31, "v": 34, "w": "4" }, { "u": 31, "v": 35, "w": "4" }, { "u": 31, "v": 36, "w": "4" }, { "u": 31, "v": 37, "w": "3" }, { "u": 31, "v": 38, "w": "3" }, { "u": 31, "v": 39, "w": "2" }, { "u": 31, "v": 40, "w": "2" }, { "u": 31, "v": 41, "w": "4" }, { "u": 31, "v": 42, "w": "4" }, { "u": 31, "v": 43, "w": "2" }, { "u": 31, "v": 44, "w": "3" }, { "u": 31, "v": 45, "w": "2" }, { "u": 31, "v": 46, "w": "4" }, { "u": 31, "v": 47, "w": "3" }, { "u": 31, "v": 48, "w": "4" }, { "u": 31, "v": 49, "w": "3" }, { "u": 31, "v": 50, "w": "4" }, { "u": 31, "v": 51, "w": "2" }, { "u": 31, "v": 52, "w": "4" }, { "u": 31, "v": 53, "w": "3" }, { "u": 31, "v": 54, "w": "4" }, { "u": 31, "v": 55, "w": "3" }, { "u": 31, "v": 56, "w": "4" }, { "u": 31, "v": 57, "w": "4" }, { "u": 31, "v": 58, "w": "3" }, { "u": 32, "v": 1, "w": "5" }, { "u": 32, "v": 2, "w": "3" }, { "u": 32, "v": 3, "w": "4" }, { "u": 32, "v": 4, "w": "4" }, { "u": 32, "v": 5, "w": "3" }, { "u": 32, "v": 6, "w": "4" }, { "u": 32, "v": 7, "w": "4" }, { "u": 32, "v": 8, "w": "4" }, { "u": 32, "v": 9, "w": "3" }, { "u": 32, "v": 10, "w": "3" }, { "u": 32, "v": 11, "w": "5" }, { "u": 32, "v": 12, "w": "3" }, { "u": 32, "v": 13, "w": "2" }, { "u": 32, "v": 14, "w": "4" }, { "u": 32, "v": 15, "w": "5" }, { "u": 32, "v": 16, "w": "4" }, { "u": 32, "v": 17, "w": "4" }, { "u": 32, "v": 18, "w": "5" }, { "u": 32, "v": 19, "w": "4" }, { "u": 32, "v": 20, "w": "5" }, { "u": 32, "v": 21, "w": "5" }, { "u": 32, "v": 22, "w": "4" }, { "u": 32, "v": 23, "w": "3" }, { "u": 32, "v": 24, "w": "4" }, { "u": 32, "v": 25, "w": "3" }, { "u": 32, "v": 26, "w": "2" }, { "u": 32, "v": 27, "w": "4" }, { "u": 32, "v": 28, "w": "2" }, { "u": 32, "v": 29, "w": "2" }, { "u": 32, "v": 30, "w": "5" }, { "u": 32, "v": 31, "w": "5" }, { "u": 32, "v": 33, "w": "5" }, { "u": 32, "v": 34, "w": "5" }, { "u": 32, "v": 35, "w": "4" }, { "u": 32, "v": 36, "w": "3" }, { "u": 32, "v": 37, "w": "3" }, { "u": 32, "v": 38, "w": "3" }, { "u": 32, "v": 39, "w": "2" }, { "u": 32, "v": 40, "w": "2" }, { "u": 32, "v": 41, "w": "3" }, { "u": 32, "v": 42, "w": "3" }, { "u": 32, "v": 43, "w": "2" }, { "u": 32, "v": 44, "w": "4" }, { "u": 32, "v": 45, "w": "2" }, { "u": 32, "v": 46, "w": "5" }, { "u": 32, "v": 47, "w": "3" }, { "u": 32, "v": 48, "w": "5" }, { "u": 32, "v": 49, "w": "5" }, { "u": 32, "v": 50, "w": "5" }, { "u": 32, "v": 51, "w": "2" }, { "u": 32, "v": 52, "w": "2" }, { "u": 32, "v": 53, "w": "4" }, { "u": 32, "v": 54, "w": "4" }, { "u": 32, "v": 55, "w": "3" }, { "u": 32, "v": 56, "w": "4" }, { "u": 32, "v": 57, "w": "5" }, { "u": 32, "v": 58, "w": "5" }, { "u": 33, "v": 1, "w": "3" }, { "u": 33, "v": 2, "w": "3" }, { "u": 33, "v": 3, "w": "3" }, { "u": 33, "v": 4, "w": "5" }, { "u": 33, "v": 5, "w": "2" }, { "u": 33, "v": 6, "w": "5" }, { "u": 33, "v": 7, "w": "5" }, { "u": 33, "v": 8, "w": "2" }, { "u": 33, "v": 9, "w": "3" }, { "u": 33, "v": 10, "w": "2" }, { "u": 33, "v": 11, "w": "3" }, { "u": 33, "v": 12, "w": "4" }, { "u": 33, "v": 13, "w": "3" }, { "u": 33, "v": 14, "w": "4" }, { "u": 33, "v": 15, "w": "4" }, { "u": 33, "v": 16, "w": "4" }, { "u": 33, "v": 17, "w": "3" }, { "u": 33, "v": 18, "w": "3" }, { "u": 33, "v": 19, "w": "4" }, { "u": 33, "v": 20, "w": "5" }, { "u": 33, "v": 21, "w": "2" }, { "u": 33, "v": 22, "w": "2" }, { "u": 33, "v": 23, "w": "4" }, { "u": 33, "v": 24, "w": "2" }, { "u": 33, "v": 25, "w": "3" }, { "u": 33, "v": 26, "w": "3" }, { "u": 33, "v": 27, "w": "3" }, { "u": 33, "v": 28, "w": "4" }, { "u": 33, "v": 29, "w": "3" }, { "u": 33, "v": 30, "w": "3" }, { "u": 33, "v": 31, "w": "2" }, { "u": 33, "v": 32, "w": "3" }, { "u": 33, "v": 34, "w": "4" }, { "u": 33, "v": 35, "w": "4" }, { "u": 33, "v": 36, "w": "3" }, { "u": 33, "v": 37, "w": "2" }, { "u": 33, "v": 38, "w": "3" }, { "u": 33, "v": 39, "w": "2" }, { "u": 33, "v": 40, "w": "3" }, { "u": 33, "v": 41, "w": "3" }, { "u": 33, "v": 42, "w": "3" }, { "u": 33, "v": 43, "w": "4" }, { "u": 33, "v": 44, "w": "2" }, { "u": 33, "v": 45, "w": "2" }, { "u": 33, "v": 46, "w": "2" }, { "u": 33, "v": 47, "w": "3" }, { "u": 33, "v": 48, "w": "2" }, { "u": 33, "v": 49, "w": "4" }, { "u": 33, "v": 50, "w": "3" }, { "u": 33, "v": 51, "w": "2" }, { "u": 33, "v": 52, "w": "3" }, { "u": 33, "v": 53, "w": "5" }, { "u": 33, "v": 54, "w": "4" }, { "u": 33, "v": 55, "w": "4" }, { "u": 33, "v": 56, "w": "4" }, { "u": 33, "v": 57, "w": "4" }, { "u": 33, "v": 58, "w": "4" }, { "u": 34, "v": 1, "w": "4" }, { "u": 34, "v": 2, "w": "3" }, { "u": 34, "v": 3, "w": "4" }, { "u": 34, "v": 4, "w": "5" }, { "u": 34, "v": 5, "w": "3" }, { "u": 34, "v": 6, "w": "4" }, { "u": 34, "v": 7, "w": "4" }, { "u": 34, "v": 8, "w": "4" }, { "u": 34, "v": 9, "w": "4" }, { "u": 34, "v": 10, "w": "3" }, { "u": 34, "v": 11, "w": "3" }, { "u": 34, "v": 12, "w": "3" }, { "u": 34, "v": 13, "w": "2" }, { "u": 34, "v": 14, "w": "2" }, { "u": 34, "v": 15, "w": "3" }, { "u": 34, "v": 16, "w": "3" }, { "u": 34, "v": 17, "w": "3" }, { "u": 34, "v": 18, "w": "4" }, { "u": 34, "v": 19, "w": "5" }, { "u": 34, "v": 20, "w": "5" }, { "u": 34, "v": 21, "w": "3" }, { "u": 34, "v": 22, "w": "2" }, { "u": 34, "v": 23, "w": "5" }, { "u": 34, "v": 24, "w": "5" }, { "u": 34, "v": 25, "w": "3" }, { "u": 34, "v": 26, "w": "4" }, { "u": 34, "v": 27, "w": "4" }, { "u": 34, "v": 28, "w": "2" }, { "u": 34, "v": 29, "w": "3" }, { "u": 34, "v": 30, "w": "4" }, { "u": 34, "v": 31, "w": "4" }, { "u": 34, "v": 32, "w": "4" }, { "u": 34, "v": 33, "w": "5" }, { "u": 34, "v": 35, "w": "3" }, { "u": 34, "v": 36, "w": "4" }, { "u": 34, "v": 37, "w": "4" }, { "u": 34, "v": 38, "w": "3" }, { "u": 34, "v": 39, "w": "4" }, { "u": 34, "v": 40, "w": "3" }, { "u": 34, "v": 41, "w": "5" }, { "u": 34, "v": 42, "w": "3" }, { "u": 34, "v": 43, "w": "3" }, { "u": 34, "v": 44, "w": "3" }, { "u": 34, "v": 45, "w": "2" }, { "u": 34, "v": 46, "w": "4" }, { "u": 34, "v": 47, "w": "3" }, { "u": 34, "v": 48, "w": "3" }, { "u": 34, "v": 49, "w": "5" }, { "u": 34, "v": 50, "w": "5" }, { "u": 34, "v": 51, "w": "2" }, { "u": 34, "v": 52, "w": "4" }, { "u": 34, "v": 53, "w": "5" }, { "u": 34, "v": 54, "w": "3" }, { "u": 34, "v": 55, "w": "4" }, { "u": 34, "v": 56, "w": "5" }, { "u": 34, "v": 57, "w": "4" }, { "u": 34, "v": 58, "w": "5" }, { "u": 35, "v": 1, "w": "2" }, { "u": 35, "v": 2, "w": "4" }, { "u": 35, "v": 3, "w": "5" }, { "u": 35, "v": 4, "w": "3" }, { "u": 35, "v": 5, "w": "3" }, { "u": 35, "v": 6, "w": "4" }, { "u": 35, "v": 7, "w": "4" }, { "u": 35, "v": 8, "w": "2" }, { "u": 35, "v": 9, "w": "3" }, { "u": 35, "v": 10, "w": "4" }, { "u": 35, "v": 11, "w": "3" }, { "u": 35, "v": 12, "w": "4" }, { "u": 35, "v": 13, "w": "3" }, { "u": 35, "v": 14, "w": "3" }, { "u": 35, "v": 15, "w": "4" }, { "u": 35, "v": 16, "w": "2" }, { "u": 35, "v": 17, "w": "5" }, { "u": 35, "v": 18, "w": "2" }, { "u": 35, "v": 19, "w": "4" }, { "u": 35, "v": 20, "w": "4" }, { "u": 35, "v": 21, "w": "2" }, { "u": 35, "v": 22, "w": "4" }, { "u": 35, "v": 23, "w": "3" }, { "u": 35, "v": 24, "w": "2" }, { "u": 35, "v": 25, "w": "5" }, { "u": 35, "v": 26, "w": "3" }, { "u": 35, "v": 27, "w": "3" }, { "u": 35, "v": 28, "w": "2" }, { "u": 35, "v": 29, "w": "5" }, { "u": 35, "v": 30, "w": "4" }, { "u": 35, "v": 31, "w": "3" }, { "u": 35, "v": 32, "w": "2" }, { "u": 35, "v": 33, "w": "4" }, { "u": 35, "v": 34, "w": "2" }, { "u": 35, "v": 36, "w": "2" }, { "u": 35, "v": 37, "w": "2" }, { "u": 35, "v": 38, "w": "4" }, { "u": 35, "v": 39, "w": "4" }, { "u": 35, "v": 40, "w": "3" }, { "u": 35, "v": 41, "w": "4" }, { "u": 35, "v": 42, "w": "4" }, { "u": 35, "v": 43, "w": "5" }, { "u": 35, "v": 44, "w": "3" }, { "u": 35, "v": 45, "w": "2" }, { "u": 35, "v": 46, "w": "1" }, { "u": 35, "v": 47, "w": "4" }, { "u": 35, "v": 48, "w": "1" }, { "u": 35, "v": 49, "w": "3" }, { "u": 35, "v": 50, "w": "3" }, { "u": 35, "v": 51, "w": "2" }, { "u": 35, "v": 52, "w": "2" }, { "u": 35, "v": 53, "w": "2" }, { "u": 35, "v": 54, "w": "5" }, { "u": 35, "v": 55, "w": "2" }, { "u": 35, "v": 56, "w": "3" }, { "u": 35, "v": 57, "w": "4" }, { "u": 35, "v": 58, "w": "2" }, { "u": 36, "v": 1, "w": "5" }, { "u": 36, "v": 2, "w": "1" }, { "u": 36, "v": 3, "w": "2" }, { "u": 36, "v": 4, "w": "5" }, { "u": 36, "v": 5, "w": "1" }, { "u": 36, "v": 6, "w": "4" }, { "u": 36, "v": 7, "w": "5" }, { "u": 36, "v": 8, "w": "1" }, { "u": 36, "v": 9, "w": "3" }, { "u": 36, "v": 10, "w": "3" }, { "u": 36, "v": 11, "w": "5" }, { "u": 36, "v": 12, "w": "5" }, { "u": 36, "v": 13, "w": "2" }, { "u": 36, "v": 14, "w": "5" }, { "u": 36, "v": 15, "w": "3" }, { "u": 36, "v": 16, "w": "4" }, { "u": 36, "v": 17, "w": "2" }, { "u": 36, "v": 18, "w": "3" }, { "u": 36, "v": 19, "w": "5" }, { "u": 36, "v": 20, "w": "5" }, { "u": 36, "v": 21, "w": "5" }, { "u": 36, "v": 22, "w": "2" }, { "u": 36, "v": 23, "w": "1" }, { "u": 36, "v": 24, "w": "1" }, { "u": 36, "v": 25, "w": "1" }, { "u": 36, "v": 26, "w": "1" }, { "u": 36, "v": 27, "w": "3" }, { "u": 36, "v": 28, "w": "3" }, { "u": 36, "v": 29, "w": "3" }, { "u": 36, "v": 30, "w": "2" }, { "u": 36, "v": 31, "w": "2" }, { "u": 36, "v": 32, "w": "2" }, { "u": 36, "v": 33, "w": "5" }, { "u": 36, "v": 34, "w": "4" }, { "u": 36, "v": 35, "w": "4" }, { "u": 36, "v": 37, "w": "1" }, { "u": 36, "v": 38, "w": "1" }, { "u": 36, "v": 39, "w": "1" }, { "u": 36, "v": 40, "w": "1" }, { "u": 36, "v": 41, "w": "5" }, { "u": 36, "v": 42, "w": "3" }, { "u": 36, "v": 43, "w": "4" }, { "u": 36, "v": 44, "w": "4" }, { "u": 36, "v": 45, "w": "4" }, { "u": 36, "v": 46, "w": "5" }, { "u": 36, "v": 47, "w": "4" }, { "u": 36, "v": 48, "w": "5" }, { "u": 36, "v": 49, "w": "1" }, { "u": 36, "v": 50, "w": "2" }, { "u": 36, "v": 51, "w": "1" }, { "u": 36, "v": 52, "w": "2" }, { "u": 36, "v": 53, "w": "1" }, { "u": 36, "v": 54, "w": "3" }, { "u": 36, "v": 55, "w": "1" }, { "u": 36, "v": 56, "w": "5" }, { "u": 36, "v": 57, "w": "5" }, { "u": 36, "v": 58, "w": "4" }, { "u": 37, "v": 1, "w": "2" }, { "u": 37, "v": 2, "w": "3" }, { "u": 37, "v": 3, "w": "3" }, { "u": 37, "v": 4, "w": "4" }, { "u": 37, "v": 5, "w": "2" }, { "u": 37, "v": 6, "w": "4" }, { "u": 37, "v": 7, "w": "3" }, { "u": 37, "v": 8, "w": "3" }, { "u": 37, "v": 9, "w": "4" }, { "u": 37, "v": 10, "w": "3" }, { "u": 37, "v": 11, "w": "3" }, { "u": 37, "v": 12, "w": "4" }, { "u": 37, "v": 13, "w": "3" }, { "u": 37, "v": 14, "w": "3" }, { "u": 37, "v": 15, "w": "3" }, { "u": 37, "v": 16, "w": "3" }, { "u": 37, "v": 17, "w": "2" }, { "u": 37, "v": 18, "w": "2" }, { "u": 37, "v": 19, "w": "5" }, { "u": 37, "v": 20, "w": "3" }, { "u": 37, "v": 21, "w": "2" }, { "u": 37, "v": 22, "w": "3" }, { "u": 37, "v": 23, "w": "5" }, { "u": 37, "v": 24, "w": "2" }, { "u": 37, "v": 25, "w": "4" }, { "u": 37, "v": 26, "w": "4" }, { "u": 37, "v": 27, "w": "3" }, { "u": 37, "v": 28, "w": "2" }, { "u": 37, "v": 29, "w": "2" }, { "u": 37, "v": 30, "w": "3" }, { "u": 37, "v": 31, "w": "3" }, { "u": 37, "v": 32, "w": "2" }, { "u": 37, "v": 33, "w": "3" }, { "u": 37, "v": 34, "w": "4" }, { "u": 37, "v": 35, "w": "4" }, { "u": 37, "v": 36, "w": "2" }, { "u": 37, "v": 38, "w": "4" }, { "u": 37, "v": 39, "w": "5" }, { "u": 37, "v": 40, "w": "2" }, { "u": 37, "v": 41, "w": "4" }, { "u": 37, "v": 42, "w": "3" }, { "u": 37, "v": 43, "w": "3" }, { "u": 37, "v": 44, "w": "4" }, { "u": 37, "v": 45, "w": "2" }, { "u": 37, "v": 46, "w": "2" }, { "u": 37, "v": 47, "w": "5" }, { "u": 37, "v": 48, "w": "2" }, { "u": 37, "v": 49, "w": "5" }, { "u": 37, "v": 50, "w": "3" }, { "u": 37, "v": 51, "w": "5" }, { "u": 37, "v": 52, "w": "3" }, { "u": 37, "v": 53, "w": "3" }, { "u": 37, "v": 54, "w": "3" }, { "u": 37, "v": 55, "w": "3" }, { "u": 37, "v": 56, "w": "2" }, { "u": 37, "v": 57, "w": "3" }, { "u": 37, "v": 58, "w": "3" }, { "u": 38, "v": 1, "w": "2" }, { "u": 38, "v": 2, "w": "5" }, { "u": 38, "v": 3, "w": "3" }, { "u": 38, "v": 4, "w": "3" }, { "u": 38, "v": 5, "w": "2" }, { "u": 38, "v": 6, "w": "3" }, { "u": 38, "v": 7, "w": "2" }, { "u": 38, "v": 8, "w": "2" }, { "u": 38, "v": 9, "w": "3" }, { "u": 38, "v": 10, "w": "3" }, { "u": 38, "v": 11, "w": "3" }, { "u": 38, "v": 12, "w": "3" }, { "u": 38, "v": 13, "w": "3" }, { "u": 38, "v": 14, "w": "3" }, { "u": 38, "v": 15, "w": "4" }, { "u": 38, "v": 16, "w": "4" }, { "u": 38, "v": 17, "w": "4" }, { "u": 38, "v": 18, "w": "2" }, { "u": 38, "v": 19, "w": "4" }, { "u": 38, "v": 20, "w": "4" }, { "u": 38, "v": 21, "w": "2" }, { "u": 38, "v": 22, "w": "2" }, { "u": 38, "v": 23, "w": "3" }, { "u": 38, "v": 24, "w": "2" }, { "u": 38, "v": 25, "w": "3" }, { "u": 38, "v": 26, "w": "2" }, { "u": 38, "v": 27, "w": "2" }, { "u": 38, "v": 28, "w": "3" }, { "u": 38, "v": 29, "w": "4" }, { "u": 38, "v": 30, "w": "3" }, { "u": 38, "v": 31, "w": "3" }, { "u": 38, "v": 32, "w": "3" }, { "u": 38, "v": 33, "w": "4" }, { "u": 38, "v": 34, "w": "2" }, { "u": 38, "v": 35, "w": "5" }, { "u": 38, "v": 36, "w": "3" }, { "u": 38, "v": 37, "w": "3" }, { "u": 38, "v": 39, "w": "3" }, { "u": 38, "v": 40, "w": "2" }, { "u": 38, "v": 41, "w": "3" }, { "u": 38, "v": 42, "w": "4" }, { "u": 38, "v": 43, "w": "4" }, { "u": 38, "v": 44, "w": "3" }, { "u": 38, "v": 45, "w": "2" }, { "u": 38, "v": 46, "w": "3" }, { "u": 38, "v": 47, "w": "4" }, { "u": 38, "v": 48, "w": "3" }, { "u": 38, "v": 49, "w": "3" }, { "u": 38, "v": 50, "w": "3" }, { "u": 38, "v": 51, "w": "2" }, { "u": 38, "v": 52, "w": "2" }, { "u": 38, "v": 53, "w": "4" }, { "u": 38, "v": 54, "w": "4" }, { "u": 38, "v": 55, "w": "2" }, { "u": 38, "v": 56, "w": "3" }, { "u": 38, "v": 57, "w": "4" }, { "u": 38, "v": 58, "w": "3" }, { "u": 39, "v": 1, "w": "2" }, { "u": 39, "v": 2, "w": "2" }, { "u": 39, "v": 3, "w": "3" }, { "u": 39, "v": 4, "w": "4" }, { "u": 39, "v": 5, "w": "5" }, { "u": 39, "v": 6, "w": "3" }, { "u": 39, "v": 7, "w": "3" }, { "u": 39, "v": 8, "w": "2" }, { "u": 39, "v": 9, "w": "3" }, { "u": 39, "v": 10, "w": "4" }, { "u": 39, "v": 11, "w": "4" }, { "u": 39, "v": 12, "w": "5" }, { "u": 39, "v": 13, "w": "4" }, { "u": 39, "v": 14, "w": "4" }, { "u": 39, "v": 15, "w": "3" }, { "u": 39, "v": 16, "w": "5" }, { "u": 39, "v": 17, "w": "3" }, { "u": 39, "v": 18, "w": "2" }, { "u": 39, "v": 19, "w": "2" }, { "u": 39, "v": 20, "w": "4" }, { "u": 39, "v": 21, "w": "2" }, { "u": 39, "v": 22, "w": "5" }, { "u": 39, "v": 23, "w": "5" }, { "u": 39, "v": 24, "w": "2" }, { "u": 39, "v": 25, "w": "5" }, { "u": 39, "v": 26, "w": "4" }, { "u": 39, "v": 27, "w": "4" }, { "u": 39, "v": 28, "w": "2" }, { "u": 39, "v": 29, "w": "5" }, { "u": 39, "v": 30, "w": "2" }, { "u": 39, "v": 31, "w": "2" }, { "u": 39, "v": 32, "w": "2" }, { "u": 39, "v": 33, "w": "2" }, { "u": 39, "v": 34, "w": "3" }, { "u": 39, "v": 35, "w": "5" }, { "u": 39, "v": 36, "w": "2" }, { "u": 39, "v": 37, "w": "5" }, { "u": 39, "v": 38, "w": "2" }, { "u": 39, "v": 40, "w": "3" }, { "u": 39, "v": 41, "w": "2" }, { "u": 39, "v": 42, "w": "5" }, { "u": 39, "v": 43, "w": "5" }, { "u": 39, "v": 44, "w": "3" }, { "u": 39, "v": 45, "w": "5" }, { "u": 39, "v": 46, "w": "2" }, { "u": 39, "v": 47, "w": "5" }, { "u": 39, "v": 48, "w": "2" }, { "u": 39, "v": 49, "w": "5" }, { "u": 39, "v": 50, "w": "3" }, { "u": 39, "v": 51, "w": "5" }, { "u": 39, "v": 52, "w": "2" }, { "u": 39, "v": 53, "w": "3" }, { "u": 39, "v": 54, "w": "4" }, { "u": 39, "v": 55, "w": "3" }, { "u": 39, "v": 56, "w": "3" }, { "u": 39, "v": 57, "w": "4" }, { "u": 39, "v": 58, "w": "3" }, { "u": 40, "v": 1, "w": "2" }, { "u": 40, "v": 2, "w": "3" }, { "u": 40, "v": 3, "w": "3" }, { "u": 40, "v": 4, "w": "1" }, { "u": 40, "v": 5, "w": "4" }, { "u": 40, "v": 6, "w": "3" }, { "u": 40, "v": 7, "w": "4" }, { "u": 40, "v": 8, "w": "2" }, { "u": 40, "v": 9, "w": "1" }, { "u": 40, "v": 10, "w": "2" }, { "u": 40, "v": 11, "w": "1" }, { "u": 40, "v": 12, "w": "2" }, { "u": 40, "v": 13, "w": "5" }, { "u": 40, "v": 14, "w": "3" }, { "u": 40, "v": 15, "w": "3" }, { "u": 40, "v": 16, "w": "3" }, { "u": 40, "v": 17, "w": "2" }, { "u": 40, "v": 18, "w": "2" }, { "u": 40, "v": 19, "w": "4" }, { "u": 40, "v": 20, "w": "4" }, { "u": 40, "v": 21, "w": "1" }, { "u": 40, "v": 22, "w": "2" }, { "u": 40, "v": 23, "w": "1" }, { "u": 40, "v": 24, "w": "2" }, { "u": 40, "v": 25, "w": "3" }, { "u": 40, "v": 26, "w": "2" }, { "u": 40, "v": 27, "w": "2" }, { "u": 40, "v": 28, "w": "1" }, { "u": 40, "v": 29, "w": "4" }, { "u": 40, "v": 30, "w": "1" }, { "u": 40, "v": 31, "w": "1" }, { "u": 40, "v": 32, "w": "1" }, { "u": 40, "v": 33, "w": "2" }, { "u": 40, "v": 34, "w": "3" }, { "u": 40, "v": 35, "w": "3" }, { "u": 40, "v": 36, "w": "3" }, { "u": 40, "v": 37, "w": "2" }, { "u": 40, "v": 38, "w": "2" }, { "u": 40, "v": 39, "w": "3" }, { "u": 40, "v": 41, "w": "2" }, { "u": 40, "v": 42, "w": "3" }, { "u": 40, "v": 43, "w": "2" }, { "u": 40, "v": 44, "w": "3" }, { "u": 40, "v": 45, "w": "3" }, { "u": 40, "v": 46, "w": "1" }, { "u": 40, "v": 47, "w": "1" }, { "u": 40, "v": 48, "w": "1" }, { "u": 40, "v": 49, "w": "1" }, { "u": 40, "v": 50, "w": "2" }, { "u": 40, "v": 51, "w": "1" }, { "u": 40, "v": 52, "w": "5" }, { "u": 40, "v": 53, "w": "2" }, { "u": 40, "v": 54, "w": "4" }, { "u": 40, "v": 55, "w": "2" }, { "u": 40, "v": 56, "w": "2" }, { "u": 40, "v": 57, "w": "4" }, { "u": 40, "v": 58, "w": "2" }, { "u": 41, "v": 1, "w": "3" }, { "u": 41, "v": 2, "w": "2" }, { "u": 41, "v": 3, "w": "2" }, { "u": 41, "v": 4, "w": "5" }, { "u": 41, "v": 5, "w": "3" }, { "u": 41, "v": 6, "w": "4" }, { "u": 41, "v": 7, "w": "4" }, { "u": 41, "v": 8, "w": "2" }, { "u": 41, "v": 9, "w": "2" }, { "u": 41, "v": 10, "w": "4" }, { "u": 41, "v": 11, "w": "2" }, { "u": 41, "v": 12, "w": "2" }, { "u": 41, "v": 13, "w": "2" }, { "u": 41, "v": 14, "w": "4" }, { "u": 41, "v": 15, "w": "4" }, { "u": 41, "v": 16, "w": "3" }, { "u": 41, "v": 17, "w": "3" }, { "u": 41, "v": 18, "w": "2" }, { "u": 41, "v": 19, "w": "5" }, { "u": 41, "v": 20, "w": "5" }, { "u": 41, "v": 21, "w": "2" }, { "u": 41, "v": 22, "w": "4" }, { "u": 41, "v": 23, "w": "5" }, { "u": 41, "v": 24, "w": "2" }, { "u": 41, "v": 25, "w": "3" }, { "u": 41, "v": 26, "w": "2" }, { "u": 41, "v": 27, "w": "2" }, { "u": 41, "v": 28, "w": "2" }, { "u": 41, "v": 29, "w": "3" }, { "u": 41, "v": 30, "w": "4" }, { "u": 41, "v": 31, "w": "3" }, { "u": 41, "v": 32, "w": "2" }, { "u": 41, "v": 33, "w": "4" }, { "u": 41, "v": 34, "w": "3" }, { "u": 41, "v": 35, "w": "4" }, { "u": 41, "v": 36, "w": "4" }, { "u": 41, "v": 37, "w": "3" }, { "u": 41, "v": 38, "w": "3" }, { "u": 41, "v": 39, "w": "3" }, { "u": 41, "v": 40, "w": "2" }, { "u": 41, "v": 42, "w": "2" }, { "u": 41, "v": 43, "w": "4" }, { "u": 41, "v": 44, "w": "3" }, { "u": 41, "v": 45, "w": "4" }, { "u": 41, "v": 46, "w": "2" }, { "u": 41, "v": 47, "w": "3" }, { "u": 41, "v": 48, "w": "2" }, { "u": 41, "v": 49, "w": "5" }, { "u": 41, "v": 50, "w": "3" }, { "u": 41, "v": 51, "w": "4" }, { "u": 41, "v": 52, "w": "3" }, { "u": 41, "v": 53, "w": "5" }, { "u": 41, "v": 54, "w": "2" }, { "u": 41, "v": 55, "w": "2" }, { "u": 41, "v": 56, "w": "4" }, { "u": 41, "v": 57, "w": "4" }, { "u": 41, "v": 58, "w": "5" }, { "u": 42, "v": 1, "w": "2" }, { "u": 42, "v": 2, "w": "1" }, { "u": 42, "v": 3, "w": "4" }, { "u": 42, "v": 4, "w": "1" }, { "u": 42, "v": 5, "w": "2" }, { "u": 42, "v": 6, "w": "3" }, { "u": 42, "v": 7, "w": "3" }, { "u": 42, "v": 8, "w": "2" }, { "u": 42, "v": 9, "w": "1" }, { "u": 42, "v": 10, "w": "1" }, { "u": 42, "v": 11, "w": "3" }, { "u": 42, "v": 12, "w": "1" }, { "u": 42, "v": 13, "w": "4" }, { "u": 42, "v": 14, "w": "4" }, { "u": 42, "v": 15, "w": "3" }, { "u": 42, "v": 16, "w": "5" }, { "u": 42, "v": 17, "w": "5" }, { "u": 42, "v": 18, "w": "1" }, { "u": 42, "v": 19, "w": "3" }, { "u": 42, "v": 20, "w": "3" }, { "u": 42, "v": 21, "w": "1" }, { "u": 42, "v": 22, "w": "2" }, { "u": 42, "v": 23, "w": "1" }, { "u": 42, "v": 24, "w": "1" }, { "u": 42, "v": 25, "w": "2" }, { "u": 42, "v": 26, "w": "2" }, { "u": 42, "v": 27, "w": "2" }, { "u": 42, "v": 28, "w": "1" }, { "u": 42, "v": 29, "w": "4" }, { "u": 42, "v": 30, "w": "3" }, { "u": 42, "v": 31, "w": "3" }, { "u": 42, "v": 32, "w": "1" }, { "u": 42, "v": 33, "w": "2" }, { "u": 42, "v": 34, "w": "2" }, { "u": 42, "v": 35, "w": "5" }, { "u": 42, "v": 36, "w": "1" }, { "u": 42, "v": 37, "w": "1" }, { "u": 42, "v": 38, "w": "4" }, { "u": 42, "v": 39, "w": "2" }, { "u": 42, "v": 40, "w": "1" }, { "u": 42, "v": 41, "w": "2" }, { "u": 42, "v": 43, "w": "5" }, { "u": 42, "v": 44, "w": "2" }, { "u": 42, "v": 45, "w": "1" }, { "u": 42, "v": 46, "w": "1" }, { "u": 42, "v": 47, "w": "2" }, { "u": 42, "v": 48, "w": "1" }, { "u": 42, "v": 49, "w": "2" }, { "u": 42, "v": 50, "w": "1" }, { "u": 42, "v": 51, "w": "1" }, { "u": 42, "v": 52, "w": "1" }, { "u": 42, "v": 53, "w": "4" }, { "u": 42, "v": 54, "w": "4" }, { "u": 42, "v": 55, "w": "2" }, { "u": 42, "v": 56, "w": "3" }, { "u": 42, "v": 57, "w": "3" }, { "u": 42, "v": 58, "w": "1" }, { "u": 43, "v": 1, "w": "2" }, { "u": 43, "v": 2, "w": "3" }, { "u": 43, "v": 3, "w": "4" }, { "u": 43, "v": 4, "w": "3" }, { "u": 43, "v": 5, "w": "2" }, { "u": 43, "v": 6, "w": "2" }, { "u": 43, "v": 7, "w": "2" }, { "u": 43, "v": 8, "w": "1" }, { "u": 43, "v": 9, "w": "3" }, { "u": 43, "v": 10, "w": "2" }, { "u": 43, "v": 11, "w": "2" }, { "u": 43, "v": 12, "w": "2" }, { "u": 43, "v": 13, "w": "2" }, { "u": 43, "v": 14, "w": "3" }, { "u": 43, "v": 15, "w": "2" }, { "u": 43, "v": 16, "w": "3" }, { "u": 43, "v": 17, "w": "2" }, { "u": 43, "v": 18, "w": "1" }, { "u": 43, "v": 19, "w": "4" }, { "u": 43, "v": 20, "w": "2" }, { "u": 43, "v": 21, "w": "1" }, { "u": 43, "v": 22, "w": "2" }, { "u": 43, "v": 23, "w": "2" }, { "u": 43, "v": 24, "w": "2" }, { "u": 43, "v": 25, "w": "5" }, { "u": 43, "v": 26, "w": "4" }, { "u": 43, "v": 27, "w": "2" }, { "u": 43, "v": 28, "w": "2" }, { "u": 43, "v": 29, "w": "3" }, { "u": 43, "v": 30, "w": "3" }, { "u": 43, "v": 31, "w": "2" }, { "u": 43, "v": 32, "w": "1" }, { "u": 43, "v": 33, "w": "2" }, { "u": 43, "v": 34, "w": "1" }, { "u": 43, "v": 35, "w": "5" }, { "u": 43, "v": 36, "w": "3" }, { "u": 43, "v": 37, "w": "2" }, { "u": 43, "v": 38, "w": "3" }, { "u": 43, "v": 39, "w": "3" }, { "u": 43, "v": 40, "w": "2" }, { "u": 43, "v": 41, "w": "3" }, { "u": 43, "v": 42, "w": "4" }, { "u": 43, "v": 44, "w": "1" }, { "u": 43, "v": 45, "w": "2" }, { "u": 43, "v": 46, "w": "1" }, { "u": 43, "v": 47, "w": "3" }, { "u": 43, "v": 48, "w": "1" }, { "u": 43, "v": 49, "w": "2" }, { "u": 43, "v": 50, "w": "1" }, { "u": 43, "v": 51, "w": "1" }, { "u": 43, "v": 52, "w": "2" }, { "u": 43, "v": 53, "w": "3" }, { "u": 43, "v": 54, "w": "4" }, { "u": 43, "v": 55, "w": "3" }, { "u": 43, "v": 56, "w": "3" }, { "u": 43, "v": 57, "w": "2" }, { "u": 43, "v": 58, "w": "1" }, { "u": 44, "v": 1, "w": "3" }, { "u": 44, "v": 2, "w": "2" }, { "u": 44, "v": 3, "w": "4" }, { "u": 44, "v": 4, "w": "4" }, { "u": 44, "v": 5, "w": "4" }, { "u": 44, "v": 6, "w": "3" }, { "u": 44, "v": 7, "w": "3" }, { "u": 44, "v": 8, "w": "2" }, { "u": 44, "v": 9, "w": "2" }, { "u": 44, "v": 10, "w": "1" }, { "u": 44, "v": 11, "w": "4" }, { "u": 44, "v": 12, "w": "2" }, { "u": 44, "v": 13, "w": "3" }, { "u": 44, "v": 14, "w": "4" }, { "u": 44, "v": 15, "w": "5" }, { "u": 44, "v": 16, "w": "3" }, { "u": 44, "v": 17, "w": "5" }, { "u": 44, "v": 18, "w": "5" }, { "u": 44, "v": 19, "w": "3" }, { "u": 44, "v": 20, "w": "3" }, { "u": 44, "v": 21, "w": "5" }, { "u": 44, "v": 22, "w": "4" }, { "u": 44, "v": 23, "w": "4" }, { "u": 44, "v": 24, "w": "2" }, { "u": 44, "v": 25, "w": "2" }, { "u": 44, "v": 26, "w": "2" }, { "u": 44, "v": 27, "w": "2" }, { "u": 44, "v": 28, "w": "4" }, { "u": 44, "v": 29, "w": "3" }, { "u": 44, "v": 30, "w": "2" }, { "u": 44, "v": 31, "w": "3" }, { "u": 44, "v": 32, "w": "2" }, { "u": 44, "v": 33, "w": "3" }, { "u": 44, "v": 34, "w": "2" }, { "u": 44, "v": 35, "w": "2" }, { "u": 44, "v": 36, "w": "3" }, { "u": 44, "v": 37, "w": "4" }, { "u": 44, "v": 38, "w": "3" }, { "u": 44, "v": 39, "w": "3" }, { "u": 44, "v": 40, "w": "2" }, { "u": 44, "v": 41, "w": "1" }, { "u": 44, "v": 42, "w": "3" }, { "u": 44, "v": 43, "w": "3" }, { "u": 44, "v": 45, "w": "3" }, { "u": 44, "v": 46, "w": "3" }, { "u": 44, "v": 47, "w": "5" }, { "u": 44, "v": 48, "w": "3" }, { "u": 44, "v": 49, "w": "4" }, { "u": 44, "v": 50, "w": "2" }, { "u": 44, "v": 51, "w": "1" }, { "u": 44, "v": 52, "w": "1" }, { "u": 44, "v": 53, "w": "3" }, { "u": 44, "v": 54, "w": "2" }, { "u": 44, "v": 55, "w": "2" }, { "u": 44, "v": 56, "w": "3" }, { "u": 44, "v": 57, "w": "4" }, { "u": 44, "v": 58, "w": "2" }, { "u": 45, "v": 1, "w": "2" }, { "u": 45, "v": 2, "w": "3" }, { "u": 45, "v": 3, "w": "4" }, { "u": 45, "v": 4, "w": "4" }, { "u": 45, "v": 5, "w": "5" }, { "u": 45, "v": 6, "w": "2" }, { "u": 45, "v": 7, "w": "1" }, { "u": 45, "v": 8, "w": "1" }, { "u": 45, "v": 9, "w": "1" }, { "u": 45, "v": 10, "w": "3" }, { "u": 45, "v": 11, "w": "4" }, { "u": 45, "v": 12, "w": "3" }, { "u": 45, "v": 13, "w": "2" }, { "u": 45, "v": 14, "w": "4" }, { "u": 45, "v": 15, "w": "4" }, { "u": 45, "v": 16, "w": "4" }, { "u": 45, "v": 17, "w": "2" }, { "u": 45, "v": 18, "w": "1" }, { "u": 45, "v": 19, "w": "1" }, { "u": 45, "v": 20, "w": "1" }, { "u": 45, "v": 21, "w": "1" }, { "u": 45, "v": 22, "w": "2" }, { "u": 45, "v": 23, "w": "4" }, { "u": 45, "v": 24, "w": "1" }, { "u": 45, "v": 25, "w": "2" }, { "u": 45, "v": 26, "w": "3" }, { "u": 45, "v": 27, "w": "3" }, { "u": 45, "v": 28, "w": "1" }, { "u": 45, "v": 29, "w": "1" }, { "u": 45, "v": 30, "w": "1" }, { "u": 45, "v": 31, "w": "3" }, { "u": 45, "v": 32, "w": "1" }, { "u": 45, "v": 33, "w": "1" }, { "u": 45, "v": 34, "w": "1" }, { "u": 45, "v": 35, "w": "1" }, { "u": 45, "v": 36, "w": "1" }, { "u": 45, "v": 37, "w": "1" }, { "u": 45, "v": 38, "w": "1" }, { "u": 45, "v": 39, "w": "5" }, { "u": 45, "v": 40, "w": "3" }, { "u": 45, "v": 41, "w": "2" }, { "u": 45, "v": 42, "w": "1" }, { "u": 45, "v": 43, "w": "1" }, { "u": 45, "v": 44, "w": "3" }, { "u": 45, "v": 46, "w": "1" }, { "u": 45, "v": 47, "w": "1" }, { "u": 45, "v": 48, "w": "1" }, { "u": 45, "v": 49, "w": "4" }, { "u": 45, "v": 50, "w": "1" }, { "u": 45, "v": 51, "w": "1" }, { "u": 45, "v": 52, "w": "1" }, { "u": 45, "v": 53, "w": "1" }, { "u": 45, "v": 54, "w": "1" }, { "u": 45, "v": 55, "w": "1" }, { "u": 45, "v": 56, "w": "1" }, { "u": 45, "v": 57, "w": "4" }, { "u": 45, "v": 58, "w": "1" }, { "u": 46, "v": 1, "w": "4" }, { "u": 46, "v": 2, "w": "2" }, { "u": 46, "v": 3, "w": "3" }, { "u": 46, "v": 4, "w": "1" }, { "u": 46, "v": 5, "w": "1" }, { "u": 46, "v": 6, "w": "2" }, { "u": 46, "v": 7, "w": "3" }, { "u": 46, "v": 8, "w": "3" }, { "u": 46, "v": 9, "w": "3" }, { "u": 46, "v": 10, "w": "4" }, { "u": 46, "v": 11, "w": "4" }, { "u": 46, "v": 12, "w": "3" }, { "u": 46, "v": 13, "w": "2" }, { "u": 46, "v": 14, "w": "3" }, { "u": 46, "v": 15, "w": "3" }, { "u": 46, "v": 16, "w": "3" }, { "u": 46, "v": 17, "w": "2" }, { "u": 46, "v": 18, "w": "4" }, { "u": 46, "v": 19, "w": "3" }, { "u": 46, "v": 20, "w": "4" }, { "u": 46, "v": 21, "w": "4" }, { "u": 46, "v": 22, "w": "3" }, { "u": 46, "v": 23, "w": "3" }, { "u": 46, "v": 24, "w": "2" }, { "u": 46, "v": 25, "w": "1" }, { "u": 46, "v": 26, "w": "1" }, { "u": 46, "v": 27, "w": "3" }, { "u": 46, "v": 28, "w": "1" }, { "u": 46, "v": 29, "w": "4" }, { "u": 46, "v": 30, "w": "3" }, { "u": 46, "v": 31, "w": "4" }, { "u": 46, "v": 32, "w": "5" }, { "u": 46, "v": 33, "w": "1" }, { "u": 46, "v": 34, "w": "2" }, { "u": 46, "v": 35, "w": "3" }, { "u": 46, "v": 36, "w": "4" }, { "u": 46, "v": 37, "w": "1" }, { "u": 46, "v": 38, "w": "2" }, { "u": 46, "v": 39, "w": "3" }, { "u": 46, "v": 40, "w": "2" }, { "u": 46, "v": 41, "w": "1" }, { "u": 46, "v": 42, "w": "1" }, { "u": 46, "v": 43, "w": "1" }, { "u": 46, "v": 44, "w": "4" }, { "u": 46, "v": 45, "w": "1" }, { "u": 46, "v": 47, "w": "1" }, { "u": 46, "v": 48, "w": "5" }, { "u": 46, "v": 49, "w": "3" }, { "u": 46, "v": 50, "w": "3" }, { "u": 46, "v": 51, "w": "1" }, { "u": 46, "v": 52, "w": "2" }, { "u": 46, "v": 53, "w": "1" }, { "u": 46, "v": 54, "w": "3" }, { "u": 46, "v": 55, "w": "3" }, { "u": 46, "v": 56, "w": "4" }, { "u": 46, "v": 57, "w": "4" }, { "u": 46, "v": 58, "w": "1" }, { "u": 47, "v": 1, "w": "3" }, { "u": 47, "v": 2, "w": "3" }, { "u": 47, "v": 3, "w": "3" }, { "u": 47, "v": 4, "w": "3" }, { "u": 47, "v": 5, "w": "4" }, { "u": 47, "v": 6, "w": "4" }, { "u": 47, "v": 7, "w": "2" }, { "u": 47, "v": 8, "w": "2" }, { "u": 47, "v": 9, "w": "5" }, { "u": 47, "v": 10, "w": "1" }, { "u": 47, "v": 11, "w": "5" }, { "u": 47, "v": 12, "w": "3" }, { "u": 47, "v": 13, "w": "2" }, { "u": 47, "v": 14, "w": "2" }, { "u": 47, "v": 15, "w": "3" }, { "u": 47, "v": 16, "w": "3" }, { "u": 47, "v": 17, "w": "3" }, { "u": 47, "v": 18, "w": "1" }, { "u": 47, "v": 19, "w": "5" }, { "u": 47, "v": 20, "w": "3" }, { "u": 47, "v": 21, "w": "1" }, { "u": 47, "v": 22, "w": "3" }, { "u": 47, "v": 23, "w": "5" }, { "u": 47, "v": 24, "w": "2" }, { "u": 47, "v": 25, "w": "3" }, { "u": 47, "v": 26, "w": "3" }, { "u": 47, "v": 27, "w": "1" }, { "u": 47, "v": 28, "w": "2" }, { "u": 47, "v": 29, "w": "3" }, { "u": 47, "v": 30, "w": "5" }, { "u": 47, "v": 31, "w": "3" }, { "u": 47, "v": 32, "w": "2" }, { "u": 47, "v": 33, "w": "2" }, { "u": 47, "v": 34, "w": "2" }, { "u": 47, "v": 35, "w": "4" }, { "u": 47, "v": 36, "w": "3" }, { "u": 47, "v": 37, "w": "4" }, { "u": 47, "v": 38, "w": "3" }, { "u": 47, "v": 39, "w": "5" }, { "u": 47, "v": 40, "w": "2" }, { "u": 47, "v": 41, "w": "3" }, { "u": 47, "v": 42, "w": "2" }, { "u": 47, "v": 43, "w": "3" }, { "u": 47, "v": 44, "w": "5" }, { "u": 47, "v": 45, "w": "2" }, { "u": 47, "v": 46, "w": "1" }, { "u": 47, "v": 48, "w": "1" }, { "u": 47, "v": 49, "w": "4" }, { "u": 47, "v": 50, "w": "2" }, { "u": 47, "v": 51, "w": "5" }, { "u": 47, "v": 52, "w": "2" }, { "u": 47, "v": 53, "w": "3" }, { "u": 47, "v": 54, "w": "1" }, { "u": 47, "v": 55, "w": "4" }, { "u": 47, "v": 56, "w": "3" }, { "u": 47, "v": 57, "w": "5" }, { "u": 47, "v": 58, "w": "1" }, { "u": 48, "v": 1, "w": "4" }, { "u": 48, "v": 2, "w": "4" }, { "u": 48, "v": 3, "w": "3" }, { "u": 48, "v": 4, "w": "3" }, { "u": 48, "v": 5, "w": "3" }, { "u": 48, "v": 6, "w": "3" }, { "u": 48, "v": 7, "w": "3" }, { "u": 48, "v": 8, "w": "3" }, { "u": 48, "v": 9, "w": "3" }, { "u": 48, "v": 10, "w": "4" }, { "u": 48, "v": 11, "w": "5" }, { "u": 48, "v": 12, "w": "3" }, { "u": 48, "v": 13, "w": "3" }, { "u": 48, "v": 14, "w": "2" }, { "u": 48, "v": 15, "w": "3" }, { "u": 48, "v": 16, "w": "4" }, { "u": 48, "v": 17, "w": "3" }, { "u": 48, "v": 18, "w": "4" }, { "u": 48, "v": 19, "w": "1" }, { "u": 48, "v": 20, "w": "4" }, { "u": 48, "v": 21, "w": "5" }, { "u": 48, "v": 22, "w": "3" }, { "u": 48, "v": 23, "w": "3" }, { "u": 48, "v": 24, "w": "1" }, { "u": 48, "v": 25, "w": "3" }, { "u": 48, "v": 26, "w": "4" }, { "u": 48, "v": 27, "w": "4" }, { "u": 48, "v": 28, "w": "2" }, { "u": 48, "v": 29, "w": "3" }, { "u": 48, "v": 30, "w": "3" }, { "u": 48, "v": 31, "w": "4" }, { "u": 48, "v": 32, "w": "5" }, { "u": 48, "v": 33, "w": "2" }, { "u": 48, "v": 34, "w": "2" }, { "u": 48, "v": 35, "w": "2" }, { "u": 48, "v": 36, "w": "3" }, { "u": 48, "v": 37, "w": "2" }, { "u": 48, "v": 38, "w": "2" }, { "u": 48, "v": 39, "w": "3" }, { "u": 48, "v": 40, "w": "2" }, { "u": 48, "v": 41, "w": "2" }, { "u": 48, "v": 42, "w": "3" }, { "u": 48, "v": 43, "w": "2" }, { "u": 48, "v": 44, "w": "3" }, { "u": 48, "v": 45, "w": "2" }, { "u": 48, "v": 46, "w": "5" }, { "u": 48, "v": 47, "w": "2" }, { "u": 48, "v": 49, "w": "3" }, { "u": 48, "v": 50, "w": "4" }, { "u": 48, "v": 51, "w": "1" }, { "u": 48, "v": 52, "w": "2" }, { "u": 48, "v": 53, "w": "2" }, { "u": 48, "v": 54, "w": "3" }, { "u": 48, "v": 55, "w": "3" }, { "u": 48, "v": 56, "w": "3" }, { "u": 48, "v": 57, "w": "3" }, { "u": 48, "v": 58, "w": "2" }, { "u": 49, "v": 1, "w": "4" }, { "u": 49, "v": 2, "w": "4" }, { "u": 49, "v": 3, "w": "2" }, { "u": 49, "v": 4, "w": "5" }, { "u": 49, "v": 5, "w": "4" }, { "u": 49, "v": 6, "w": "4" }, { "u": 49, "v": 7, "w": "3" }, { "u": 49, "v": 8, "w": "2" }, { "u": 49, "v": 9, "w": "4" }, { "u": 49, "v": 10, "w": "3" }, { "u": 49, "v": 11, "w": "3" }, { "u": 49, "v": 12, "w": "4" }, { "u": 49, "v": 13, "w": "1" }, { "u": 49, "v": 14, "w": "4" }, { "u": 49, "v": 15, "w": "4" }, { "u": 49, "v": 16, "w": "4" }, { "u": 49, "v": 17, "w": "4" }, { "u": 49, "v": 18, "w": "1" }, { "u": 49, "v": 19, "w": "5" }, { "u": 49, "v": 20, "w": "4" }, { "u": 49, "v": 21, "w": "1" }, { "u": 49, "v": 22, "w": "3" }, { "u": 49, "v": 23, "w": "5" }, { "u": 49, "v": 24, "w": "3" }, { "u": 49, "v": 25, "w": "3" }, { "u": 49, "v": 26, "w": "3" }, { "u": 49, "v": 27, "w": "3" }, { "u": 49, "v": 28, "w": "2" }, { "u": 49, "v": 29, "w": "3" }, { "u": 49, "v": 30, "w": "2" }, { "u": 49, "v": 31, "w": "4" }, { "u": 49, "v": 32, "w": "3" }, { "u": 49, "v": 33, "w": "4" }, { "u": 49, "v": 34, "w": "5" }, { "u": 49, "v": 35, "w": "4" }, { "u": 49, "v": 36, "w": "3" }, { "u": 49, "v": 37, "w": "4" }, { "u": 49, "v": 38, "w": "3" }, { "u": 49, "v": 39, "w": "4" }, { "u": 49, "v": 40, "w": "3" }, { "u": 49, "v": 41, "w": "5" }, { "u": 49, "v": 42, "w": "2" }, { "u": 49, "v": 43, "w": "2" }, { "u": 49, "v": 44, "w": "5" }, { "u": 49, "v": 45, "w": "5" }, { "u": 49, "v": 46, "w": "2" }, { "u": 49, "v": 47, "w": "4" }, { "u": 49, "v": 48, "w": "2" }, { "u": 49, "v": 50, "w": "5" }, { "u": 49, "v": 51, "w": "2" }, { "u": 49, "v": 52, "w": "3" }, { "u": 49, "v": 53, "w": "4" }, { "u": 49, "v": 54, "w": "2" }, { "u": 49, "v": 55, "w": "2" }, { "u": 49, "v": 56, "w": "3" }, { "u": 49, "v": 57, "w": "4" }, { "u": 49, "v": 58, "w": "5" }, { "u": 50, "v": 1, "w": "5" }, { "u": 50, "v": 2, "w": "4" }, { "u": 50, "v": 3, "w": "2" }, { "u": 50, "v": 4, "w": "5" }, { "u": 50, "v": 5, "w": "3" }, { "u": 50, "v": 6, "w": "4" }, { "u": 50, "v": 7, "w": "4" }, { "u": 50, "v": 8, "w": "4" }, { "u": 50, "v": 9, "w": "4" }, { "u": 50, "v": 10, "w": "5" }, { "u": 50, "v": 11, "w": "5" }, { "u": 50, "v": 12, "w": "3" }, { "u": 50, "v": 13, "w": "3" }, { "u": 50, "v": 14, "w": "3" }, { "u": 50, "v": 15, "w": "4" }, { "u": 50, "v": 16, "w": "3" }, { "u": 50, "v": 17, "w": "2" }, { "u": 50, "v": 18, "w": "2" }, { "u": 50, "v": 19, "w": "2" }, { "u": 50, "v": 20, "w": "5" }, { "u": 50, "v": 21, "w": "4" }, { "u": 50, "v": 22, "w": "3" }, { "u": 50, "v": 23, "w": "3" }, { "u": 50, "v": 24, "w": "4" }, { "u": 50, "v": 25, "w": "3" }, { "u": 50, "v": 26, "w": "3" }, { "u": 50, "v": 27, "w": "4" }, { "u": 50, "v": 28, "w": "3" }, { "u": 50, "v": 29, "w": "2" }, { "u": 50, "v": 30, "w": "3" }, { "u": 50, "v": 31, "w": "3" }, { "u": 50, "v": 32, "w": "2" }, { "u": 50, "v": 33, "w": "3" }, { "u": 50, "v": 34, "w": "5" }, { "u": 50, "v": 35, "w": "3" }, { "u": 50, "v": 36, "w": "2" }, { "u": 50, "v": 37, "w": "3" }, { "u": 50, "v": 38, "w": "3" }, { "u": 50, "v": 39, "w": "3" }, { "u": 50, "v": 40, "w": "3" }, { "u": 50, "v": 41, "w": "2" }, { "u": 50, "v": 42, "w": "3" }, { "u": 50, "v": 43, "w": "3" }, { "u": 50, "v": 44, "w": "4" }, { "u": 50, "v": 45, "w": "3" }, { "u": 50, "v": 46, "w": "5" }, { "u": 50, "v": 47, "w": "3" }, { "u": 50, "v": 48, "w": "5" }, { "u": 50, "v": 49, "w": "4" }, { "u": 50, "v": 51, "w": "1" }, { "u": 50, "v": 52, "w": "3" }, { "u": 50, "v": 53, "w": "3" }, { "u": 50, "v": 54, "w": "1" }, { "u": 50, "v": 55, "w": "3" }, { "u": 50, "v": 56, "w": "5" }, { "u": 50, "v": 57, "w": "5" }, { "u": 50, "v": 58, "w": "5" }, { "u": 51, "v": 1, "w": "2" }, { "u": 51, "v": 2, "w": "2" }, { "u": 51, "v": 3, "w": "2" }, { "u": 51, "v": 4, "w": "3" }, { "u": 51, "v": 5, "w": "4" }, { "u": 51, "v": 6, "w": "2" }, { "u": 51, "v": 7, "w": "2" }, { "u": 51, "v": 8, "w": "2" }, { "u": 51, "v": 9, "w": "2" }, { "u": 51, "v": 10, "w": "5" }, { "u": 51, "v": 11, "w": "2" }, { "u": 51, "v": 12, "w": "2" }, { "u": 51, "v": 13, "w": "3" }, { "u": 51, "v": 14, "w": "3" }, { "u": 51, "v": 15, "w": "2" }, { "u": 51, "v": 16, "w": "2" }, { "u": 51, "v": 17, "w": "2" }, { "u": 51, "v": 18, "w": "2" }, { "u": 51, "v": 19, "w": "3" }, { "u": 51, "v": 20, "w": "1" }, { "u": 51, "v": 21, "w": "1" }, { "u": 51, "v": 22, "w": "1" }, { "u": 51, "v": 23, "w": "5" }, { "u": 51, "v": 24, "w": "1" }, { "u": 51, "v": 25, "w": "1" }, { "u": 51, "v": 26, "w": "4" }, { "u": 51, "v": 27, "w": "2" }, { "u": 51, "v": 28, "w": "1" }, { "u": 51, "v": 29, "w": "2" }, { "u": 51, "v": 30, "w": "2" }, { "u": 51, "v": 31, "w": "2" }, { "u": 51, "v": 32, "w": "2" }, { "u": 51, "v": 33, "w": "2" }, { "u": 51, "v": 34, "w": "1" }, { "u": 51, "v": 35, "w": "3" }, { "u": 51, "v": 36, "w": "1" }, { "u": 51, "v": 37, "w": "5" }, { "u": 51, "v": 38, "w": "2" }, { "u": 51, "v": 39, "w": "5" }, { "u": 51, "v": 40, "w": "2" }, { "u": 51, "v": 41, "w": "3" }, { "u": 51, "v": 42, "w": "1" }, { "u": 51, "v": 43, "w": "2" }, { "u": 51, "v": 44, "w": "2" }, { "u": 51, "v": 45, "w": "2" }, { "u": 51, "v": 46, "w": "1" }, { "u": 51, "v": 47, "w": "5" }, { "u": 51, "v": 48, "w": "1" }, { "u": 51, "v": 49, "w": "3" }, { "u": 51, "v": 50, "w": "2" }, { "u": 51, "v": 52, "w": "1" }, { "u": 51, "v": 53, "w": "2" }, { "u": 51, "v": 54, "w": "2" }, { "u": 51, "v": 55, "w": "2" }, { "u": 51, "v": 56, "w": "1" }, { "u": 51, "v": 57, "w": "2" }, { "u": 51, "v": 58, "w": "1" }, { "u": 52, "v": 1, "w": "2" }, { "u": 52, "v": 2, "w": "2" }, { "u": 52, "v": 3, "w": "3" }, { "u": 52, "v": 4, "w": "3" }, { "u": 52, "v": 5, "w": "2" }, { "u": 52, "v": 6, "w": "2" }, { "u": 52, "v": 7, "w": "4" }, { "u": 52, "v": 8, "w": "2" }, { "u": 52, "v": 9, "w": "4" }, { "u": 52, "v": 10, "w": "2" }, { "u": 52, "v": 11, "w": "3" }, { "u": 52, "v": 12, "w": "4" }, { "u": 52, "v": 13, "w": "2" }, { "u": 52, "v": 14, "w": "3" }, { "u": 52, "v": 15, "w": "3" }, { "u": 52, "v": 16, "w": "2" }, { "u": 52, "v": 17, "w": "2" }, { "u": 52, "v": 18, "w": "3" }, { "u": 52, "v": 19, "w": "3" }, { "u": 52, "v": 20, "w": "2" }, { "u": 52, "v": 21, "w": "4" }, { "u": 52, "v": 22, "w": "4" }, { "u": 52, "v": 23, "w": "3" }, { "u": 52, "v": 24, "w": "3" }, { "u": 52, "v": 25, "w": "3" }, { "u": 52, "v": 26, "w": "4" }, { "u": 52, "v": 27, "w": "3" }, { "u": 52, "v": 28, "w": "2" }, { "u": 52, "v": 29, "w": "2" }, { "u": 52, "v": 30, "w": "2" }, { "u": 52, "v": 31, "w": "2" }, { "u": 52, "v": 32, "w": "2" }, { "u": 52, "v": 33, "w": "3" }, { "u": 52, "v": 34, "w": "3" }, { "u": 52, "v": 35, "w": "3" }, { "u": 52, "v": 36, "w": "2" }, { "u": 52, "v": 37, "w": "3" }, { "u": 52, "v": 38, "w": "2" }, { "u": 52, "v": 39, "w": "2" }, { "u": 52, "v": 40, "w": "4" }, { "u": 52, "v": 41, "w": "2" }, { "u": 52, "v": 42, "w": "2" }, { "u": 52, "v": 43, "w": "3" }, { "u": 52, "v": 44, "w": "3" }, { "u": 52, "v": 45, "w": "3" }, { "u": 52, "v": 46, "w": "3" }, { "u": 52, "v": 47, "w": "3" }, { "u": 52, "v": 48, "w": "3" }, { "u": 52, "v": 49, "w": "3" }, { "u": 52, "v": 50, "w": "3" }, { "u": 52, "v": 51, "w": "2" }, { "u": 52, "v": 53, "w": "3" }, { "u": 52, "v": 54, "w": "3" }, { "u": 52, "v": 55, "w": "2" }, { "u": 52, "v": 56, "w": "3" }, { "u": 52, "v": 57, "w": "4" }, { "u": 52, "v": 58, "w": "3" }, { "u": 53, "v": 1, "w": "2" }, { "u": 53, "v": 2, "w": "2" }, { "u": 53, "v": 3, "w": "3" }, { "u": 53, "v": 4, "w": "5" }, { "u": 53, "v": 5, "w": "2" }, { "u": 53, "v": 6, "w": "5" }, { "u": 53, "v": 7, "w": "5" }, { "u": 53, "v": 8, "w": "1" }, { "u": 53, "v": 9, "w": "2" }, { "u": 53, "v": 10, "w": "1" }, { "u": 53, "v": 11, "w": "3" }, { "u": 53, "v": 12, "w": "2" }, { "u": 53, "v": 13, "w": "2" }, { "u": 53, "v": 14, "w": "4" }, { "u": 53, "v": 15, "w": "3" }, { "u": 53, "v": 16, "w": "4" }, { "u": 53, "v": 17, "w": "4" }, { "u": 53, "v": 18, "w": "1" }, { "u": 53, "v": 19, "w": "4" }, { "u": 53, "v": 20, "w": "5" }, { "u": 53, "v": 21, "w": "1" }, { "u": 53, "v": 22, "w": "1" }, { "u": 53, "v": 23, "w": "4" }, { "u": 53, "v": 24, "w": "1" }, { "u": 53, "v": 25, "w": "3" }, { "u": 53, "v": 26, "w": "1" }, { "u": 53, "v": 27, "w": "1" }, { "u": 53, "v": 28, "w": "5" }, { "u": 53, "v": 29, "w": "3" }, { "u": 53, "v": 30, "w": "5" }, { "u": 53, "v": 31, "w": "2" }, { "u": 53, "v": 32, "w": "2" }, { "u": 53, "v": 33, "w": "5" }, { "u": 53, "v": 34, "w": "4" }, { "u": 53, "v": 35, "w": "3" }, { "u": 53, "v": 36, "w": "1" }, { "u": 53, "v": 37, "w": "1" }, { "u": 53, "v": 38, "w": "3" }, { "u": 53, "v": 39, "w": "2" }, { "u": 53, "v": 40, "w": "1" }, { "u": 53, "v": 41, "w": "4" }, { "u": 53, "v": 42, "w": "4" }, { "u": 53, "v": 43, "w": "3" }, { "u": 53, "v": 44, "w": "1" }, { "u": 53, "v": 45, "w": "1" }, { "u": 53, "v": 46, "w": "1" }, { "u": 53, "v": 47, "w": "1" }, { "u": 53, "v": 48, "w": "1" }, { "u": 53, "v": 49, "w": "4" }, { "u": 53, "v": 50, "w": "2" }, { "u": 53, "v": 51, "w": "1" }, { "u": 53, "v": 52, "w": "1" }, { "u": 53, "v": 54, "w": "3" }, { "u": 53, "v": 55, "w": "1" }, { "u": 53, "v": 56, "w": "2" }, { "u": 53, "v": 57, "w": "3" }, { "u": 53, "v": 58, "w": "4" }, { "u": 54, "v": 1, "w": "2" }, { "u": 54, "v": 2, "w": "3" }, { "u": 54, "v": 3, "w": "5" }, { "u": 54, "v": 4, "w": "3" }, { "u": 54, "v": 5, "w": "3" }, { "u": 54, "v": 6, "w": "5" }, { "u": 54, "v": 7, "w": "3" }, { "u": 54, "v": 8, "w": "2" }, { "u": 54, "v": 9, "w": "4" }, { "u": 54, "v": 10, "w": "3" }, { "u": 54, "v": 11, "w": "2" }, { "u": 54, "v": 12, "w": "4" }, { "u": 54, "v": 13, "w": "4" }, { "u": 54, "v": 14, "w": "2" }, { "u": 54, "v": 15, "w": "2" }, { "u": 54, "v": 16, "w": "3" }, { "u": 54, "v": 17, "w": "3" }, { "u": 54, "v": 18, "w": "1" }, { "u": 54, "v": 19, "w": "2" }, { "u": 54, "v": 20, "w": "5" }, { "u": 54, "v": 21, "w": "2" }, { "u": 54, "v": 22, "w": "5" }, { "u": 54, "v": 23, "w": "2" }, { "u": 54, "v": 24, "w": "1" }, { "u": 54, "v": 25, "w": "5" }, { "u": 54, "v": 26, "w": "2" }, { "u": 54, "v": 27, "w": "4" }, { "u": 54, "v": 28, "w": "3" }, { "u": 54, "v": 29, "w": "5" }, { "u": 54, "v": 30, "w": "3" }, { "u": 54, "v": 31, "w": "2" }, { "u": 54, "v": 32, "w": "1" }, { "u": 54, "v": 33, "w": "4" }, { "u": 54, "v": 34, "w": "2" }, { "u": 54, "v": 35, "w": "5" }, { "u": 54, "v": 36, "w": "3" }, { "u": 54, "v": 37, "w": "2" }, { "u": 54, "v": 38, "w": "4" }, { "u": 54, "v": 39, "w": "3" }, { "u": 54, "v": 40, "w": "4" }, { "u": 54, "v": 41, "w": "2" }, { "u": 54, "v": 42, "w": "4" }, { "u": 54, "v": 43, "w": "4" }, { "u": 54, "v": 44, "w": "3" }, { "u": 54, "v": 45, "w": "3" }, { "u": 54, "v": 46, "w": "2" }, { "u": 54, "v": 47, "w": "2" }, { "u": 54, "v": 48, "w": "2" }, { "u": 54, "v": 49, "w": "3" }, { "u": 54, "v": 50, "w": "4" }, { "u": 54, "v": 51, "w": "2" }, { "u": 54, "v": 52, "w": "2" }, { "u": 54, "v": 53, "w": "4" }, { "u": 54, "v": 55, "w": "3" }, { "u": 54, "v": 56, "w": "4" }, { "u": 54, "v": 57, "w": "5" }, { "u": 54, "v": 58, "w": "3" }, { "u": 55, "v": 1, "w": "2" }, { "u": 55, "v": 2, "w": "2" }, { "u": 55, "v": 3, "w": "3" }, { "u": 55, "v": 4, "w": "2" }, { "u": 55, "v": 5, "w": "2" }, { "u": 55, "v": 6, "w": "3" }, { "u": 55, "v": 7, "w": "4" }, { "u": 55, "v": 8, "w": "2" }, { "u": 55, "v": 9, "w": "2" }, { "u": 55, "v": 10, "w": "3" }, { "u": 55, "v": 11, "w": "4" }, { "u": 55, "v": 12, "w": "2" }, { "u": 55, "v": 13, "w": "3" }, { "u": 55, "v": 14, "w": "3" }, { "u": 55, "v": 15, "w": "3" }, { "u": 55, "v": 16, "w": "2" }, { "u": 55, "v": 17, "w": "2" }, { "u": 55, "v": 18, "w": "4" }, { "u": 55, "v": 19, "w": "1" }, { "u": 55, "v": 20, "w": "5" }, { "u": 55, "v": 21, "w": "4" }, { "u": 55, "v": 22, "w": "3" }, { "u": 55, "v": 23, "w": "2" }, { "u": 55, "v": 24, "w": "2" }, { "u": 55, "v": 25, "w": "5" }, { "u": 55, "v": 26, "w": "1" }, { "u": 55, "v": 27, "w": "3" }, { "u": 55, "v": 28, "w": "1" }, { "u": 55, "v": 29, "w": "4" }, { "u": 55, "v": 30, "w": "2" }, { "u": 55, "v": 31, "w": "4" }, { "u": 55, "v": 32, "w": "1" }, { "u": 55, "v": 33, "w": "3" }, { "u": 55, "v": 34, "w": "3" }, { "u": 55, "v": 35, "w": "3" }, { "u": 55, "v": 36, "w": "2" }, { "u": 55, "v": 37, "w": "1" }, { "u": 55, "v": 38, "w": "2" }, { "u": 55, "v": 39, "w": "1" }, { "u": 55, "v": 40, "w": "1" }, { "u": 55, "v": 41, "w": "1" }, { "u": 55, "v": 42, "w": "2" }, { "u": 55, "v": 43, "w": "2" }, { "u": 55, "v": 44, "w": "2" }, { "u": 55, "v": 45, "w": "2" }, { "u": 55, "v": 46, "w": "3" }, { "u": 55, "v": 47, "w": "4" }, { "u": 55, "v": 48, "w": "2" }, { "u": 55, "v": 49, "w": "2" }, { "u": 55, "v": 50, "w": "4" }, { "u": 55, "v": 51, "w": "1" }, { "u": 55, "v": 52, "w": "1" }, { "u": 55, "v": 53, "w": "2" }, { "u": 55, "v": 54, "w": "5" }, { "u": 55, "v": 56, "w": "2" }, { "u": 55, "v": 57, "w": "2" }, { "u": 55, "v": 58, "w": "2" }, { "u": 56, "v": 1, "w": "3" }, { "u": 56, "v": 2, "w": "1" }, { "u": 56, "v": 3, "w": "3" }, { "u": 56, "v": 4, "w": "4" }, { "u": 56, "v": 5, "w": "2" }, { "u": 56, "v": 6, "w": "5" }, { "u": 56, "v": 7, "w": "5" }, { "u": 56, "v": 8, "w": "3" }, { "u": 56, "v": 9, "w": "5" }, { "u": 56, "v": 10, "w": "3" }, { "u": 56, "v": 11, "w": "4" }, { "u": 56, "v": 12, "w": "5" }, { "u": 56, "v": 13, "w": "2" }, { "u": 56, "v": 14, "w": "4" }, { "u": 56, "v": 15, "w": "5" }, { "u": 56, "v": 16, "w": "5" }, { "u": 56, "v": 17, "w": "5" }, { "u": 56, "v": 18, "w": "1" }, { "u": 56, "v": 19, "w": "5" }, { "u": 56, "v": 20, "w": "3" }, { "u": 56, "v": 21, "w": "3" }, { "u": 56, "v": 22, "w": "3" }, { "u": 56, "v": 23, "w": "3" }, { "u": 56, "v": 24, "w": "3" }, { "u": 56, "v": 25, "w": "4" }, { "u": 56, "v": 26, "w": "2" }, { "u": 56, "v": 27, "w": "4" }, { "u": 56, "v": 28, "w": "1" }, { "u": 56, "v": 29, "w": "4" }, { "u": 56, "v": 30, "w": "4" }, { "u": 56, "v": 31, "w": "3" }, { "u": 56, "v": 32, "w": "3" }, { "u": 56, "v": 33, "w": "4" }, { "u": 56, "v": 34, "w": "4" }, { "u": 56, "v": 35, "w": "5" }, { "u": 56, "v": 36, "w": "5" }, { "u": 56, "v": 37, "w": "3" }, { "u": 56, "v": 38, "w": "1" }, { "u": 56, "v": 39, "w": "2" }, { "u": 56, "v": 40, "w": "4" }, { "u": 56, "v": 41, "w": "4" }, { "u": 56, "v": 42, "w": "3" }, { "u": 56, "v": 43, "w": "4" }, { "u": 56, "v": 44, "w": "4" }, { "u": 56, "v": 45, "w": "2" }, { "u": 56, "v": 46, "w": "5" }, { "u": 56, "v": 47, "w": "3" }, { "u": 56, "v": 48, "w": "5" }, { "u": 56, "v": 49, "w": "3" }, { "u": 56, "v": 50, "w": "5" }, { "u": 56, "v": 51, "w": "1" }, { "u": 56, "v": 52, "w": "4" }, { "u": 56, "v": 53, "w": "3" }, { "u": 56, "v": 54, "w": "5" }, { "u": 56, "v": 55, "w": "4" }, { "u": 56, "v": 57, "w": "5" }, { "u": 56, "v": 58, "w": "5" }, { "u": 57, "v": 1, "w": "2" }, { "u": 57, "v": 2, "w": "1" }, { "u": 57, "v": 3, "w": "5" }, { "u": 57, "v": 4, "w": "5" }, { "u": 57, "v": 5, "w": "5" }, { "u": 57, "v": 6, "w": "3" }, { "u": 57, "v": 7, "w": "4" }, { "u": 57, "v": 8, "w": "1" }, { "u": 57, "v": 9, "w": "4" }, { "u": 57, "v": 10, "w": "1" }, { "u": 57, "v": 11, "w": "5" }, { "u": 57, "v": 12, "w": "2" }, { "u": 57, "v": 13, "w": "2" }, { "u": 57, "v": 14, "w": "3" }, { "u": 57, "v": 15, "w": "3" }, { "u": 57, "v": 16, "w": "4" }, { "u": 57, "v": 17, "w": "3" }, { "u": 57, "v": 18, "w": "1" }, { "u": 57, "v": 19, "w": "2" }, { "u": 57, "v": 20, "w": "5" }, { "u": 57, "v": 21, "w": "3" }, { "u": 57, "v": 22, "w": "2" }, { "u": 57, "v": 23, "w": "2" }, { "u": 57, "v": 24, "w": "1" }, { "u": 57, "v": 25, "w": "1" }, { "u": 57, "v": 26, "w": "1" }, { "u": 57, "v": 27, "w": "4" }, { "u": 57, "v": 28, "w": "1" }, { "u": 57, "v": 29, "w": "5" }, { "u": 57, "v": 30, "w": "5" }, { "u": 57, "v": 31, "w": "1" }, { "u": 57, "v": 32, "w": "1" }, { "u": 57, "v": 33, "w": "3" }, { "u": 57, "v": 34, "w": "2" }, { "u": 57, "v": 35, "w": "5" }, { "u": 57, "v": 36, "w": "5" }, { "u": 57, "v": 37, "w": "1" }, { "u": 57, "v": 38, "w": "2" }, { "u": 57, "v": 39, "w": "3" }, { "u": 57, "v": 40, "w": "2" }, { "u": 57, "v": 41, "w": "1" }, { "u": 57, "v": 42, "w": "3" }, { "u": 57, "v": 43, "w": "2" }, { "u": 57, "v": 44, "w": "3" }, { "u": 57, "v": 45, "w": "4" }, { "u": 57, "v": 46, "w": "2" }, { "u": 57, "v": 47, "w": "5" }, { "u": 57, "v": 48, "w": "1" }, { "u": 57, "v": 49, "w": "3" }, { "u": 57, "v": 50, "w": "2" }, { "u": 57, "v": 51, "w": "1" }, { "u": 57, "v": 52, "w": "2" }, { "u": 57, "v": 53, "w": "3" }, { "u": 57, "v": 54, "w": "3" }, { "u": 57, "v": 55, "w": "1" }, { "u": 57, "v": 56, "w": "5" }, { "u": 57, "v": 58, "w": "5" }, { "u": 58, "v": 1, "w": "3" }, { "u": 58, "v": 2, "w": "2" }, { "u": 58, "v": 3, "w": "2" }, { "u": 58, "v": 4, "w": "5" }, { "u": 58, "v": 5, "w": "2" }, { "u": 58, "v": 6, "w": "4" }, { "u": 58, "v": 7, "w": "2" }, { "u": 58, "v": 8, "w": "3" }, { "u": 58, "v": 9, "w": "3" }, { "u": 58, "v": 10, "w": "3" }, { "u": 58, "v": 11, "w": "3" }, { "u": 58, "v": 12, "w": "4" }, { "u": 58, "v": 13, "w": "1" }, { "u": 58, "v": 14, "w": "3" }, { "u": 58, "v": 15, "w": "3" }, { "u": 58, "v": 16, "w": "3" }, { "u": 58, "v": 17, "w": "2" }, { "u": 58, "v": 18, "w": "3" }, { "u": 58, "v": 19, "w": "3" }, { "u": 58, "v": 20, "w": "2" }, { "u": 58, "v": 21, "w": "2" }, { "u": 58, "v": 22, "w": "2" }, { "u": 58, "v": 23, "w": "4" }, { "u": 58, "v": 24, "w": "4" }, { "u": 58, "v": 25, "w": "2" }, { "u": 58, "v": 26, "w": "2" }, { "u": 58, "v": 27, "w": "3" }, { "u": 58, "v": 28, "w": "1" }, { "u": 58, "v": 29, "w": "2" }, { "u": 58, "v": 30, "w": "3" }, { "u": 58, "v": 31, "w": "3" }, { "u": 58, "v": 32, "w": "2" }, { "u": 58, "v": 33, "w": "2" }, { "u": 58, "v": 34, "w": "5" }, { "u": 58, "v": 35, "w": "3" }, { "u": 58, "v": 36, "w": "2" }, { "u": 58, "v": 37, "w": "2" }, { "u": 58, "v": 38, "w": "2" }, { "u": 58, "v": 39, "w": "2" }, { "u": 58, "v": 40, "w": "2" }, { "u": 58, "v": 41, "w": "4" }, { "u": 58, "v": 42, "w": "2" }, { "u": 58, "v": 43, "w": "2" }, { "u": 58, "v": 44, "w": "2" }, { "u": 58, "v": 45, "w": "2" }, { "u": 58, "v": 46, "w": "2" }, { "u": 58, "v": 47, "w": "2" }, { "u": 58, "v": 48, "w": "2" }, { "u": 58, "v": 49, "w": "5" }, { "u": 58, "v": 50, "w": "4" }, { "u": 58, "v": 51, "w": "1" }, { "u": 58, "v": 52, "w": "3" }, { "u": 58, "v": 53, "w": "3" }, { "u": 58, "v": 54, "w": "3" }, { "u": 58, "v": 55, "w": "3" }, { "u": 58, "v": 56, "w": "3" }, { "u": 58, "v": 57, "w": "3" } ], "hash_sha256": "e5ecf113bd55960c196d17b36cfc94d127cff950907698d8fd84fce86d41977c", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] }, "ties_graph": 1934 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 3, "w": "2" }, { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 7, "w": "2" }, { "u": 1, "v": 11, "w": "1" }, { "u": 1, "v": 12, "w": "1" }, { "u": 1, "v": 13, "w": "2" }, { "u": 1, "v": 17, "w": "1" }, { "u": 1, "v": 19, "w": "1" }, { "u": 1, "v": 22, "w": "1" }, { "u": 1, "v": 29, "w": "2" }, { "u": 1, "v": 30, "w": "1" }, { "u": 1, "v": 31, "w": "1" }, { "u": 1, "v": 32, "w": "1" }, { "u": 1, "v": 34, "w": "2" }, { "u": 1, "v": 35, "w": "1" }, { "u": 1, "v": 36, "w": "2" }, { "u": 1, "v": 41, "w": "1" }, { "u": 1, "v": 49, "w": "1" }, { "u": 1, "v": 54, "w": "1" }, { "u": 1, "v": 55, "w": "1" }, { "u": 1, "v": 56, "w": "4" }, { "u": 1, "v": 57, "w": "1" }, { "u": 1, "v": 58, "w": "1" }, { "u": 2, "v": 3, "w": "10" }, { "u": 2, "v": 6, "w": "2" }, { "u": 2, "v": 7, "w": "1" }, { "u": 2, "v": 9, "w": "2" }, { "u": 2, "v": 13, "w": "6" }, { "u": 2, "v": 14, "w": "2" }, { "u": 2, "v": 16, "w": "1" }, { "u": 2, "v": 20, "w": "1" }, { "u": 2, "v": 22, "w": "10" }, { "u": 2, "v": 23, "w": "2" }, { "u": 2, "v": 25, "w": "4" }, { "u": 2, "v": 27, "w": "3" }, { "u": 2, "v": 29, "w": "1" }, { "u": 2, "v": 30, "w": "1" }, { "u": 2, "v": 35, "w": "5" }, { "u": 2, "v": 36, "w": "1" }, { "u": 2, "v": 38, "w": "4" }, { "u": 2, "v": 44, "w": "1" }, { "u": 2, "v": 45, "w": "1" }, { "u": 2, "v": 48, "w": "5" }, { "u": 2, "v": 49, "w": "3" }, { "u": 2, "v": 54, "w": "1" }, { "u": 2, "v": 56, "w": "1" }, { "u": 2, "v": 57, "w": "4" }, { "u": 3, "v": 1, "w": "2" }, { "u": 3, "v": 2, "w": "10" }, { "u": 3, "v": 4, "w": "6" }, { "u": 3, "v": 5, "w": "11" }, { "u": 3, "v": 6, "w": "14" }, { "u": 3, "v": 7, "w": "15" }, { "u": 3, "v": 8, "w": "4" }, { "u": 3, "v": 9, "w": "12" }, { "u": 3, "v": 11, "w": "5" }, { "u": 3, "v": 12, "w": "4" }, { "u": 3, "v": 13, "w": "3" }, { "u": 3, "v": 14, "w": "8" }, { "u": 3, "v": 15, "w": "10" }, { "u": 3, "v": 16, "w": "8" }, { "u": 3, "v": 17, "w": "11" }, { "u": 3, "v": 19, "w": "2" }, { "u": 3, "v": 20, "w": "19" }, { "u": 3, "v": 21, "w": "2" }, { "u": 3, "v": 22, "w": "15" }, { "u": 3, "v": 23, "w": "1" }, { "u": 3, "v": 24, "w": "2" }, { "u": 3, "v": 25, "w": "6" }, { "u": 3, "v": 26, "w": "1" }, { "u": 3, "v": 27, "w": "5" }, { "u": 3, "v": 29, "w": "12" }, { "u": 3, "v": 30, "w": "5" }, { "u": 3, "v": 31, "w": "4" }, { "u": 3, "v": 33, "w": "1" }, { "u": 3, "v": 34, "w": "4" }, { "u": 3, "v": 35, "w": "15" }, { "u": 3, "v": 36, "w": "3" }, { "u": 3, "v": 37, "w": "1" }, { "u": 3, "v": 38, "w": "3" }, { "u": 3, "v": 39, "w": "6" }, { "u": 3, "v": 41, "w": "2" }, { "u": 3, "v": 42, "w": "3" }, { "u": 3, "v": 44, "w": "9" }, { "u": 3, "v": 45, "w": "8" }, { "u": 3, "v": 46, "w": "2" }, { "u": 3, "v": 47, "w": "1" }, { "u": 3, "v": 48, "w": "3" }, { "u": 3, "v": 49, "w": "6" }, { "u": 3, "v": 50, "w": "2" }, { "u": 3, "v": 52, "w": "2" }, { "u": 3, "v": 53, "w": "2" }, { "u": 3, "v": 54, "w": "16" }, { "u": 3, "v": 55, "w": "4" }, { "u": 3, "v": 56, "w": "5" }, { "u": 3, "v": 57, "w": "19" }, { "u": 3, "v": 58, "w": "1" }, { "u": 4, "v": 1, "w": "1" }, { "u": 4, "v": 3, "w": "6" }, { "u": 4, "v": 5, "w": "2" }, { "u": 4, "v": 6, "w": "3" }, { "u": 4, "v": 7, "w": "9" }, { "u": 4, "v": 8, "w": "1" }, { "u": 4, "v": 9, "w": "8" }, { "u": 4, "v": 12, "w": "5" }, { "u": 4, "v": 15, "w": "2" }, { "u": 4, "v": 16, "w": "4" }, { "u": 4, "v": 17, "w": "3" }, { "u": 4, "v": 18, "w": "2" }, { "u": 4, "v": 19, "w": "2" }, { "u": 4, "v": 20, "w": "6" }, { "u": 4, "v": 22, "w": "1" }, { "u": 4, "v": 23, "w": "1" }, { "u": 4, "v": 24, "w": "3" }, { "u": 4, "v": 25, "w": "1" }, { "u": 4, "v": 27, "w": "5" }, { "u": 4, "v": 28, "w": "1" }, { "u": 4, "v": 29, "w": "1" }, { "u": 4, "v": 30, "w": "3" }, { "u": 4, "v": 32, "w": "1" }, { "u": 4, "v": 33, "w": "1" }, { "u": 4, "v": 34, "w": "4" }, { "u": 4, "v": 35, "w": "1" }, { "u": 4, "v": 37, "w": "1" }, { "u": 4, "v": 38, "w": "3" }, { "u": 4, "v": 39, "w": "2" }, { "u": 4, "v": 41, "w": "1" }, { "u": 4, "v": 44, "w": "1" }, { "u": 4, "v": 45, "w": "1" }, { "u": 4, "v": 46, "w": "1" }, { "u": 4, "v": 47, "w": "1" }, { "u": 4, "v": 48, "w": "2" }, { "u": 4, "v": 49, "w": "1" }, { "u": 4, "v": 50, "w": "3" }, { "u": 4, "v": 53, "w": "2" }, { "u": 4, "v": 54, "w": "1" }, { "u": 4, "v": 55, "w": "2" }, { "u": 4, "v": 56, "w": "2" }, { "u": 4, "v": 57, "w": "3" }, { "u": 4, "v": 58, "w": "5" }, { "u": 5, "v": 3, "w": "11" }, { "u": 5, "v": 4, "w": "2" }, { "u": 5, "v": 6, "w": "2" }, { "u": 5, "v": 7, "w": "8" }, { "u": 5, "v": 8, "w": "1" }, { "u": 5, "v": 9, "w": "1" }, { "u": 5, "v": 10, "w": "1" }, { "u": 5, "v": 13, "w": "2" }, { "u": 5, "v": 15, "w": "1" }, { "u": 5, "v": 16, "w": "1" }, { "u": 5, "v": 20, "w": "3" }, { "u": 5, "v": 27, "w": "8" }, { "u": 5, "v": 29, "w": "1" }, { "u": 5, "v": 30, "w": "5" }, { "u": 5, "v": 33, "w": "1" }, { "u": 5, "v": 39, "w": "9" }, { "u": 5, "v": 40, "w": "2" }, { "u": 5, "v": 41, "w": "1" }, { "u": 5, "v": 43, "w": "1" }, { "u": 5, "v": 44, "w": "8" }, { "u": 5, "v": 45, "w": "25" }, { "u": 5, "v": 53, "w": "1" }, { "u": 5, "v": 54, "w": "2" }, { "u": 5, "v": 57, "w": "4" }, { "u": 6, "v": 2, "w": "2" }, { "u": 6, "v": 3, "w": "14" }, { "u": 6, "v": 4, "w": "3" }, { "u": 6, "v": 5, "w": "2" }, { "u": 6, "v": 7, "w": "30" }, { "u": 6, "v": 8, "w": "2" }, { "u": 6, "v": 9, "w": "8" }, { "u": 6, "v": 11, "w": "4" }, { "u": 6, "v": 12, "w": "4" }, { "u": 6, "v": 13, "w": "1" }, { "u": 6, "v": 14, "w": "6" }, { "u": 6, "v": 15, "w": "2" }, { "u": 6, "v": 16, "w": "14" }, { "u": 6, "v": 17, "w": "9" }, { "u": 6, "v": 19, "w": "1" }, { "u": 6, "v": 20, "w": "51" }, { "u": 6, "v": 22, "w": "3" }, { "u": 6, "v": 23, "w": "2" }, { "u": 6, "v": 24, "w": "1" }, { "u": 6, "v": 26, "w": "1" }, { "u": 6, "v": 27, "w": "6" }, { "u": 6, "v": 29, "w": "3" }, { "u": 6, "v": 30, "w": "11" }, { "u": 6, "v": 31, "w": "2" }, { "u": 6, "v": 33, "w": "15" }, { "u": 6, "v": 34, "w": "5" }, { "u": 6, "v": 35, "w": "3" }, { "u": 6, "v": 36, "w": "1" }, { "u": 6, "v": 38, "w": "2" }, { "u": 6, "v": 39, "w": "2" }, { "u": 6, "v": 40, "w": "1" }, { "u": 6, "v": 41, "w": "3" }, { "u": 6, "v": 42, "w": "1" }, { "u": 6, "v": 44, "w": "3" }, { "u": 6, "v": 45, "w": "2" }, { "u": 6, "v": 46, "w": "2" }, { "u": 6, "v": 47, "w": "6" }, { "u": 6, "v": 48, "w": "1" }, { "u": 6, "v": 49, "w": "3" }, { "u": 6, "v": 50, "w": "4" }, { "u": 6, "v": 52, "w": "2" }, { "u": 6, "v": 53, "w": "8" }, { "u": 6, "v": 54, "w": "9" }, { "u": 6, "v": 55, "w": "3" }, { "u": 6, "v": 56, "w": "2" }, { "u": 6, "v": 57, "w": "18" }, { "u": 6, "v": 58, "w": "2" }, { "u": 7, "v": 1, "w": "2" }, { "u": 7, "v": 2, "w": "1" }, { "u": 7, "v": 3, "w": "15" }, { "u": 7, "v": 4, "w": "9" }, { "u": 7, "v": 5, "w": "8" }, { "u": 7, "v": 6, "w": "30" }, { "u": 7, "v": 8, "w": "10" }, { "u": 7, "v": 9, "w": "4" }, { "u": 7, "v": 10, "w": "2" }, { "u": 7, "v": 11, "w": "7" }, { "u": 7, "v": 12, "w": "3" }, { "u": 7, "v": 14, "w": "12" }, { "u": 7, "v": 15, "w": "9" }, { "u": 7, "v": 16, "w": "10" }, { "u": 7, "v": 17, "w": "9" }, { "u": 7, "v": 18, "w": "2" }, { "u": 7, "v": 19, "w": "3" }, { "u": 7, "v": 20, "w": "40" }, { "u": 7, "v": 21, "w": "2" }, { "u": 7, "v": 22, "w": "2" }, { "u": 7, "v": 23, "w": "5" }, { "u": 7, "v": 24, "w": "2" }, { "u": 7, "v": 26, "w": "1" }, { "u": 7, "v": 27, "w": "19" }, { "u": 7, "v": 28, "w": "1" }, { "u": 7, "v": 29, "w": "10" }, { "u": 7, "v": 30, "w": "14" }, { "u": 7, "v": 31, "w": "5" }, { "u": 7, "v": 32, "w": "3" }, { "u": 7, "v": 33, "w": "14" }, { "u": 7, "v": 34, "w": "7" }, { "u": 7, "v": 35, "w": "7" }, { "u": 7, "v": 36, "w": "5" }, { "u": 7, "v": 37, "w": "3" }, { "u": 7, "v": 38, "w": "4" }, { "u": 7, "v": 39, "w": "5" }, { "u": 7, "v": 40, "w": "7" }, { "u": 7, "v": 41, "w": "8" }, { "u": 7, "v": 42, "w": "5" }, { "u": 7, "v": 44, "w": "2" }, { "u": 7, "v": 45, "w": "4" }, { "u": 7, "v": 46, "w": "7" }, { "u": 7, "v": 47, "w": "3" }, { "u": 7, "v": 48, "w": "7" }, { "u": 7, "v": 49, "w": "7" }, { "u": 7, "v": 50, "w": "2" }, { "u": 7, "v": 53, "w": "6" }, { "u": 7, "v": 54, "w": "5" }, { "u": 7, "v": 55, "w": "14" }, { "u": 7, "v": 56, "w": "16" }, { "u": 7, "v": 57, "w": "20" }, { "u": 7, "v": 58, "w": "4" }, { "u": 8, "v": 3, "w": "4" }, { "u": 8, "v": 4, "w": "1" }, { "u": 8, "v": 5, "w": "1" }, { "u": 8, "v": 6, "w": "2" }, { "u": 8, "v": 7, "w": "10" }, { "u": 8, "v": 9, "w": "3" }, { "u": 8, "v": 11, "w": "2" }, { "u": 8, "v": 13, "w": "1" }, { "u": 8, "v": 14, "w": "3" }, { "u": 8, "v": 15, "w": "3" }, { "u": 8, "v": 16, "w": "3" }, { "u": 8, "v": 17, "w": "5" }, { "u": 8, "v": 20, "w": "6" }, { "u": 8, "v": 21, "w": "1" }, { "u": 8, "v": 23, "w": "2" }, { "u": 8, "v": 24, "w": "3" }, { "u": 8, "v": 26, "w": "1" }, { "u": 8, "v": 27, "w": "6" }, { "u": 8, "v": 29, "w": "2" }, { "u": 8, "v": 31, "w": "9" }, { "u": 8, "v": 32, "w": "1" }, { "u": 8, "v": 34, "w": "1" }, { "u": 8, "v": 35, "w": "2" }, { "u": 8, "v": 36, "w": "4" }, { "u": 8, "v": 37, "w": "2" }, { "u": 8, "v": 38, "w": "5" }, { "u": 8, "v": 39, "w": "1" }, { "u": 8, "v": 41, "w": "3" }, { "u": 8, "v": 42, "w": "5" }, { "u": 8, "v": 45, "w": "5" }, { "u": 8, "v": 47, "w": "1" }, { "u": 8, "v": 48, "w": "3" }, { "u": 8, "v": 49, "w": "1" }, { "u": 8, "v": 50, "w": "1" }, { "u": 8, "v": 52, "w": "1" }, { "u": 8, "v": 53, "w": "2" }, { "u": 8, "v": 54, "w": "5" }, { "u": 8, "v": 56, "w": "2" }, { "u": 8, "v": 57, "w": "4" }, { "u": 8, "v": 58, "w": "2" }, { "u": 9, "v": 2, "w": "2" }, { "u": 9, "v": 3, "w": "12" }, { "u": 9, "v": 4, "w": "8" }, { "u": 9, "v": 5, "w": "1" }, { "u": 9, "v": 6, "w": "8" }, { "u": 9, "v": 7, "w": "4" }, { "u": 9, "v": 8, "w": "3" }, { "u": 9, "v": 11, "w": "5" }, { "u": 9, "v": 12, "w": "5" }, { "u": 9, "v": 13, "w": "2" }, { "u": 9, "v": 14, "w": "2" }, { "u": 9, "v": 15, "w": "4" }, { "u": 9, "v": 16, "w": "5" }, { "u": 9, "v": 17, "w": "6" }, { "u": 9, "v": 18, "w": "1" }, { "u": 9, "v": 20, "w": "5" }, { "u": 9, "v": 22, "w": "5" }, { "u": 9, "v": 24, "w": "3" }, { "u": 9, "v": 25, "w": "3" }, { "u": 9, "v": 26, "w": "3" }, { "u": 9, "v": 27, "w": "3" }, { "u": 9, "v": 28, "w": "1" }, { "u": 9, "v": 29, "w": "2" }, { "u": 9, "v": 30, "w": "3" }, { "u": 9, "v": 31, "w": "1" }, { "u": 9, "v": 33, "w": "2" }, { "u": 9, "v": 34, "w": "4" }, { "u": 9, "v": 35, "w": "4" }, { "u": 9, "v": 36, "w": "3" }, { "u": 9, "v": 37, "w": "5" }, { "u": 9, "v": 38, "w": "1" }, { "u": 9, "v": 39, "w": "2" }, { "u": 9, "v": 41, "w": "1" }, { "u": 9, "v": 42, "w": "1" }, { "u": 9, "v": 43, "w": "1" }, { "u": 9, "v": 44, "w": "2" }, { "u": 9, "v": 47, "w": "4" }, { "u": 9, "v": 49, "w": "1" }, { "u": 9, "v": 50, "w": "4" }, { "u": 9, "v": 52, "w": "6" }, { "u": 9, "v": 53, "w": "1" }, { "u": 9, "v": 54, "w": "4" }, { "u": 9, "v": 55, "w": "3" }, { "u": 9, "v": 56, "w": "2" }, { "u": 9, "v": 57, "w": "7" }, { "u": 9, "v": 58, "w": "1" }, { "u": 10, "v": 5, "w": "1" }, { "u": 10, "v": 7, "w": "2" }, { "u": 10, "v": 15, "w": "1" }, { "u": 10, "v": 16, "w": "2" }, { "u": 10, "v": 27, "w": "6" }, { "u": 10, "v": 29, "w": "1" }, { "u": 10, "v": 31, "w": "1" }, { "u": 10, "v": 38, "w": "1" }, { "u": 10, "v": 39, "w": "2" }, { "u": 10, "v": 40, "w": "2" }, { "u": 10, "v": 46, "w": "1" }, { "u": 10, "v": 54, "w": "1" }, { "u": 11, "v": 1, "w": "1" }, { "u": 11, "v": 3, "w": "5" }, { "u": 11, "v": 6, "w": "4" }, { "u": 11, "v": 7, "w": "7" }, { "u": 11, "v": 8, "w": "2" }, { "u": 11, "v": 9, "w": "5" }, { "u": 11, "v": 14, "w": "1" }, { "u": 11, "v": 15, "w": "3" }, { "u": 11, "v": 16, "w": "3" }, { "u": 11, "v": 17, "w": "5" }, { "u": 11, "v": 18, "w": "3" }, { "u": 11, "v": 20, "w": "7" }, { "u": 11, "v": 21, "w": "4" }, { "u": 11, "v": 22, "w": "1" }, { "u": 11, "v": 24, "w": "3" }, { "u": 11, "v": 27, "w": "4" }, { "u": 11, "v": 29, "w": "5" }, { "u": 11, "v": 30, "w": "1" }, { "u": 11, "v": 31, "w": "3" }, { "u": 11, "v": 34, "w": "2" }, { "u": 11, "v": 35, "w": "2" }, { "u": 11, "v": 36, "w": "3" }, { "u": 11, "v": 37, "w": "5" }, { "u": 11, "v": 38, "w": "3" }, { "u": 11, "v": 39, "w": "2" }, { "u": 11, "v": 42, "w": "1" }, { "u": 11, "v": 44, "w": "2" }, { "u": 11, "v": 45, "w": "1" }, { "u": 11, "v": 46, "w": "4" }, { "u": 11, "v": 47, "w": "5" }, { "u": 11, "v": 48, "w": "2" }, { "u": 11, "v": 49, "w": "1" }, { "u": 11, "v": 54, "w": "4" }, { "u": 11, "v": 55, "w": "6" }, { "u": 11, "v": 56, "w": "6" }, { "u": 11, "v": 57, "w": "12" }, { "u": 12, "v": 1, "w": "1" }, { "u": 12, "v": 3, "w": "4" }, { "u": 12, "v": 4, "w": "5" }, { "u": 12, "v": 6, "w": "4" }, { "u": 12, "v": 7, "w": "3" }, { "u": 12, "v": 9, "w": "5" }, { "u": 12, "v": 20, "w": "3" }, { "u": 12, "v": 22, "w": "1" }, { "u": 12, "v": 24, "w": "1" }, { "u": 12, "v": 25, "w": "1" }, { "u": 12, "v": 29, "w": "2" }, { "u": 12, "v": 31, "w": "2" }, { "u": 12, "v": 33, "w": "1" }, { "u": 12, "v": 34, "w": "2" }, { "u": 12, "v": 35, "w": "3" }, { "u": 12, "v": 36, "w": "2" }, { "u": 12, "v": 37, "w": "2" }, { "u": 12, "v": 38, "w": "1" }, { "u": 12, "v": 42, "w": "1" }, { "u": 12, "v": 44, "w": "1" }, { "u": 12, "v": 45, "w": "1" }, { "u": 12, "v": 46, "w": "1" }, { "u": 12, "v": 49, "w": "1" }, { "u": 12, "v": 50, "w": "2" }, { "u": 12, "v": 53, "w": "1" }, { "u": 12, "v": 54, "w": "2" }, { "u": 12, "v": 56, "w": "7" }, { "u": 12, "v": 57, "w": "3" }, { "u": 12, "v": 58, "w": "3" }, { "u": 13, "v": 1, "w": "2" }, { "u": 13, "v": 2, "w": "6" }, { "u": 13, "v": 3, "w": "3" }, { "u": 13, "v": 5, "w": "2" }, { "u": 13, "v": 6, "w": "1" }, { "u": 13, "v": 8, "w": "1" }, { "u": 13, "v": 9, "w": "2" }, { "u": 13, "v": 14, "w": "2" }, { "u": 13, "v": 15, "w": "1" }, { "u": 13, "v": 16, "w": "3" }, { "u": 13, "v": 17, "w": "3" }, { "u": 13, "v": 19, "w": "1" }, { "u": 13, "v": 22, "w": "6" }, { "u": 13, "v": 23, "w": "2" }, { "u": 13, "v": 27, "w": "3" }, { "u": 13, "v": 29, "w": "1" }, { "u": 13, "v": 33, "w": "1" }, { "u": 13, "v": 34, "w": "1" }, { "u": 13, "v": 35, "w": "1" }, { "u": 13, "v": 38, "w": "1" }, { "u": 13, "v": 39, "w": "1" }, { "u": 13, "v": 40, "w": "1" }, { "u": 13, "v": 41, "w": "1" }, { "u": 13, "v": 42, "w": "1" }, { "u": 13, "v": 43, "w": "1" }, { "u": 13, "v": 45, "w": "2" }, { "u": 13, "v": 46, "w": "1" }, { "u": 13, "v": 49, "w": "2" }, { "u": 13, "v": 53, "w": "2" }, { "u": 13, "v": 54, "w": "4" }, { "u": 13, "v": 55, "w": "1" }, { "u": 14, "v": 2, "w": "2" }, { "u": 14, "v": 3, "w": "8" }, { "u": 14, "v": 6, "w": "6" }, { "u": 14, "v": 7, "w": "12" }, { "u": 14, "v": 8, "w": "3" }, { "u": 14, "v": 9, "w": "2" }, { "u": 14, "v": 11, "w": "1" }, { "u": 14, "v": 13, "w": "2" }, { "u": 14, "v": 15, "w": "3" }, { "u": 14, "v": 16, "w": "8" }, { "u": 14, "v": 17, "w": "11" }, { "u": 14, "v": 18, "w": "1" }, { "u": 14, "v": 19, "w": "4" }, { "u": 14, "v": 20, "w": "8" }, { "u": 14, "v": 22, "w": "1" }, { "u": 14, "v": 25, "w": "1" }, { "u": 14, "v": 26, "w": "1" }, { "u": 14, "v": 27, "w": "4" }, { "u": 14, "v": 29, "w": "8" }, { "u": 14, "v": 30, "w": "4" }, { "u": 14, "v": 31, "w": "6" }, { "u": 14, "v": 33, "w": "3" }, { "u": 14, "v": 34, "w": "1" }, { "u": 14, "v": 35, "w": "5" }, { "u": 14, "v": 36, "w": "1" }, { "u": 14, "v": 37, "w": "1" }, { "u": 14, "v": 41, "w": "1" }, { "u": 14, "v": 42, "w": "3" }, { "u": 14, "v": 44, "w": "2" }, { "u": 14, "v": 45, "w": "2" }, { "u": 14, "v": 46, "w": "1" }, { "u": 14, "v": 47, "w": "1" }, { "u": 14, "v": 48, "w": "1" }, { "u": 14, "v": 53, "w": "1" }, { "u": 14, "v": 55, "w": "2" }, { "u": 14, "v": 56, "w": "1" }, { "u": 14, "v": 57, "w": "5" }, { "u": 14, "v": 58, "w": "1" }, { "u": 15, "v": 3, "w": "10" }, { "u": 15, "v": 4, "w": "2" }, { "u": 15, "v": 5, "w": "1" }, { "u": 15, "v": 6, "w": "2" }, { "u": 15, "v": 7, "w": "9" }, { "u": 15, "v": 8, "w": "3" }, { "u": 15, "v": 9, "w": "4" }, { "u": 15, "v": 10, "w": "1" }, { "u": 15, "v": 11, "w": "3" }, { "u": 15, "v": 13, "w": "1" }, { "u": 15, "v": 14, "w": "3" }, { "u": 15, "v": 16, "w": "9" }, { "u": 15, "v": 17, "w": "14" }, { "u": 15, "v": 19, "w": "6" }, { "u": 15, "v": 20, "w": "9" }, { "u": 15, "v": 22, "w": "2" }, { "u": 15, "v": 23, "w": "1" }, { "u": 15, "v": 24, "w": "2" }, { "u": 15, "v": 25, "w": "1" }, { "u": 15, "v": 27, "w": "4" }, { "u": 15, "v": 29, "w": "3" }, { "u": 15, "v": 31, "w": "2" }, { "u": 15, "v": 32, "w": "1" }, { "u": 15, "v": 33, "w": "1" }, { "u": 15, "v": 34, "w": "4" }, { "u": 15, "v": 35, "w": "2" }, { "u": 15, "v": 36, "w": "3" }, { "u": 15, "v": 38, "w": "6" }, { "u": 15, "v": 39, "w": "1" }, { "u": 15, "v": 41, "w": "7" }, { "u": 15, "v": 42, "w": "1" }, { "u": 15, "v": 44, "w": "7" }, { "u": 15, "v": 45, "w": "1" }, { "u": 15, "v": 46, "w": "1" }, { "u": 15, "v": 49, "w": "1" }, { "u": 15, "v": 50, "w": "1" }, { "u": 15, "v": 53, "w": "7" }, { "u": 15, "v": 54, "w": "6" }, { "u": 15, "v": 55, "w": "4" }, { "u": 15, "v": 56, "w": "9" }, { "u": 15, "v": 57, "w": "4" }, { "u": 16, "v": 2, "w": "1" }, { "u": 16, "v": 3, "w": "8" }, { "u": 16, "v": 4, "w": "4" }, { "u": 16, "v": 5, "w": "1" }, { "u": 16, "v": 6, "w": "14" }, { "u": 16, "v": 7, "w": "10" }, { "u": 16, "v": 8, "w": "3" }, { "u": 16, "v": 9, "w": "5" }, { "u": 16, "v": 10, "w": "2" }, { "u": 16, "v": 11, "w": "3" }, { "u": 16, "v": 13, "w": "3" }, { "u": 16, "v": 14, "w": "8" }, { "u": 16, "v": 15, "w": "9" }, { "u": 16, "v": 17, "w": "26" }, { "u": 16, "v": 18, "w": "3" }, { "u": 16, "v": 19, "w": "1" }, { "u": 16, "v": 20, "w": "12" }, { "u": 16, "v": 22, "w": "2" }, { "u": 16, "v": 25, "w": "1" }, { "u": 16, "v": 27, "w": "7" }, { "u": 16, "v": 29, "w": "5" }, { "u": 16, "v": 30, "w": "6" }, { "u": 16, "v": 31, "w": "5" }, { "u": 16, "v": 32, "w": "4" }, { "u": 16, "v": 33, "w": "2" }, { "u": 16, "v": 34, "w": "2" }, { "u": 16, "v": 35, "w": "2" }, { "u": 16, "v": 36, "w": "2" }, { "u": 16, "v": 38, "w": "4" }, { "u": 16, "v": 39, "w": "4" }, { "u": 16, "v": 41, "w": "2" }, { "u": 16, "v": 42, "w": "5" }, { "u": 16, "v": 43, "w": "1" }, { "u": 16, "v": 44, "w": "3" }, { "u": 16, "v": 45, "w": "2" }, { "u": 16, "v": 46, "w": "1" }, { "u": 16, "v": 47, "w": "1" }, { "u": 16, "v": 48, "w": "4" }, { "u": 16, "v": 50, "w": "2" }, { "u": 16, "v": 53, "w": "8" }, { "u": 16, "v": 54, "w": "4" }, { "u": 16, "v": 55, "w": "2" }, { "u": 16, "v": 57, "w": "11" }, { "u": 16, "v": 58, "w": "3" }, { "u": 17, "v": 1, "w": "1" }, { "u": 17, "v": 3, "w": "11" }, { "u": 17, "v": 4, "w": "3" }, { "u": 17, "v": 6, "w": "9" }, { "u": 17, "v": 7, "w": "9" }, { "u": 17, "v": 8, "w": "5" }, { "u": 17, "v": 9, "w": "6" }, { "u": 17, "v": 11, "w": "5" }, { "u": 17, "v": 13, "w": "3" }, { "u": 17, "v": 14, "w": "11" }, { "u": 17, "v": 15, "w": "14" }, { "u": 17, "v": 16, "w": "26" }, { "u": 17, "v": 18, "w": "3" }, { "u": 17, "v": 20, "w": "9" }, { "u": 17, "v": 22, "w": "1" }, { "u": 17, "v": 25, "w": "1" }, { "u": 17, "v": 27, "w": "5" }, { "u": 17, "v": 29, "w": "5" }, { "u": 17, "v": 30, "w": "2" }, { "u": 17, "v": 31, "w": "2" }, { "u": 17, "v": 32, "w": "4" }, { "u": 17, "v": 33, "w": "2" }, { "u": 17, "v": 34, "w": "1" }, { "u": 17, "v": 35, "w": "4" }, { "u": 17, "v": 36, "w": "2" }, { "u": 17, "v": 38, "w": "1" }, { "u": 17, "v": 39, "w": "1" }, { "u": 17, "v": 40, "w": "1" }, { "u": 17, "v": 41, "w": "2" }, { "u": 17, "v": 42, "w": "3" }, { "u": 17, "v": 44, "w": "3" }, { "u": 17, "v": 45, "w": "1" }, { "u": 17, "v": 48, "w": "3" }, { "u": 17, "v": 49, "w": "1" }, { "u": 17, "v": 50, "w": "2" }, { "u": 17, "v": 53, "w": "7" }, { "u": 17, "v": 54, "w": "7" }, { "u": 17, "v": 55, "w": "4" }, { "u": 17, "v": 57, "w": "11" }, { "u": 18, "v": 4, "w": "2" }, { "u": 18, "v": 7, "w": "2" }, { "u": 18, "v": 9, "w": "1" }, { "u": 18, "v": 11, "w": "3" }, { "u": 18, "v": 14, "w": "1" }, { "u": 18, "v": 16, "w": "3" }, { "u": 18, "v": 17, "w": "3" }, { "u": 18, "v": 21, "w": "3" }, { "u": 18, "v": 29, "w": "1" }, { "u": 18, "v": 32, "w": "3" }, { "u": 18, "v": 34, "w": "1" }, { "u": 18, "v": 35, "w": "1" }, { "u": 18, "v": 36, "w": "1" }, { "u": 18, "v": 37, "w": "1" }, { "u": 18, "v": 39, "w": "1" }, { "u": 18, "v": 44, "w": "1" }, { "u": 18, "v": 46, "w": "2" }, { "u": 18, "v": 48, "w": "2" }, { "u": 18, "v": 55, "w": "2" }, { "u": 18, "v": 56, "w": "1" }, { "u": 18, "v": 58, "w": "1" }, { "u": 19, "v": 1, "w": "1" }, { "u": 19, "v": 3, "w": "2" }, { "u": 19, "v": 4, "w": "2" }, { "u": 19, "v": 6, "w": "1" }, { "u": 19, "v": 7, "w": "3" }, { "u": 19, "v": 13, "w": "1" }, { "u": 19, "v": 14, "w": "4" }, { "u": 19, "v": 15, "w": "6" }, { "u": 19, "v": 16, "w": "1" }, { "u": 19, "v": 20, "w": "5" }, { "u": 19, "v": 23, "w": "2" }, { "u": 19, "v": 24, "w": "1" }, { "u": 19, "v": 25, "w": "3" }, { "u": 19, "v": 30, "w": "1" }, { "u": 19, "v": 31, "w": "1" }, { "u": 19, "v": 34, "w": "1" }, { "u": 19, "v": 35, "w": "1" }, { "u": 19, "v": 36, "w": "1" }, { "u": 19, "v": 37, "w": "1" }, { "u": 19, "v": 38, "w": "2" }, { "u": 19, "v": 40, "w": "1" }, { "u": 19, "v": 41, "w": "14" }, { "u": 19, "v": 42, "w": "1" }, { "u": 19, "v": 44, "w": "1" }, { "u": 19, "v": 47, "w": "1" }, { "u": 19, "v": 49, "w": "3" }, { "u": 19, "v": 53, "w": "1" }, { "u": 19, "v": 56, "w": "3" }, { "u": 19, "v": 57, "w": "1" }, { "u": 19, "v": 58, "w": "2" }, { "u": 20, "v": 2, "w": "1" }, { "u": 20, "v": 3, "w": "19" }, { "u": 20, "v": 4, "w": "6" }, { "u": 20, "v": 5, "w": "3" }, { "u": 20, "v": 6, "w": "51" }, { "u": 20, "v": 7, "w": "40" }, { "u": 20, "v": 8, "w": "6" }, { "u": 20, "v": 9, "w": "5" }, { "u": 20, "v": 11, "w": "7" }, { "u": 20, "v": 12, "w": "3" }, { "u": 20, "v": 14, "w": "8" }, { "u": 20, "v": 15, "w": "9" }, { "u": 20, "v": 16, "w": "12" }, { "u": 20, "v": 17, "w": "9" }, { "u": 20, "v": 19, "w": "5" }, { "u": 20, "v": 21, "w": "3" }, { "u": 20, "v": 22, "w": "2" }, { "u": 20, "v": 23, "w": "3" }, { "u": 20, "v": 24, "w": "2" }, { "u": 20, "v": 25, "w": "1" }, { "u": 20, "v": 26, "w": "1" }, { "u": 20, "v": 27, "w": "7" }, { "u": 20, "v": 28, "w": "1" }, { "u": 20, "v": 29, "w": "10" }, { "u": 20, "v": 30, "w": "6" }, { "u": 20, "v": 31, "w": "6" }, { "u": 20, "v": 32, "w": "1" }, { "u": 20, "v": 33, "w": "13" }, { "u": 20, "v": 34, "w": "12" }, { "u": 20, "v": 35, "w": "9" }, { "u": 20, "v": 36, "w": "2" }, { "u": 20, "v": 37, "w": "1" }, { "u": 20, "v": 38, "w": "6" }, { "u": 20, "v": 39, "w": "2" }, { "u": 20, "v": 40, "w": "1" }, { "u": 20, "v": 41, "w": "10" }, { "u": 20, "v": 42, "w": "4" }, { "u": 20, "v": 44, "w": "2" }, { "u": 20, "v": 45, "w": "2" }, { "u": 20, "v": 46, "w": "1" }, { "u": 20, "v": 47, "w": "2" }, { "u": 20, "v": 48, "w": "1" }, { "u": 20, "v": 49, "w": "6" }, { "u": 20, "v": 50, "w": "1" }, { "u": 20, "v": 53, "w": "12" }, { "u": 20, "v": 54, "w": "17" }, { "u": 20, "v": 55, "w": "11" }, { "u": 20, "v": 56, "w": "9" }, { "u": 20, "v": 57, "w": "23" }, { "u": 20, "v": 58, "w": "5" }, { "u": 21, "v": 3, "w": "2" }, { "u": 21, "v": 7, "w": "2" }, { "u": 21, "v": 8, "w": "1" }, { "u": 21, "v": 11, "w": "4" }, { "u": 21, "v": 18, "w": "3" }, { "u": 21, "v": 20, "w": "3" }, { "u": 21, "v": 23, "w": "1" }, { "u": 21, "v": 29, "w": "2" }, { "u": 21, "v": 31, "w": "2" }, { "u": 21, "v": 34, "w": "1" }, { "u": 21, "v": 35, "w": "1" }, { "u": 21, "v": 36, "w": "1" }, { "u": 21, "v": 38, "w": "1" }, { "u": 21, "v": 41, "w": "1" }, { "u": 21, "v": 42, "w": "1" }, { "u": 21, "v": 46, "w": "5" }, { "u": 21, "v": 48, "w": "1" }, { "u": 21, "v": 49, "w": "1" }, { "u": 21, "v": 54, "w": "1" }, { "u": 21, "v": 55, "w": "2" }, { "u": 21, "v": 56, "w": "4" }, { "u": 21, "v": 57, "w": "2" }, { "u": 21, "v": 58, "w": "1" }, { "u": 22, "v": 1, "w": "1" }, { "u": 22, "v": 2, "w": "10" }, { "u": 22, "v": 3, "w": "15" }, { "u": 22, "v": 4, "w": "1" }, { "u": 22, "v": 6, "w": "3" }, { "u": 22, "v": 7, "w": "2" }, { "u": 22, "v": 9, "w": "5" }, { "u": 22, "v": 11, "w": "1" }, { "u": 22, "v": 12, "w": "1" }, { "u": 22, "v": 13, "w": "6" }, { "u": 22, "v": 14, "w": "1" }, { "u": 22, "v": 15, "w": "2" }, { "u": 22, "v": 16, "w": "2" }, { "u": 22, "v": 17, "w": "1" }, { "u": 22, "v": 20, "w": "2" }, { "u": 22, "v": 23, "w": "1" }, { "u": 22, "v": 24, "w": "1" }, { "u": 22, "v": 25, "w": "7" }, { "u": 22, "v": 26, "w": "2" }, { "u": 22, "v": 27, "w": "1" }, { "u": 22, "v": 29, "w": "3" }, { "u": 22, "v": 30, "w": "1" }, { "u": 22, "v": 35, "w": "1" }, { "u": 22, "v": 36, "w": "1" }, { "u": 22, "v": 37, "w": "1" }, { "u": 22, "v": 39, "w": "2" }, { "u": 22, "v": 44, "w": "1" }, { "u": 22, "v": 46, "w": "3" }, { "u": 22, "v": 49, "w": "2" }, { "u": 22, "v": 50, "w": "1" }, { "u": 22, "v": 54, "w": "2" }, { "u": 22, "v": 55, "w": "1" }, { "u": 22, "v": 56, "w": "1" }, { "u": 22, "v": 57, "w": "3" }, { "u": 23, "v": 2, "w": "2" }, { "u": 23, "v": 3, "w": "1" }, { "u": 23, "v": 4, "w": "1" }, { "u": 23, "v": 6, "w": "2" }, { "u": 23, "v": 7, "w": "5" }, { "u": 23, "v": 8, "w": "2" }, { "u": 23, "v": 13, "w": "2" }, { "u": 23, "v": 15, "w": "1" }, { "u": 23, "v": 19, "w": "2" }, { "u": 23, "v": 20, "w": "3" }, { "u": 23, "v": 21, "w": "1" }, { "u": 23, "v": 22, "w": "1" }, { "u": 23, "v": 25, "w": "1" }, { "u": 23, "v": 27, "w": "1" }, { "u": 23, "v": 29, "w": "2" }, { "u": 23, "v": 31, "w": "2" }, { "u": 23, "v": 33, "w": "3" }, { "u": 23, "v": 34, "w": "1" }, { "u": 23, "v": 35, "w": "2" }, { "u": 23, "v": 36, "w": "1" }, { "u": 23, "v": 37, "w": "2" }, { "u": 23, "v": 38, "w": "2" }, { "u": 23, "v": 39, "w": "2" }, { "u": 23, "v": 40, "w": "1" }, { "u": 23, "v": 41, "w": "7" }, { "u": 23, "v": 42, "w": "1" }, { "u": 23, "v": 44, "w": "1" }, { "u": 23, "v": 45, "w": "2" }, { "u": 23, "v": 47, "w": "2" }, { "u": 23, "v": 49, "w": "11" }, { "u": 23, "v": 50, "w": "1" }, { "u": 23, "v": 51, "w": "1" }, { "u": 23, "v": 53, "w": "1" }, { "u": 23, "v": 54, "w": "4" }, { "u": 23, "v": 55, "w": "1" }, { "u": 23, "v": 56, "w": "2" }, { "u": 23, "v": 57, "w": "3" }, { "u": 23, "v": 58, "w": "1" }, { "u": 24, "v": 3, "w": "2" }, { "u": 24, "v": 4, "w": "3" }, { "u": 24, "v": 6, "w": "1" }, { "u": 24, "v": 7, "w": "2" }, { "u": 24, "v": 8, "w": "3" }, { "u": 24, "v": 9, "w": "3" }, { "u": 24, "v": 11, "w": "3" }, { "u": 24, "v": 12, "w": "1" }, { "u": 24, "v": 15, "w": "2" }, { "u": 24, "v": 19, "w": "1" }, { "u": 24, "v": 20, "w": "2" }, { "u": 24, "v": 22, "w": "1" }, { "u": 24, "v": 26, "w": "1" }, { "u": 24, "v": 29, "w": "1" }, { "u": 24, "v": 30, "w": "1" }, { "u": 24, "v": 31, "w": "1" }, { "u": 24, "v": 34, "w": "2" }, { "u": 24, "v": 35, "w": "1" }, { "u": 24, "v": 36, "w": "1" }, { "u": 24, "v": 38, "w": "2" }, { "u": 24, "v": 44, "w": "1" }, { "u": 24, "v": 46, "w": "1" }, { "u": 24, "v": 48, "w": "1" }, { "u": 24, "v": 56, "w": "2" }, { "u": 24, "v": 57, "w": "1" }, { "u": 24, "v": 58, "w": "1" }, { "u": 25, "v": 2, "w": "4" }, { "u": 25, "v": 3, "w": "6" }, { "u": 25, "v": 4, "w": "1" }, { "u": 25, "v": 9, "w": "3" }, { "u": 25, "v": 12, "w": "1" }, { "u": 25, "v": 14, "w": "1" }, { "u": 25, "v": 15, "w": "1" }, { "u": 25, "v": 16, "w": "1" }, { "u": 25, "v": 17, "w": "1" }, { "u": 25, "v": 19, "w": "3" }, { "u": 25, "v": 20, "w": "1" }, { "u": 25, "v": 22, "w": "7" }, { "u": 25, "v": 23, "w": "1" }, { "u": 25, "v": 29, "w": "3" }, { "u": 25, "v": 30, "w": "1" }, { "u": 25, "v": 35, "w": "3" }, { "u": 25, "v": 37, "w": "1" }, { "u": 25, "v": 38, "w": "1" }, { "u": 25, "v": 41, "w": "4" }, { "u": 25, "v": 44, "w": "1" }, { "u": 25, "v": 53, "w": "2" }, { "u": 25, "v": 54, "w": "1" }, { "u": 25, "v": 55, "w": "1" }, { "u": 25, "v": 56, "w": "1" }, { "u": 25, "v": 57, "w": "5" }, { "u": 26, "v": 3, "w": "1" }, { "u": 26, "v": 6, "w": "1" }, { "u": 26, "v": 7, "w": "1" }, { "u": 26, "v": 8, "w": "1" }, { "u": 26, "v": 9, "w": "3" }, { "u": 26, "v": 14, "w": "1" }, { "u": 26, "v": 20, "w": "1" }, { "u": 26, "v": 22, "w": "2" }, { "u": 26, "v": 24, "w": "1" }, { "u": 26, "v": 27, "w": "1" }, { "u": 26, "v": 30, "w": "1" }, { "u": 26, "v": 35, "w": "1" }, { "u": 26, "v": 38, "w": "1" }, { "u": 26, "v": 39, "w": "3" }, { "u": 26, "v": 45, "w": "1" }, { "u": 26, "v": 48, "w": "1" }, { "u": 26, "v": 49, "w": "2" }, { "u": 26, "v": 52, "w": "2" }, { "u": 26, "v": 54, "w": "1" }, { "u": 26, "v": 55, "w": "1" }, { "u": 26, "v": 56, "w": "1" }, { "u": 26, "v": 57, "w": "2" }, { "u": 27, "v": 2, "w": "3" }, { "u": 27, "v": 3, "w": "5" }, { "u": 27, "v": 4, "w": "5" }, { "u": 27, "v": 5, "w": "8" }, { "u": 27, "v": 6, "w": "6" }, { "u": 27, "v": 7, "w": "19" }, { "u": 27, "v": 8, "w": "6" }, { "u": 27, "v": 9, "w": "3" }, { "u": 27, "v": 10, "w": "6" }, { "u": 27, "v": 11, "w": "4" }, { "u": 27, "v": 13, "w": "3" }, { "u": 27, "v": 14, "w": "4" }, { "u": 27, "v": 15, "w": "4" }, { "u": 27, "v": 16, "w": "7" }, { "u": 27, "v": 17, "w": "5" }, { "u": 27, "v": 20, "w": "7" }, { "u": 27, "v": 22, "w": "1" }, { "u": 27, "v": 23, "w": "1" }, { "u": 27, "v": 26, "w": "1" }, { "u": 27, "v": 29, "w": "6" }, { "u": 27, "v": 30, "w": "6" }, { "u": 27, "v": 31, "w": "2" }, { "u": 27, "v": 32, "w": "1" }, { "u": 27, "v": 33, "w": "1" }, { "u": 27, "v": 34, "w": "4" }, { "u": 27, "v": 36, "w": "1" }, { "u": 27, "v": 38, "w": "2" }, { "u": 27, "v": 39, "w": "4" }, { "u": 27, "v": 41, "w": "3" }, { "u": 27, "v": 42, "w": "2" }, { "u": 27, "v": 43, "w": "1" }, { "u": 27, "v": 44, "w": "1" }, { "u": 27, "v": 45, "w": "4" }, { "u": 27, "v": 46, "w": "1" }, { "u": 27, "v": 48, "w": "5" }, { "u": 27, "v": 49, "w": "2" }, { "u": 27, "v": 53, "w": "1" }, { "u": 27, "v": 54, "w": "2" }, { "u": 27, "v": 55, "w": "2" }, { "u": 27, "v": 56, "w": "4" }, { "u": 27, "v": 57, "w": "6" }, { "u": 27, "v": 58, "w": "2" }, { "u": 28, "v": 4, "w": "1" }, { "u": 28, "v": 7, "w": "1" }, { "u": 28, "v": 9, "w": "1" }, { "u": 28, "v": 20, "w": "1" }, { "u": 28, "v": 34, "w": "1" }, { "u": 28, "v": 53, "w": "1" }, { "u": 29, "v": 1, "w": "2" }, { "u": 29, "v": 2, "w": "1" }, { "u": 29, "v": 3, "w": "12" }, { "u": 29, "v": 4, "w": "1" }, { "u": 29, "v": 5, "w": "1" }, { "u": 29, "v": 6, "w": "3" }, { "u": 29, "v": 7, "w": "10" }, { "u": 29, "v": 8, "w": "2" }, { "u": 29, "v": 9, "w": "2" }, { "u": 29, "v": 10, "w": "1" }, { "u": 29, "v": 11, "w": "5" }, { "u": 29, "v": 12, "w": "2" }, { "u": 29, "v": 13, "w": "1" }, { "u": 29, "v": 14, "w": "8" }, { "u": 29, "v": 15, "w": "3" }, { "u": 29, "v": 16, "w": "5" }, { "u": 29, "v": 17, "w": "5" }, { "u": 29, "v": 18, "w": "1" }, { "u": 29, "v": 20, "w": "10" }, { "u": 29, "v": 21, "w": "2" }, { "u": 29, "v": 22, "w": "3" }, { "u": 29, "v": 23, "w": "2" }, { "u": 29, "v": 24, "w": "1" }, { "u": 29, "v": 25, "w": "3" }, { "u": 29, "v": 27, "w": "6" }, { "u": 29, "v": 30, "w": "3" }, { "u": 29, "v": 31, "w": "1" }, { "u": 29, "v": 35, "w": "20" }, { "u": 29, "v": 36, "w": "2" }, { "u": 29, "v": 37, "w": "2" }, { "u": 29, "v": 38, "w": "3" }, { "u": 29, "v": 39, "w": "3" }, { "u": 29, "v": 40, "w": "2" }, { "u": 29, "v": 41, "w": "1" }, { "u": 29, "v": 42, "w": "2" }, { "u": 29, "v": 44, "w": "3" }, { "u": 29, "v": 45, "w": "3" }, { "u": 29, "v": 47, "w": "1" }, { "u": 29, "v": 48, "w": "1" }, { "u": 29, "v": 49, "w": "1" }, { "u": 29, "v": 50, "w": "1" }, { "u": 29, "v": 54, "w": "7" }, { "u": 29, "v": 55, "w": "1" }, { "u": 29, "v": 56, "w": "2" }, { "u": 29, "v": 57, "w": "10" }, { "u": 29, "v": 58, "w": "1" }, { "u": 30, "v": 1, "w": "1" }, { "u": 30, "v": 2, "w": "1" }, { "u": 30, "v": 3, "w": "5" }, { "u": 30, "v": 4, "w": "3" }, { "u": 30, "v": 5, "w": "5" }, { "u": 30, "v": 6, "w": "11" }, { "u": 30, "v": 7, "w": "14" }, { "u": 30, "v": 9, "w": "3" }, { "u": 30, "v": 11, "w": "1" }, { "u": 30, "v": 14, "w": "4" }, { "u": 30, "v": 16, "w": "6" }, { "u": 30, "v": 17, "w": "2" }, { "u": 30, "v": 19, "w": "1" }, { "u": 30, "v": 20, "w": "6" }, { "u": 30, "v": 22, "w": "1" }, { "u": 30, "v": 24, "w": "1" }, { "u": 30, "v": 25, "w": "1" }, { "u": 30, "v": 26, "w": "1" }, { "u": 30, "v": 27, "w": "6" }, { "u": 30, "v": 29, "w": "3" }, { "u": 30, "v": 31, "w": "3" }, { "u": 30, "v": 33, "w": "1" }, { "u": 30, "v": 35, "w": "6" }, { "u": 30, "v": 36, "w": "1" }, { "u": 30, "v": 37, "w": "1" }, { "u": 30, "v": 38, "w": "1" }, { "u": 30, "v": 39, "w": "3" }, { "u": 30, "v": 40, "w": "1" }, { "u": 30, "v": 41, "w": "4" }, { "u": 30, "v": 42, "w": "1" }, { "u": 30, "v": 43, "w": "2" }, { "u": 30, "v": 45, "w": "1" }, { "u": 30, "v": 47, "w": "5" }, { "u": 30, "v": 48, "w": "1" }, { "u": 30, "v": 49, "w": "3" }, { "u": 30, "v": 50, "w": "1" }, { "u": 30, "v": 53, "w": "3" }, { "u": 30, "v": 54, "w": "2" }, { "u": 30, "v": 55, "w": "1" }, { "u": 30, "v": 56, "w": "6" }, { "u": 30, "v": 57, "w": "10" }, { "u": 30, "v": 58, "w": "2" }, { "u": 31, "v": 1, "w": "1" }, { "u": 31, "v": 3, "w": "4" }, { "u": 31, "v": 6, "w": "2" }, { "u": 31, "v": 7, "w": "5" }, { "u": 31, "v": 8, "w": "9" }, { "u": 31, "v": 9, "w": "1" }, { "u": 31, "v": 10, "w": "1" }, { "u": 31, "v": 11, "w": "3" }, { "u": 31, "v": 12, "w": "2" }, { "u": 31, "v": 14, "w": "6" }, { "u": 31, "v": 15, "w": "2" }, { "u": 31, "v": 16, "w": "5" }, { "u": 31, "v": 17, "w": "2" }, { "u": 31, "v": 19, "w": "1" }, { "u": 31, "v": 20, "w": "6" }, { "u": 31, "v": 21, "w": "2" }, { "u": 31, "v": 23, "w": "2" }, { "u": 31, "v": 24, "w": "1" }, { "u": 31, "v": 27, "w": "2" }, { "u": 31, "v": 29, "w": "1" }, { "u": 31, "v": 30, "w": "3" }, { "u": 31, "v": 32, "w": "4" }, { "u": 31, "v": 34, "w": "3" }, { "u": 31, "v": 35, "w": "1" }, { "u": 31, "v": 36, "w": "3" }, { "u": 31, "v": 38, "w": "1" }, { "u": 31, "v": 40, "w": "1" }, { "u": 31, "v": 41, "w": "3" }, { "u": 31, "v": 42, "w": "3" }, { "u": 31, "v": 45, "w": "1" }, { "u": 31, "v": 46, "w": "3" }, { "u": 31, "v": 48, "w": "2" }, { "u": 31, "v": 49, "w": "1" }, { "u": 31, "v": 53, "w": "1" }, { "u": 31, "v": 54, "w": "4" }, { "u": 31, "v": 55, "w": "1" }, { "u": 31, "v": 56, "w": "1" }, { "u": 31, "v": 57, "w": "3" }, { "u": 31, "v": 58, "w": "2" }, { "u": 32, "v": 1, "w": "1" }, { "u": 32, "v": 4, "w": "1" }, { "u": 32, "v": 7, "w": "3" }, { "u": 32, "v": 8, "w": "1" }, { "u": 32, "v": 15, "w": "1" }, { "u": 32, "v": 16, "w": "4" }, { "u": 32, "v": 17, "w": "4" }, { "u": 32, "v": 18, "w": "3" }, { "u": 32, "v": 20, "w": "1" }, { "u": 32, "v": 27, "w": "1" }, { "u": 32, "v": 31, "w": "4" }, { "u": 32, "v": 34, "w": "2" }, { "u": 32, "v": 41, "w": "1" }, { "u": 32, "v": 46, "w": "3" }, { "u": 32, "v": 48, "w": "6" }, { "u": 32, "v": 58, "w": "1" }, { "u": 33, "v": 3, "w": "1" }, { "u": 33, "v": 4, "w": "1" }, { "u": 33, "v": 5, "w": "1" }, { "u": 33, "v": 6, "w": "15" }, { "u": 33, "v": 7, "w": "14" }, { "u": 33, "v": 9, "w": "2" }, { "u": 33, "v": 12, "w": "1" }, { "u": 33, "v": 13, "w": "1" }, { "u": 33, "v": 14, "w": "3" }, { "u": 33, "v": 15, "w": "1" }, { "u": 33, "v": 16, "w": "2" }, { "u": 33, "v": 17, "w": "2" }, { "u": 33, "v": 20, "w": "13" }, { "u": 33, "v": 23, "w": "3" }, { "u": 33, "v": 27, "w": "1" }, { "u": 33, "v": 30, "w": "1" }, { "u": 33, "v": 34, "w": "1" }, { "u": 33, "v": 35, "w": "1" }, { "u": 33, "v": 36, "w": "1" }, { "u": 33, "v": 40, "w": "3" }, { "u": 33, "v": 41, "w": "1" }, { "u": 33, "v": 49, "w": "1" }, { "u": 33, "v": 52, "w": "2" }, { "u": 33, "v": 53, "w": "8" }, { "u": 33, "v": 54, "w": "1" }, { "u": 33, "v": 56, "w": "1" }, { "u": 33, "v": 57, "w": "3" }, { "u": 34, "v": 1, "w": "2" }, { "u": 34, "v": 3, "w": "4" }, { "u": 34, "v": 4, "w": "4" }, { "u": 34, "v": 6, "w": "5" }, { "u": 34, "v": 7, "w": "7" }, { "u": 34, "v": 8, "w": "1" }, { "u": 34, "v": 9, "w": "4" }, { "u": 34, "v": 11, "w": "2" }, { "u": 34, "v": 12, "w": "2" }, { "u": 34, "v": 13, "w": "1" }, { "u": 34, "v": 14, "w": "1" }, { "u": 34, "v": 15, "w": "4" }, { "u": 34, "v": 16, "w": "2" }, { "u": 34, "v": 17, "w": "1" }, { "u": 34, "v": 18, "w": "1" }, { "u": 34, "v": 19, "w": "1" }, { "u": 34, "v": 20, "w": "12" }, { "u": 34, "v": 21, "w": "1" }, { "u": 34, "v": 23, "w": "1" }, { "u": 34, "v": 24, "w": "2" }, { "u": 34, "v": 27, "w": "4" }, { "u": 34, "v": 28, "w": "1" }, { "u": 34, "v": 31, "w": "3" }, { "u": 34, "v": 32, "w": "2" }, { "u": 34, "v": 33, "w": "1" }, { "u": 34, "v": 35, "w": "3" }, { "u": 34, "v": 36, "w": "1" }, { "u": 34, "v": 39, "w": "1" }, { "u": 34, "v": 40, "w": "1" }, { "u": 34, "v": 41, "w": "2" }, { "u": 34, "v": 42, "w": "1" }, { "u": 34, "v": 46, "w": "3" }, { "u": 34, "v": 47, "w": "2" }, { "u": 34, "v": 48, "w": "2" }, { "u": 34, "v": 49, "w": "1" }, { "u": 34, "v": 50, "w": "3" }, { "u": 34, "v": 53, "w": "2" }, { "u": 34, "v": 54, "w": "4" }, { "u": 34, "v": 55, "w": "3" }, { "u": 34, "v": 56, "w": "4" }, { "u": 34, "v": 57, "w": "3" }, { "u": 34, "v": 58, "w": "6" }, { "u": 35, "v": 1, "w": "1" }, { "u": 35, "v": 2, "w": "5" }, { "u": 35, "v": 3, "w": "15" }, { "u": 35, "v": 4, "w": "1" }, { "u": 35, "v": 6, "w": "3" }, { "u": 35, "v": 7, "w": "7" }, { "u": 35, "v": 8, "w": "2" }, { "u": 35, "v": 9, "w": "4" }, { "u": 35, "v": 11, "w": "2" }, { "u": 35, "v": 12, "w": "3" }, { "u": 35, "v": 13, "w": "1" }, { "u": 35, "v": 14, "w": "5" }, { "u": 35, "v": 15, "w": "2" }, { "u": 35, "v": 16, "w": "2" }, { "u": 35, "v": 17, "w": "4" }, { "u": 35, "v": 18, "w": "1" }, { "u": 35, "v": 19, "w": "1" }, { "u": 35, "v": 20, "w": "9" }, { "u": 35, "v": 21, "w": "1" }, { "u": 35, "v": 22, "w": "1" }, { "u": 35, "v": 23, "w": "2" }, { "u": 35, "v": 24, "w": "1" }, { "u": 35, "v": 25, "w": "3" }, { "u": 35, "v": 26, "w": "1" }, { "u": 35, "v": 29, "w": "20" }, { "u": 35, "v": 30, "w": "6" }, { "u": 35, "v": 31, "w": "1" }, { "u": 35, "v": 33, "w": "1" }, { "u": 35, "v": 34, "w": "3" }, { "u": 35, "v": 36, "w": "2" }, { "u": 35, "v": 37, "w": "1" }, { "u": 35, "v": 38, "w": "3" }, { "u": 35, "v": 39, "w": "2" }, { "u": 35, "v": 40, "w": "2" }, { "u": 35, "v": 41, "w": "3" }, { "u": 35, "v": 42, "w": "4" }, { "u": 35, "v": 43, "w": "2" }, { "u": 35, "v": 44, "w": "2" }, { "u": 35, "v": 47, "w": "1" }, { "u": 35, "v": 49, "w": "6" }, { "u": 35, "v": 50, "w": "1" }, { "u": 35, "v": 53, "w": "1" }, { "u": 35, "v": 54, "w": "12" }, { "u": 35, "v": 55, "w": "2" }, { "u": 35, "v": 56, "w": "3" }, { "u": 35, "v": 57, "w": "6" }, { "u": 35, "v": 58, "w": "2" }, { "u": 36, "v": 1, "w": "2" }, { "u": 36, "v": 2, "w": "1" }, { "u": 36, "v": 3, "w": "3" }, { "u": 36, "v": 6, "w": "1" }, { "u": 36, "v": 7, "w": "5" }, { "u": 36, "v": 8, "w": "4" }, { "u": 36, "v": 9, "w": "3" }, { "u": 36, "v": 11, "w": "3" }, { "u": 36, "v": 12, "w": "2" }, { "u": 36, "v": 14, "w": "1" }, { "u": 36, "v": 15, "w": "3" }, { "u": 36, "v": 16, "w": "2" }, { "u": 36, "v": 17, "w": "2" }, { "u": 36, "v": 18, "w": "1" }, { "u": 36, "v": 19, "w": "1" }, { "u": 36, "v": 20, "w": "2" }, { "u": 36, "v": 21, "w": "1" }, { "u": 36, "v": 22, "w": "1" }, { "u": 36, "v": 23, "w": "1" }, { "u": 36, "v": 24, "w": "1" }, { "u": 36, "v": 27, "w": "1" }, { "u": 36, "v": 29, "w": "2" }, { "u": 36, "v": 30, "w": "1" }, { "u": 36, "v": 31, "w": "3" }, { "u": 36, "v": 33, "w": "1" }, { "u": 36, "v": 34, "w": "1" }, { "u": 36, "v": 35, "w": "2" }, { "u": 36, "v": 39, "w": "1" }, { "u": 36, "v": 41, "w": "1" }, { "u": 36, "v": 42, "w": "2" }, { "u": 36, "v": 44, "w": "1" }, { "u": 36, "v": 46, "w": "3" }, { "u": 36, "v": 49, "w": "3" }, { "u": 36, "v": 53, "w": "1" }, { "u": 36, "v": 55, "w": "2" }, { "u": 36, "v": 56, "w": "10" }, { "u": 36, "v": 57, "w": "1" }, { "u": 36, "v": 58, "w": "1" }, { "u": 37, "v": 3, "w": "1" }, { "u": 37, "v": 4, "w": "1" }, { "u": 37, "v": 7, "w": "3" }, { "u": 37, "v": 8, "w": "2" }, { "u": 37, "v": 9, "w": "5" }, { "u": 37, "v": 11, "w": "5" }, { "u": 37, "v": 12, "w": "2" }, { "u": 37, "v": 14, "w": "1" }, { "u": 37, "v": 18, "w": "1" }, { "u": 37, "v": 19, "w": "1" }, { "u": 37, "v": 20, "w": "1" }, { "u": 37, "v": 22, "w": "1" }, { "u": 37, "v": 23, "w": "2" }, { "u": 37, "v": 25, "w": "1" }, { "u": 37, "v": 29, "w": "2" }, { "u": 37, "v": 30, "w": "1" }, { "u": 37, "v": 35, "w": "1" }, { "u": 37, "v": 39, "w": "3" }, { "u": 37, "v": 41, "w": "1" }, { "u": 37, "v": 45, "w": "1" }, { "u": 37, "v": 47, "w": "4" }, { "u": 37, "v": 49, "w": "2" }, { "u": 37, "v": 51, "w": "1" }, { "u": 37, "v": 53, "w": "2" }, { "u": 37, "v": 54, "w": "1" }, { "u": 37, "v": 56, "w": "1" }, { "u": 37, "v": 57, "w": "3" }, { "u": 38, "v": 2, "w": "4" }, { "u": 38, "v": 3, "w": "3" }, { "u": 38, "v": 4, "w": "3" }, { "u": 38, "v": 6, "w": "2" }, { "u": 38, "v": 7, "w": "4" }, { "u": 38, "v": 8, "w": "5" }, { "u": 38, "v": 9, "w": "1" }, { "u": 38, "v": 10, "w": "1" }, { "u": 38, "v": 11, "w": "3" }, { "u": 38, "v": 12, "w": "1" }, { "u": 38, "v": 13, "w": "1" }, { "u": 38, "v": 15, "w": "6" }, { "u": 38, "v": 16, "w": "4" }, { "u": 38, "v": 17, "w": "1" }, { "u": 38, "v": 19, "w": "2" }, { "u": 38, "v": 20, "w": "6" }, { "u": 38, "v": 21, "w": "1" }, { "u": 38, "v": 23, "w": "2" }, { "u": 38, "v": 24, "w": "2" }, { "u": 38, "v": 25, "w": "1" }, { "u": 38, "v": 26, "w": "1" }, { "u": 38, "v": 27, "w": "2" }, { "u": 38, "v": 29, "w": "3" }, { "u": 38, "v": 30, "w": "1" }, { "u": 38, "v": 31, "w": "1" }, { "u": 38, "v": 35, "w": "3" }, { "u": 38, "v": 40, "w": "1" }, { "u": 38, "v": 41, "w": "2" }, { "u": 38, "v": 42, "w": "1" }, { "u": 38, "v": 45, "w": "1" }, { "u": 38, "v": 47, "w": "2" }, { "u": 38, "v": 50, "w": "1" }, { "u": 38, "v": 53, "w": "1" }, { "u": 38, "v": 54, "w": "6" }, { "u": 38, "v": 55, "w": "1" }, { "u": 38, "v": 56, "w": "1" }, { "u": 38, "v": 57, "w": "4" }, { "u": 38, "v": 58, "w": "2" }, { "u": 39, "v": 3, "w": "6" }, { "u": 39, "v": 4, "w": "2" }, { "u": 39, "v": 5, "w": "9" }, { "u": 39, "v": 6, "w": "2" }, { "u": 39, "v": 7, "w": "5" }, { "u": 39, "v": 8, "w": "1" }, { "u": 39, "v": 9, "w": "2" }, { "u": 39, "v": 10, "w": "2" }, { "u": 39, "v": 11, "w": "2" }, { "u": 39, "v": 13, "w": "1" }, { "u": 39, "v": 15, "w": "1" }, { "u": 39, "v": 16, "w": "4" }, { "u": 39, "v": 17, "w": "1" }, { "u": 39, "v": 18, "w": "1" }, { "u": 39, "v": 20, "w": "2" }, { "u": 39, "v": 22, "w": "2" }, { "u": 39, "v": 23, "w": "2" }, { "u": 39, "v": 26, "w": "3" }, { "u": 39, "v": 27, "w": "4" }, { "u": 39, "v": 29, "w": "3" }, { "u": 39, "v": 30, "w": "3" }, { "u": 39, "v": 34, "w": "1" }, { "u": 39, "v": 35, "w": "2" }, { "u": 39, "v": 36, "w": "1" }, { "u": 39, "v": 37, "w": "3" }, { "u": 39, "v": 40, "w": "1" }, { "u": 39, "v": 44, "w": "4" }, { "u": 39, "v": 45, "w": "9" }, { "u": 39, "v": 46, "w": "2" }, { "u": 39, "v": 47, "w": "1" }, { "u": 39, "v": 48, "w": "2" }, { "u": 39, "v": 49, "w": "5" }, { "u": 39, "v": 50, "w": "4" }, { "u": 39, "v": 51, "w": "3" }, { "u": 39, "v": 54, "w": "2" }, { "u": 39, "v": 55, "w": "2" }, { "u": 39, "v": 56, "w": "1" }, { "u": 39, "v": 57, "w": "2" }, { "u": 40, "v": 5, "w": "2" }, { "u": 40, "v": 6, "w": "1" }, { "u": 40, "v": 7, "w": "7" }, { "u": 40, "v": 10, "w": "2" }, { "u": 40, "v": 13, "w": "1" }, { "u": 40, "v": 17, "w": "1" }, { "u": 40, "v": 19, "w": "1" }, { "u": 40, "v": 20, "w": "1" }, { "u": 40, "v": 23, "w": "1" }, { "u": 40, "v": 29, "w": "2" }, { "u": 40, "v": 30, "w": "1" }, { "u": 40, "v": 31, "w": "1" }, { "u": 40, "v": 33, "w": "3" }, { "u": 40, "v": 34, "w": "1" }, { "u": 40, "v": 35, "w": "2" }, { "u": 40, "v": 38, "w": "1" }, { "u": 40, "v": 39, "w": "1" }, { "u": 40, "v": 47, "w": "1" }, { "u": 40, "v": 53, "w": "1" }, { "u": 40, "v": 54, "w": "2" }, { "u": 40, "v": 57, "w": "2" }, { "u": 41, "v": 1, "w": "1" }, { "u": 41, "v": 3, "w": "2" }, { "u": 41, "v": 4, "w": "1" }, { "u": 41, "v": 5, "w": "1" }, { "u": 41, "v": 6, "w": "3" }, { "u": 41, "v": 7, "w": "8" }, { "u": 41, "v": 8, "w": "3" }, { "u": 41, "v": 9, "w": "1" }, { "u": 41, "v": 13, "w": "1" }, { "u": 41, "v": 14, "w": "1" }, { "u": 41, "v": 15, "w": "7" }, { "u": 41, "v": 16, "w": "2" }, { "u": 41, "v": 17, "w": "2" }, { "u": 41, "v": 19, "w": "14" }, { "u": 41, "v": 20, "w": "10" }, { "u": 41, "v": 21, "w": "1" }, { "u": 41, "v": 23, "w": "7" }, { "u": 41, "v": 25, "w": "4" }, { "u": 41, "v": 27, "w": "3" }, { "u": 41, "v": 29, "w": "1" }, { "u": 41, "v": 30, "w": "4" }, { "u": 41, "v": 31, "w": "3" }, { "u": 41, "v": 32, "w": "1" }, { "u": 41, "v": 33, "w": "1" }, { "u": 41, "v": 34, "w": "2" }, { "u": 41, "v": 35, "w": "3" }, { "u": 41, "v": 36, "w": "1" }, { "u": 41, "v": 37, "w": "1" }, { "u": 41, "v": 38, "w": "2" }, { "u": 41, "v": 42, "w": "1" }, { "u": 41, "v": 43, "w": "1" }, { "u": 41, "v": 44, "w": "1" }, { "u": 41, "v": 45, "w": "1" }, { "u": 41, "v": 49, "w": "9" }, { "u": 41, "v": 53, "w": "4" }, { "u": 41, "v": 54, "w": "1" }, { "u": 41, "v": 55, "w": "1" }, { "u": 41, "v": 56, "w": "5" }, { "u": 41, "v": 57, "w": "1" }, { "u": 41, "v": 58, "w": "2" }, { "u": 42, "v": 3, "w": "3" }, { "u": 42, "v": 6, "w": "1" }, { "u": 42, "v": 7, "w": "5" }, { "u": 42, "v": 8, "w": "5" }, { "u": 42, "v": 9, "w": "1" }, { "u": 42, "v": 11, "w": "1" }, { "u": 42, "v": 12, "w": "1" }, { "u": 42, "v": 13, "w": "1" }, { "u": 42, "v": 14, "w": "3" }, { "u": 42, "v": 15, "w": "1" }, { "u": 42, "v": 16, "w": "5" }, { "u": 42, "v": 17, "w": "3" }, { "u": 42, "v": 19, "w": "1" }, { "u": 42, "v": 20, "w": "4" }, { "u": 42, "v": 21, "w": "1" }, { "u": 42, "v": 23, "w": "1" }, { "u": 42, "v": 27, "w": "2" }, { "u": 42, "v": 29, "w": "2" }, { "u": 42, "v": 30, "w": "1" }, { "u": 42, "v": 31, "w": "3" }, { "u": 42, "v": 34, "w": "1" }, { "u": 42, "v": 35, "w": "4" }, { "u": 42, "v": 36, "w": "2" }, { "u": 42, "v": 38, "w": "1" }, { "u": 42, "v": 41, "w": "1" }, { "u": 42, "v": 43, "w": "1" }, { "u": 42, "v": 44, "w": "1" }, { "u": 42, "v": 45, "w": "1" }, { "u": 42, "v": 46, "w": "1" }, { "u": 42, "v": 47, "w": "1" }, { "u": 42, "v": 48, "w": "1" }, { "u": 42, "v": 49, "w": "1" }, { "u": 42, "v": 53, "w": "2" }, { "u": 42, "v": 54, "w": "1" }, { "u": 42, "v": 55, "w": "1" }, { "u": 42, "v": 57, "w": "3" }, { "u": 42, "v": 58, "w": "1" }, { "u": 43, "v": 5, "w": "1" }, { "u": 43, "v": 9, "w": "1" }, { "u": 43, "v": 13, "w": "1" }, { "u": 43, "v": 16, "w": "1" }, { "u": 43, "v": 27, "w": "1" }, { "u": 43, "v": 30, "w": "2" }, { "u": 43, "v": 35, "w": "2" }, { "u": 43, "v": 41, "w": "1" }, { "u": 43, "v": 42, "w": "1" }, { "u": 43, "v": 55, "w": "1" }, { "u": 44, "v": 2, "w": "1" }, { "u": 44, "v": 3, "w": "9" }, { "u": 44, "v": 4, "w": "1" }, { "u": 44, "v": 5, "w": "8" }, { "u": 44, "v": 6, "w": "3" }, { "u": 44, "v": 7, "w": "2" }, { "u": 44, "v": 9, "w": "2" }, { "u": 44, "v": 11, "w": "2" }, { "u": 44, "v": 12, "w": "1" }, { "u": 44, "v": 14, "w": "2" }, { "u": 44, "v": 15, "w": "7" }, { "u": 44, "v": 16, "w": "3" }, { "u": 44, "v": 17, "w": "3" }, { "u": 44, "v": 18, "w": "1" }, { "u": 44, "v": 19, "w": "1" }, { "u": 44, "v": 20, "w": "2" }, { "u": 44, "v": 22, "w": "1" }, { "u": 44, "v": 23, "w": "1" }, { "u": 44, "v": 24, "w": "1" }, { "u": 44, "v": 25, "w": "1" }, { "u": 44, "v": 27, "w": "1" }, { "u": 44, "v": 29, "w": "3" }, { "u": 44, "v": 35, "w": "2" }, { "u": 44, "v": 36, "w": "1" }, { "u": 44, "v": 39, "w": "4" }, { "u": 44, "v": 41, "w": "1" }, { "u": 44, "v": 42, "w": "1" }, { "u": 44, "v": 45, "w": "2" }, { "u": 44, "v": 47, "w": "1" }, { "u": 44, "v": 49, "w": "2" }, { "u": 44, "v": 50, "w": "1" }, { "u": 44, "v": 54, "w": "2" }, { "u": 44, "v": 55, "w": "1" }, { "u": 44, "v": 56, "w": "2" }, { "u": 44, "v": 57, "w": "3" }, { "u": 45, "v": 2, "w": "1" }, { "u": 45, "v": 3, "w": "8" }, { "u": 45, "v": 4, "w": "1" }, { "u": 45, "v": 5, "w": "25" }, { "u": 45, "v": 6, "w": "2" }, { "u": 45, "v": 7, "w": "4" }, { "u": 45, "v": 8, "w": "5" }, { "u": 45, "v": 11, "w": "1" }, { "u": 45, "v": 12, "w": "1" }, { "u": 45, "v": 13, "w": "2" }, { "u": 45, "v": 14, "w": "2" }, { "u": 45, "v": 15, "w": "1" }, { "u": 45, "v": 16, "w": "2" }, { "u": 45, "v": 17, "w": "1" }, { "u": 45, "v": 20, "w": "2" }, { "u": 45, "v": 23, "w": "2" }, { "u": 45, "v": 26, "w": "1" }, { "u": 45, "v": 27, "w": "4" }, { "u": 45, "v": 29, "w": "3" }, { "u": 45, "v": 30, "w": "1" }, { "u": 45, "v": 31, "w": "1" }, { "u": 45, "v": 37, "w": "1" }, { "u": 45, "v": 38, "w": "1" }, { "u": 45, "v": 39, "w": "9" }, { "u": 45, "v": 41, "w": "1" }, { "u": 45, "v": 42, "w": "1" }, { "u": 45, "v": 44, "w": "2" }, { "u": 45, "v": 47, "w": "1" }, { "u": 45, "v": 48, "w": "2" }, { "u": 45, "v": 49, "w": "4" }, { "u": 45, "v": 50, "w": "1" }, { "u": 45, "v": 51, "w": "1" }, { "u": 45, "v": 54, "w": "4" }, { "u": 45, "v": 57, "w": "1" }, { "u": 46, "v": 3, "w": "2" }, { "u": 46, "v": 4, "w": "1" }, { "u": 46, "v": 6, "w": "2" }, { "u": 46, "v": 7, "w": "7" }, { "u": 46, "v": 10, "w": "1" }, { "u": 46, "v": 11, "w": "4" }, { "u": 46, "v": 12, "w": "1" }, { "u": 46, "v": 13, "w": "1" }, { "u": 46, "v": 14, "w": "1" }, { "u": 46, "v": 15, "w": "1" }, { "u": 46, "v": 16, "w": "1" }, { "u": 46, "v": 18, "w": "2" }, { "u": 46, "v": 20, "w": "1" }, { "u": 46, "v": 21, "w": "5" }, { "u": 46, "v": 22, "w": "3" }, { "u": 46, "v": 24, "w": "1" }, { "u": 46, "v": 27, "w": "1" }, { "u": 46, "v": 31, "w": "3" }, { "u": 46, "v": 32, "w": "3" }, { "u": 46, "v": 34, "w": "3" }, { "u": 46, "v": 36, "w": "3" }, { "u": 46, "v": 39, "w": "2" }, { "u": 46, "v": 42, "w": "1" }, { "u": 46, "v": 48, "w": "5" }, { "u": 46, "v": 49, "w": "1" }, { "u": 46, "v": 55, "w": "1" }, { "u": 46, "v": 56, "w": "2" }, { "u": 46, "v": 57, "w": "4" }, { "u": 46, "v": 58, "w": "1" }, { "u": 47, "v": 3, "w": "1" }, { "u": 47, "v": 4, "w": "1" }, { "u": 47, "v": 6, "w": "6" }, { "u": 47, "v": 7, "w": "3" }, { "u": 47, "v": 8, "w": "1" }, { "u": 47, "v": 9, "w": "4" }, { "u": 47, "v": 11, "w": "5" }, { "u": 47, "v": 14, "w": "1" }, { "u": 47, "v": 16, "w": "1" }, { "u": 47, "v": 19, "w": "1" }, { "u": 47, "v": 20, "w": "2" }, { "u": 47, "v": 23, "w": "2" }, { "u": 47, "v": 29, "w": "1" }, { "u": 47, "v": 30, "w": "5" }, { "u": 47, "v": 34, "w": "2" }, { "u": 47, "v": 35, "w": "1" }, { "u": 47, "v": 37, "w": "4" }, { "u": 47, "v": 38, "w": "2" }, { "u": 47, "v": 39, "w": "1" }, { "u": 47, "v": 40, "w": "1" }, { "u": 47, "v": 42, "w": "1" }, { "u": 47, "v": 44, "w": "1" }, { "u": 47, "v": 45, "w": "1" }, { "u": 47, "v": 48, "w": "1" }, { "u": 47, "v": 49, "w": "2" }, { "u": 47, "v": 51, "w": "2" }, { "u": 47, "v": 53, "w": "3" }, { "u": 47, "v": 56, "w": "2" }, { "u": 47, "v": 57, "w": "6" }, { "u": 47, "v": 58, "w": "1" }, { "u": 48, "v": 2, "w": "5" }, { "u": 48, "v": 3, "w": "3" }, { "u": 48, "v": 4, "w": "2" }, { "u": 48, "v": 6, "w": "1" }, { "u": 48, "v": 7, "w": "7" }, { "u": 48, "v": 8, "w": "3" }, { "u": 48, "v": 11, "w": "2" }, { "u": 48, "v": 14, "w": "1" }, { "u": 48, "v": 16, "w": "4" }, { "u": 48, "v": 17, "w": "3" }, { "u": 48, "v": 18, "w": "2" }, { "u": 48, "v": 20, "w": "1" }, { "u": 48, "v": 21, "w": "1" }, { "u": 48, "v": 24, "w": "1" }, { "u": 48, "v": 26, "w": "1" }, { "u": 48, "v": 27, "w": "5" }, { "u": 48, "v": 29, "w": "1" }, { "u": 48, "v": 30, "w": "1" }, { "u": 48, "v": 31, "w": "2" }, { "u": 48, "v": 32, "w": "6" }, { "u": 48, "v": 34, "w": "2" }, { "u": 48, "v": 39, "w": "2" }, { "u": 48, "v": 42, "w": "1" }, { "u": 48, "v": 45, "w": "2" }, { "u": 48, "v": 46, "w": "5" }, { "u": 48, "v": 47, "w": "1" }, { "u": 48, "v": 49, "w": "3" }, { "u": 48, "v": 50, "w": "2" }, { "u": 48, "v": 54, "w": "2" }, { "u": 48, "v": 55, "w": "1" }, { "u": 48, "v": 57, "w": "2" }, { "u": 49, "v": 1, "w": "1" }, { "u": 49, "v": 2, "w": "3" }, { "u": 49, "v": 3, "w": "6" }, { "u": 49, "v": 4, "w": "1" }, { "u": 49, "v": 6, "w": "3" }, { "u": 49, "v": 7, "w": "7" }, { "u": 49, "v": 8, "w": "1" }, { "u": 49, "v": 9, "w": "1" }, { "u": 49, "v": 11, "w": "1" }, { "u": 49, "v": 12, "w": "1" }, { "u": 49, "v": 13, "w": "2" }, { "u": 49, "v": 15, "w": "1" }, { "u": 49, "v": 17, "w": "1" }, { "u": 49, "v": 19, "w": "3" }, { "u": 49, "v": 20, "w": "6" }, { "u": 49, "v": 21, "w": "1" }, { "u": 49, "v": 22, "w": "2" }, { "u": 49, "v": 23, "w": "11" }, { "u": 49, "v": 26, "w": "2" }, { "u": 49, "v": 27, "w": "2" }, { "u": 49, "v": 29, "w": "1" }, { "u": 49, "v": 30, "w": "3" }, { "u": 49, "v": 31, "w": "1" }, { "u": 49, "v": 33, "w": "1" }, { "u": 49, "v": 34, "w": "1" }, { "u": 49, "v": 35, "w": "6" }, { "u": 49, "v": 36, "w": "3" }, { "u": 49, "v": 37, "w": "2" }, { "u": 49, "v": 39, "w": "5" }, { "u": 49, "v": 41, "w": "9" }, { "u": 49, "v": 42, "w": "1" }, { "u": 49, "v": 44, "w": "2" }, { "u": 49, "v": 45, "w": "4" }, { "u": 49, "v": 46, "w": "1" }, { "u": 49, "v": 47, "w": "2" }, { "u": 49, "v": 48, "w": "3" }, { "u": 49, "v": 50, "w": "4" }, { "u": 49, "v": 52, "w": "1" }, { "u": 49, "v": 53, "w": "4" }, { "u": 49, "v": 54, "w": "4" }, { "u": 49, "v": 55, "w": "2" }, { "u": 49, "v": 56, "w": "2" }, { "u": 49, "v": 57, "w": "3" }, { "u": 49, "v": 58, "w": "1" }, { "u": 50, "v": 3, "w": "2" }, { "u": 50, "v": 4, "w": "3" }, { "u": 50, "v": 6, "w": "4" }, { "u": 50, "v": 7, "w": "2" }, { "u": 50, "v": 8, "w": "1" }, { "u": 50, "v": 9, "w": "4" }, { "u": 50, "v": 12, "w": "2" }, { "u": 50, "v": 15, "w": "1" }, { "u": 50, "v": 16, "w": "2" }, { "u": 50, "v": 17, "w": "2" }, { "u": 50, "v": 20, "w": "1" }, { "u": 50, "v": 22, "w": "1" }, { "u": 50, "v": 23, "w": "1" }, { "u": 50, "v": 29, "w": "1" }, { "u": 50, "v": 30, "w": "1" }, { "u": 50, "v": 34, "w": "3" }, { "u": 50, "v": 35, "w": "1" }, { "u": 50, "v": 38, "w": "1" }, { "u": 50, "v": 39, "w": "4" }, { "u": 50, "v": 44, "w": "1" }, { "u": 50, "v": 45, "w": "1" }, { "u": 50, "v": 48, "w": "2" }, { "u": 50, "v": 49, "w": "4" }, { "u": 50, "v": 51, "w": "1" }, { "u": 50, "v": 54, "w": "1" }, { "u": 50, "v": 55, "w": "1" }, { "u": 50, "v": 56, "w": "1" }, { "u": 50, "v": 58, "w": "3" }, { "u": 51, "v": 23, "w": "1" }, { "u": 51, "v": 37, "w": "1" }, { "u": 51, "v": 39, "w": "3" }, { "u": 51, "v": 45, "w": "1" }, { "u": 51, "v": 47, "w": "2" }, { "u": 51, "v": 50, "w": "1" }, { "u": 52, "v": 3, "w": "2" }, { "u": 52, "v": 6, "w": "2" }, { "u": 52, "v": 8, "w": "1" }, { "u": 52, "v": 9, "w": "6" }, { "u": 52, "v": 26, "w": "2" }, { "u": 52, "v": 33, "w": "2" }, { "u": 52, "v": 49, "w": "1" }, { "u": 53, "v": 3, "w": "2" }, { "u": 53, "v": 4, "w": "2" }, { "u": 53, "v": 5, "w": "1" }, { "u": 53, "v": 6, "w": "8" }, { "u": 53, "v": 7, "w": "6" }, { "u": 53, "v": 8, "w": "2" }, { "u": 53, "v": 9, "w": "1" }, { "u": 53, "v": 12, "w": "1" }, { "u": 53, "v": 13, "w": "2" }, { "u": 53, "v": 14, "w": "1" }, { "u": 53, "v": 15, "w": "7" }, { "u": 53, "v": 16, "w": "8" }, { "u": 53, "v": 17, "w": "7" }, { "u": 53, "v": 19, "w": "1" }, { "u": 53, "v": 20, "w": "12" }, { "u": 53, "v": 23, "w": "1" }, { "u": 53, "v": 25, "w": "2" }, { "u": 53, "v": 27, "w": "1" }, { "u": 53, "v": 28, "w": "1" }, { "u": 53, "v": 30, "w": "3" }, { "u": 53, "v": 31, "w": "1" }, { "u": 53, "v": 33, "w": "8" }, { "u": 53, "v": 34, "w": "2" }, { "u": 53, "v": 35, "w": "1" }, { "u": 53, "v": 36, "w": "1" }, { "u": 53, "v": 37, "w": "2" }, { "u": 53, "v": 38, "w": "1" }, { "u": 53, "v": 40, "w": "1" }, { "u": 53, "v": 41, "w": "4" }, { "u": 53, "v": 42, "w": "2" }, { "u": 53, "v": 47, "w": "3" }, { "u": 53, "v": 49, "w": "4" }, { "u": 53, "v": 54, "w": "5" }, { "u": 53, "v": 55, "w": "1" }, { "u": 53, "v": 56, "w": "2" }, { "u": 53, "v": 57, "w": "4" }, { "u": 53, "v": 58, "w": "3" }, { "u": 54, "v": 1, "w": "1" }, { "u": 54, "v": 2, "w": "1" }, { "u": 54, "v": 3, "w": "16" }, { "u": 54, "v": 4, "w": "1" }, { "u": 54, "v": 5, "w": "2" }, { "u": 54, "v": 6, "w": "9" }, { "u": 54, "v": 7, "w": "5" }, { "u": 54, "v": 8, "w": "5" }, { "u": 54, "v": 9, "w": "4" }, { "u": 54, "v": 10, "w": "1" }, { "u": 54, "v": 11, "w": "4" }, { "u": 54, "v": 12, "w": "2" }, { "u": 54, "v": 13, "w": "4" }, { "u": 54, "v": 15, "w": "6" }, { "u": 54, "v": 16, "w": "4" }, { "u": 54, "v": 17, "w": "7" }, { "u": 54, "v": 20, "w": "17" }, { "u": 54, "v": 21, "w": "1" }, { "u": 54, "v": 22, "w": "2" }, { "u": 54, "v": 23, "w": "4" }, { "u": 54, "v": 25, "w": "1" }, { "u": 54, "v": 26, "w": "1" }, { "u": 54, "v": 27, "w": "2" }, { "u": 54, "v": 29, "w": "7" }, { "u": 54, "v": 30, "w": "2" }, { "u": 54, "v": 31, "w": "4" }, { "u": 54, "v": 33, "w": "1" }, { "u": 54, "v": 34, "w": "4" }, { "u": 54, "v": 35, "w": "12" }, { "u": 54, "v": 37, "w": "1" }, { "u": 54, "v": 38, "w": "6" }, { "u": 54, "v": 39, "w": "2" }, { "u": 54, "v": 40, "w": "2" }, { "u": 54, "v": 41, "w": "1" }, { "u": 54, "v": 42, "w": "1" }, { "u": 54, "v": 44, "w": "2" }, { "u": 54, "v": 45, "w": "4" }, { "u": 54, "v": 48, "w": "2" }, { "u": 54, "v": 49, "w": "4" }, { "u": 54, "v": 50, "w": "1" }, { "u": 54, "v": 53, "w": "5" }, { "u": 54, "v": 55, "w": "5" }, { "u": 54, "v": 56, "w": "3" }, { "u": 54, "v": 57, "w": "10" }, { "u": 55, "v": 1, "w": "1" }, { "u": 55, "v": 3, "w": "4" }, { "u": 55, "v": 4, "w": "2" }, { "u": 55, "v": 6, "w": "3" }, { "u": 55, "v": 7, "w": "14" }, { "u": 55, "v": 9, "w": "3" }, { "u": 55, "v": 11, "w": "6" }, { "u": 55, "v": 13, "w": "1" }, { "u": 55, "v": 14, "w": "2" }, { "u": 55, "v": 15, "w": "4" }, { "u": 55, "v": 16, "w": "2" }, { "u": 55, "v": 17, "w": "4" }, { "u": 55, "v": 18, "w": "2" }, { "u": 55, "v": 20, "w": "11" }, { "u": 55, "v": 21, "w": "2" }, { "u": 55, "v": 22, "w": "1" }, { "u": 55, "v": 23, "w": "1" }, { "u": 55, "v": 25, "w": "1" }, { "u": 55, "v": 26, "w": "1" }, { "u": 55, "v": 27, "w": "2" }, { "u": 55, "v": 29, "w": "1" }, { "u": 55, "v": 30, "w": "1" }, { "u": 55, "v": 31, "w": "1" }, { "u": 55, "v": 34, "w": "3" }, { "u": 55, "v": 35, "w": "2" }, { "u": 55, "v": 36, "w": "2" }, { "u": 55, "v": 38, "w": "1" }, { "u": 55, "v": 39, "w": "2" }, { "u": 55, "v": 41, "w": "1" }, { "u": 55, "v": 42, "w": "1" }, { "u": 55, "v": 43, "w": "1" }, { "u": 55, "v": 44, "w": "1" }, { "u": 55, "v": 46, "w": "1" }, { "u": 55, "v": 48, "w": "1" }, { "u": 55, "v": 49, "w": "2" }, { "u": 55, "v": 50, "w": "1" }, { "u": 55, "v": 53, "w": "1" }, { "u": 55, "v": 54, "w": "5" }, { "u": 55, "v": 56, "w": "12" }, { "u": 55, "v": 57, "w": "7" }, { "u": 55, "v": 58, "w": "1" }, { "u": 56, "v": 1, "w": "4" }, { "u": 56, "v": 2, "w": "1" }, { "u": 56, "v": 3, "w": "5" }, { "u": 56, "v": 4, "w": "2" }, { "u": 56, "v": 6, "w": "2" }, { "u": 56, "v": 7, "w": "16" }, { "u": 56, "v": 8, "w": "2" }, { "u": 56, "v": 9, "w": "2" }, { "u": 56, "v": 11, "w": "6" }, { "u": 56, "v": 12, "w": "7" }, { "u": 56, "v": 14, "w": "1" }, { "u": 56, "v": 15, "w": "9" }, { "u": 56, "v": 18, "w": "1" }, { "u": 56, "v": 19, "w": "3" }, { "u": 56, "v": 20, "w": "9" }, { "u": 56, "v": 21, "w": "4" }, { "u": 56, "v": 22, "w": "1" }, { "u": 56, "v": 23, "w": "2" }, { "u": 56, "v": 24, "w": "2" }, { "u": 56, "v": 25, "w": "1" }, { "u": 56, "v": 26, "w": "1" }, { "u": 56, "v": 27, "w": "4" }, { "u": 56, "v": 29, "w": "2" }, { "u": 56, "v": 30, "w": "6" }, { "u": 56, "v": 31, "w": "1" }, { "u": 56, "v": 33, "w": "1" }, { "u": 56, "v": 34, "w": "4" }, { "u": 56, "v": 35, "w": "3" }, { "u": 56, "v": 36, "w": "10" }, { "u": 56, "v": 37, "w": "1" }, { "u": 56, "v": 38, "w": "1" }, { "u": 56, "v": 39, "w": "1" }, { "u": 56, "v": 41, "w": "5" }, { "u": 56, "v": 44, "w": "2" }, { "u": 56, "v": 46, "w": "2" }, { "u": 56, "v": 47, "w": "2" }, { "u": 56, "v": 49, "w": "2" }, { "u": 56, "v": 50, "w": "1" }, { "u": 56, "v": 53, "w": "2" }, { "u": 56, "v": 54, "w": "3" }, { "u": 56, "v": 55, "w": "12" }, { "u": 56, "v": 57, "w": "12" }, { "u": 57, "v": 1, "w": "1" }, { "u": 57, "v": 2, "w": "4" }, { "u": 57, "v": 3, "w": "19" }, { "u": 57, "v": 4, "w": "3" }, { "u": 57, "v": 5, "w": "4" }, { "u": 57, "v": 6, "w": "18" }, { "u": 57, "v": 7, "w": "20" }, { "u": 57, "v": 8, "w": "4" }, { "u": 57, "v": 9, "w": "7" }, { "u": 57, "v": 11, "w": "12" }, { "u": 57, "v": 12, "w": "3" }, { "u": 57, "v": 14, "w": "5" }, { "u": 57, "v": 15, "w": "4" }, { "u": 57, "v": 16, "w": "11" }, { "u": 57, "v": 17, "w": "11" }, { "u": 57, "v": 19, "w": "1" }, { "u": 57, "v": 20, "w": "23" }, { "u": 57, "v": 21, "w": "2" }, { "u": 57, "v": 22, "w": "3" }, { "u": 57, "v": 23, "w": "3" }, { "u": 57, "v": 24, "w": "1" }, { "u": 57, "v": 25, "w": "5" }, { "u": 57, "v": 26, "w": "2" }, { "u": 57, "v": 27, "w": "6" }, { "u": 57, "v": 29, "w": "10" }, { "u": 57, "v": 30, "w": "10" }, { "u": 57, "v": 31, "w": "3" }, { "u": 57, "v": 33, "w": "3" }, { "u": 57, "v": 34, "w": "3" }, { "u": 57, "v": 35, "w": "6" }, { "u": 57, "v": 36, "w": "1" }, { "u": 57, "v": 37, "w": "3" }, { "u": 57, "v": 38, "w": "4" }, { "u": 57, "v": 39, "w": "2" }, { "u": 57, "v": 40, "w": "2" }, { "u": 57, "v": 41, "w": "1" }, { "u": 57, "v": 42, "w": "3" }, { "u": 57, "v": 44, "w": "3" }, { "u": 57, "v": 45, "w": "1" }, { "u": 57, "v": 46, "w": "4" }, { "u": 57, "v": 47, "w": "6" }, { "u": 57, "v": 48, "w": "2" }, { "u": 57, "v": 49, "w": "3" }, { "u": 57, "v": 53, "w": "4" }, { "u": 57, "v": 54, "w": "10" }, { "u": 57, "v": 55, "w": "7" }, { "u": 57, "v": 56, "w": "12" }, { "u": 57, "v": 58, "w": "1" }, { "u": 58, "v": 1, "w": "1" }, { "u": 58, "v": 3, "w": "1" }, { "u": 58, "v": 4, "w": "5" }, { "u": 58, "v": 6, "w": "2" }, { "u": 58, "v": 7, "w": "4" }, { "u": 58, "v": 8, "w": "2" }, { "u": 58, "v": 9, "w": "1" }, { "u": 58, "v": 12, "w": "3" }, { "u": 58, "v": 14, "w": "1" }, { "u": 58, "v": 16, "w": "3" }, { "u": 58, "v": 18, "w": "1" }, { "u": 58, "v": 19, "w": "2" }, { "u": 58, "v": 20, "w": "5" }, { "u": 58, "v": 21, "w": "1" }, { "u": 58, "v": 23, "w": "1" }, { "u": 58, "v": 24, "w": "1" }, { "u": 58, "v": 27, "w": "2" }, { "u": 58, "v": 29, "w": "1" }, { "u": 58, "v": 30, "w": "2" }, { "u": 58, "v": 31, "w": "2" }, { "u": 58, "v": 32, "w": "1" }, { "u": 58, "v": 34, "w": "6" }, { "u": 58, "v": 35, "w": "2" }, { "u": 58, "v": 36, "w": "1" }, { "u": 58, "v": 38, "w": "2" }, { "u": 58, "v": 41, "w": "2" }, { "u": 58, "v": 42, "w": "1" }, { "u": 58, "v": 46, "w": "1" }, { "u": 58, "v": 47, "w": "1" }, { "u": 58, "v": 49, "w": "1" }, { "u": 58, "v": 50, "w": "3" }, { "u": 58, "v": 53, "w": "3" }, { "u": 58, "v": 55, "w": "1" }, { "u": 58, "v": 57, "w": "1" } ], "hash_sha256": "cda3fa08638d0d85a096125c88fc3a0bb100c436145f2d05c4e9cdd2ebe1e49c", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] }, "timings": { "load_ms": 19, "reload_ms": 18, "save_ms": 6, "total_ms": 54 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/Krackhardt_High-tech_managers__FT2_multirel.json000066400000000000000000002151431524402210300325510ustar00rootroot00000000000000{ "counts": { "links_sna": 190, "nodes": 21, "ties_graph": 190 }, "dataset": { "filetype": 2, "name": "Krackhardt_High-tech_managers.paj", "path": "src/data/Krackhardt_High-tech_managers.paj" }, "graph": { "directed": true, "relations": 3, "weighted": false }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 2, "load_ms": 2, "load_msg": "", "net_name": "Krackhardt's High-tech managers", "ok": true }, "metrics": { "density": "0.45238095238095238" }, "relations_bundle": [ { "index": 0, "name": "gives_advice_to", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 8, "w": "1" }, { "u": 1, "v": 16, "w": "1" }, { "u": 1, "v": 18, "w": "1" }, { "u": 1, "v": 21, "w": "1" }, { "u": 2, "v": 6, "w": "1" }, { "u": 2, "v": 7, "w": "1" }, { "u": 2, "v": 21, "w": "1" }, { "u": 3, "v": 1, "w": "1" }, { "u": 3, "v": 2, "w": "1" }, { "u": 3, "v": 4, "w": "1" }, { "u": 3, "v": 6, "w": "1" }, { "u": 3, "v": 7, "w": "1" }, { "u": 3, "v": 8, "w": "1" }, { "u": 3, "v": 9, "w": "1" }, { "u": 3, "v": 10, "w": "1" }, { "u": 3, "v": 11, "w": "1" }, { "u": 3, "v": 12, "w": "1" }, { "u": 3, "v": 14, "w": "1" }, { "u": 3, "v": 17, "w": "1" }, { "u": 3, "v": 18, "w": "1" }, { "u": 3, "v": 20, "w": "1" }, { "u": 3, "v": 21, "w": "1" }, { "u": 4, "v": 1, "w": "1" }, { "u": 4, "v": 2, "w": "1" }, { "u": 4, "v": 6, "w": "1" }, { "u": 4, "v": 8, "w": "1" }, { "u": 4, "v": 10, "w": "1" }, { "u": 4, "v": 11, "w": "1" }, { "u": 4, "v": 12, "w": "1" }, { "u": 4, "v": 16, "w": "1" }, { "u": 4, "v": 17, "w": "1" }, { "u": 4, "v": 18, "w": "1" }, { "u": 4, "v": 20, "w": "1" }, { "u": 4, "v": 21, "w": "1" }, { "u": 5, "v": 1, "w": "1" }, { "u": 5, "v": 2, "w": "1" }, { "u": 5, "v": 6, "w": "1" }, { "u": 5, "v": 7, "w": "1" }, { "u": 5, "v": 8, "w": "1" }, { "u": 5, "v": 10, "w": "1" }, { "u": 5, "v": 11, "w": "1" }, { "u": 5, "v": 13, "w": "1" }, { "u": 5, "v": 14, "w": "1" }, { "u": 5, "v": 16, "w": "1" }, { "u": 5, "v": 17, "w": "1" }, { "u": 5, "v": 18, "w": "1" }, { "u": 5, "v": 19, "w": "1" }, { "u": 5, "v": 20, "w": "1" }, { "u": 5, "v": 21, "w": "1" }, { "u": 6, "v": 21, "w": "1" }, { "u": 7, "v": 2, "w": "1" }, { "u": 7, "v": 6, "w": "1" }, { "u": 7, "v": 11, "w": "1" }, { "u": 7, "v": 12, "w": "1" }, { "u": 7, "v": 14, "w": "1" }, { "u": 7, "v": 17, "w": "1" }, { "u": 7, "v": 18, "w": "1" }, { "u": 7, "v": 21, "w": "1" }, { "u": 8, "v": 2, "w": "1" }, { "u": 8, "v": 4, "w": "1" }, { "u": 8, "v": 6, "w": "1" }, { "u": 8, "v": 7, "w": "1" }, { "u": 8, "v": 10, "w": "1" }, { "u": 8, "v": 11, "w": "1" }, { "u": 8, "v": 18, "w": "1" }, { "u": 8, "v": 21, "w": "1" }, { "u": 9, "v": 1, "w": "1" }, { "u": 9, "v": 2, "w": "1" }, { "u": 9, "v": 6, "w": "1" }, { "u": 9, "v": 7, "w": "1" }, { "u": 9, "v": 8, "w": "1" }, { "u": 9, "v": 10, "w": "1" }, { "u": 9, "v": 11, "w": "1" }, { "u": 9, "v": 12, "w": "1" }, { "u": 9, "v": 14, "w": "1" }, { "u": 9, "v": 16, "w": "1" }, { "u": 9, "v": 17, "w": "1" }, { "u": 9, "v": 18, "w": "1" }, { "u": 9, "v": 21, "w": "1" }, { "u": 10, "v": 1, "w": "1" }, { "u": 10, "v": 2, "w": "1" }, { "u": 10, "v": 3, "w": "1" }, { "u": 10, "v": 4, "w": "1" }, { "u": 10, "v": 5, "w": "1" }, { "u": 10, "v": 8, "w": "1" }, { "u": 10, "v": 11, "w": "1" }, { "u": 10, "v": 13, "w": "1" }, { "u": 10, "v": 15, "w": "1" }, { "u": 10, "v": 16, "w": "1" }, { "u": 10, "v": 17, "w": "1" }, { "u": 10, "v": 18, "w": "1" }, { "u": 10, "v": 19, "w": "1" }, { "u": 10, "v": 20, "w": "1" }, { "u": 11, "v": 1, "w": "1" }, { "u": 11, "v": 2, "w": "1" }, { "u": 11, "v": 7, "w": "1" }, { "u": 12, "v": 7, "w": "1" }, { "u": 12, "v": 21, "w": "1" }, { "u": 13, "v": 1, "w": "1" }, { "u": 13, "v": 2, "w": "1" }, { "u": 13, "v": 5, "w": "1" }, { "u": 13, "v": 9, "w": "1" }, { "u": 13, "v": 14, "w": "1" }, { "u": 13, "v": 18, "w": "1" }, { "u": 14, "v": 2, "w": "1" }, { "u": 14, "v": 7, "w": "1" }, { "u": 14, "v": 18, "w": "1" }, { "u": 14, "v": 21, "w": "1" }, { "u": 15, "v": 1, "w": "1" }, { "u": 15, "v": 2, "w": "1" }, { "u": 15, "v": 3, "w": "1" }, { "u": 15, "v": 4, "w": "1" }, { "u": 15, "v": 5, "w": "1" }, { "u": 15, "v": 6, "w": "1" }, { "u": 15, "v": 7, "w": "1" }, { "u": 15, "v": 8, "w": "1" }, { "u": 15, "v": 9, "w": "1" }, { "u": 15, "v": 10, "w": "1" }, { "u": 15, "v": 11, "w": "1" }, { "u": 15, "v": 12, "w": "1" }, { "u": 15, "v": 13, "w": "1" }, { "u": 15, "v": 14, "w": "1" }, { "u": 15, "v": 16, "w": "1" }, { "u": 15, "v": 17, "w": "1" }, { "u": 15, "v": 18, "w": "1" }, { "u": 15, "v": 19, "w": "1" }, { "u": 15, "v": 20, "w": "1" }, { "u": 15, "v": 21, "w": "1" }, { "u": 16, "v": 1, "w": "1" }, { "u": 16, "v": 2, "w": "1" }, { "u": 16, "v": 10, "w": "1" }, { "u": 16, "v": 18, "w": "1" }, { "u": 17, "v": 1, "w": "1" }, { "u": 17, "v": 2, "w": "1" }, { "u": 17, "v": 4, "w": "1" }, { "u": 17, "v": 7, "w": "1" }, { "u": 17, "v": 21, "w": "1" }, { "u": 18, "v": 1, "w": "1" }, { "u": 18, "v": 2, "w": "1" }, { "u": 18, "v": 3, "w": "1" }, { "u": 18, "v": 4, "w": "1" }, { "u": 18, "v": 5, "w": "1" }, { "u": 18, "v": 7, "w": "1" }, { "u": 18, "v": 8, "w": "1" }, { "u": 18, "v": 9, "w": "1" }, { "u": 18, "v": 10, "w": "1" }, { "u": 18, "v": 11, "w": "1" }, { "u": 18, "v": 13, "w": "1" }, { "u": 18, "v": 14, "w": "1" }, { "u": 18, "v": 15, "w": "1" }, { "u": 18, "v": 16, "w": "1" }, { "u": 18, "v": 19, "w": "1" }, { "u": 18, "v": 20, "w": "1" }, { "u": 18, "v": 21, "w": "1" }, { "u": 19, "v": 1, "w": "1" }, { "u": 19, "v": 2, "w": "1" }, { "u": 19, "v": 3, "w": "1" }, { "u": 19, "v": 5, "w": "1" }, { "u": 19, "v": 7, "w": "1" }, { "u": 19, "v": 10, "w": "1" }, { "u": 19, "v": 11, "w": "1" }, { "u": 19, "v": 14, "w": "1" }, { "u": 19, "v": 15, "w": "1" }, { "u": 19, "v": 18, "w": "1" }, { "u": 19, "v": 20, "w": "1" }, { "u": 20, "v": 1, "w": "1" }, { "u": 20, "v": 2, "w": "1" }, { "u": 20, "v": 6, "w": "1" }, { "u": 20, "v": 8, "w": "1" }, { "u": 20, "v": 11, "w": "1" }, { "u": 20, "v": 12, "w": "1" }, { "u": 20, "v": 14, "w": "1" }, { "u": 20, "v": 15, "w": "1" }, { "u": 20, "v": 16, "w": "1" }, { "u": 20, "v": 17, "w": "1" }, { "u": 20, "v": 18, "w": "1" }, { "u": 20, "v": 21, "w": "1" }, { "u": 21, "v": 2, "w": "1" }, { "u": 21, "v": 3, "w": "1" }, { "u": 21, "v": 4, "w": "1" }, { "u": 21, "v": 6, "w": "1" }, { "u": 21, "v": 7, "w": "1" }, { "u": 21, "v": 8, "w": "1" }, { "u": 21, "v": 12, "w": "1" }, { "u": 21, "v": 14, "w": "1" }, { "u": 21, "v": 17, "w": "1" }, { "u": 21, "v": 18, "w": "1" }, { "u": 21, "v": 20, "w": "1" } ], "hash_sha256": "acaddccf56f872c895ba0e599da7d0a7a50c7f38a4d5d16aa6ca17cdacf9aa79", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", "v16", "v17", "v18", "v19", "v20", "v21" ] }, "ties_graph": 190 }, { "index": 1, "name": "is_friend_of", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 8, "w": "1" }, { "u": 1, "v": 12, "w": "1" }, { "u": 1, "v": 16, "w": "1" }, { "u": 2, "v": 1, "w": "1" }, { "u": 2, "v": 18, "w": "1" }, { "u": 2, "v": 21, "w": "1" }, { "u": 3, "v": 14, "w": "1" }, { "u": 3, "v": 19, "w": "1" }, { "u": 4, "v": 1, "w": "1" }, { "u": 4, "v": 2, "w": "1" }, { "u": 4, "v": 8, "w": "1" }, { "u": 4, "v": 12, "w": "1" }, { "u": 4, "v": 16, "w": "1" }, { "u": 4, "v": 17, "w": "1" }, { "u": 5, "v": 2, "w": "1" }, { "u": 5, "v": 9, "w": "1" }, { "u": 5, "v": 11, "w": "1" }, { "u": 5, "v": 14, "w": "1" }, { "u": 5, "v": 17, "w": "1" }, { "u": 5, "v": 19, "w": "1" }, { "u": 5, "v": 21, "w": "1" }, { "u": 6, "v": 2, "w": "1" }, { "u": 6, "v": 7, "w": "1" }, { "u": 6, "v": 9, "w": "1" }, { "u": 6, "v": 12, "w": "1" }, { "u": 6, "v": 17, "w": "1" }, { "u": 6, "v": 21, "w": "1" }, { "u": 8, "v": 4, "w": "1" }, { "u": 10, "v": 3, "w": "1" }, { "u": 10, "v": 5, "w": "1" }, { "u": 10, "v": 8, "w": "1" }, { "u": 10, "v": 9, "w": "1" }, { "u": 10, "v": 12, "w": "1" }, { "u": 10, "v": 16, "w": "1" }, { "u": 10, "v": 20, "w": "1" }, { "u": 11, "v": 1, "w": "1" }, { "u": 11, "v": 2, "w": "1" }, { "u": 11, "v": 3, "w": "1" }, { "u": 11, "v": 4, "w": "1" }, { "u": 11, "v": 5, "w": "1" }, { "u": 11, "v": 8, "w": "1" }, { "u": 11, "v": 9, "w": "1" }, { "u": 11, "v": 12, "w": "1" }, { "u": 11, "v": 13, "w": "1" }, { "u": 11, "v": 15, "w": "1" }, { "u": 11, "v": 17, "w": "1" }, { "u": 11, "v": 18, "w": "1" }, { "u": 11, "v": 19, "w": "1" }, { "u": 12, "v": 1, "w": "1" }, { "u": 12, "v": 4, "w": "1" }, { "u": 12, "v": 17, "w": "1" }, { "u": 12, "v": 21, "w": "1" }, { "u": 13, "v": 5, "w": "1" }, { "u": 13, "v": 11, "w": "1" }, { "u": 14, "v": 7, "w": "1" }, { "u": 14, "v": 15, "w": "1" }, { "u": 15, "v": 1, "w": "1" }, { "u": 15, "v": 3, "w": "1" }, { "u": 15, "v": 5, "w": "1" }, { "u": 15, "v": 6, "w": "1" }, { "u": 15, "v": 9, "w": "1" }, { "u": 15, "v": 11, "w": "1" }, { "u": 15, "v": 14, "w": "1" }, { "u": 15, "v": 19, "w": "1" }, { "u": 16, "v": 1, "w": "1" }, { "u": 16, "v": 2, "w": "1" }, { "u": 17, "v": 1, "w": "1" }, { "u": 17, "v": 2, "w": "1" }, { "u": 17, "v": 3, "w": "1" }, { "u": 17, "v": 4, "w": "1" }, { "u": 17, "v": 5, "w": "1" }, { "u": 17, "v": 6, "w": "1" }, { "u": 17, "v": 7, "w": "1" }, { "u": 17, "v": 8, "w": "1" }, { "u": 17, "v": 9, "w": "1" }, { "u": 17, "v": 10, "w": "1" }, { "u": 17, "v": 11, "w": "1" }, { "u": 17, "v": 12, "w": "1" }, { "u": 17, "v": 14, "w": "1" }, { "u": 17, "v": 15, "w": "1" }, { "u": 17, "v": 16, "w": "1" }, { "u": 17, "v": 19, "w": "1" }, { "u": 17, "v": 20, "w": "1" }, { "u": 17, "v": 21, "w": "1" }, { "u": 18, "v": 2, "w": "1" }, { "u": 19, "v": 1, "w": "1" }, { "u": 19, "v": 2, "w": "1" }, { "u": 19, "v": 3, "w": "1" }, { "u": 19, "v": 5, "w": "1" }, { "u": 19, "v": 11, "w": "1" }, { "u": 19, "v": 12, "w": "1" }, { "u": 19, "v": 14, "w": "1" }, { "u": 19, "v": 15, "w": "1" }, { "u": 19, "v": 20, "w": "1" }, { "u": 20, "v": 11, "w": "1" }, { "u": 20, "v": 18, "w": "1" }, { "u": 21, "v": 2, "w": "1" }, { "u": 21, "v": 12, "w": "1" }, { "u": 21, "v": 17, "w": "1" }, { "u": 21, "v": 18, "w": "1" } ], "hash_sha256": "76f7efb1b2bf5ac791310fd6fbfd5cd3d4c689617e3f2231fc66caecf9e1aed6", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", "v16", "v17", "v18", "v19", "v20", "v21" ] }, "ties_graph": 190 }, { "index": 2, "name": "reports_to", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 2, "v": 7, "w": "1" }, { "u": 3, "v": 14, "w": "1" }, { "u": 4, "v": 2, "w": "1" }, { "u": 5, "v": 14, "w": "1" }, { "u": 6, "v": 21, "w": "1" }, { "u": 8, "v": 21, "w": "1" }, { "u": 9, "v": 14, "w": "1" }, { "u": 10, "v": 18, "w": "1" }, { "u": 11, "v": 18, "w": "1" }, { "u": 12, "v": 21, "w": "1" }, { "u": 13, "v": 14, "w": "1" }, { "u": 14, "v": 7, "w": "1" }, { "u": 15, "v": 14, "w": "1" }, { "u": 16, "v": 2, "w": "1" }, { "u": 17, "v": 21, "w": "1" }, { "u": 18, "v": 7, "w": "1" }, { "u": 19, "v": 14, "w": "1" }, { "u": 20, "v": 14, "w": "1" }, { "u": 21, "v": 7, "w": "1" } ], "hash_sha256": "9c990a057ae0cc179f14c2bfe068ca1bfecd51fa255ccb672e1535d71f8358b4", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", "v16", "v17", "v18", "v19", "v20", "v21" ] }, "ties_graph": 190 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 8, "w": "1" }, { "u": 1, "v": 16, "w": "1" }, { "u": 1, "v": 18, "w": "1" }, { "u": 1, "v": 21, "w": "1" }, { "u": 2, "v": 6, "w": "1" }, { "u": 2, "v": 7, "w": "1" }, { "u": 2, "v": 21, "w": "1" }, { "u": 3, "v": 1, "w": "1" }, { "u": 3, "v": 2, "w": "1" }, { "u": 3, "v": 4, "w": "1" }, { "u": 3, "v": 6, "w": "1" }, { "u": 3, "v": 7, "w": "1" }, { "u": 3, "v": 8, "w": "1" }, { "u": 3, "v": 9, "w": "1" }, { "u": 3, "v": 10, "w": "1" }, { "u": 3, "v": 11, "w": "1" }, { "u": 3, "v": 12, "w": "1" }, { "u": 3, "v": 14, "w": "1" }, { "u": 3, "v": 17, "w": "1" }, { "u": 3, "v": 18, "w": "1" }, { "u": 3, "v": 20, "w": "1" }, { "u": 3, "v": 21, "w": "1" }, { "u": 4, "v": 1, "w": "1" }, { "u": 4, "v": 2, "w": "1" }, { "u": 4, "v": 6, "w": "1" }, { "u": 4, "v": 8, "w": "1" }, { "u": 4, "v": 10, "w": "1" }, { "u": 4, "v": 11, "w": "1" }, { "u": 4, "v": 12, "w": "1" }, { "u": 4, "v": 16, "w": "1" }, { "u": 4, "v": 17, "w": "1" }, { "u": 4, "v": 18, "w": "1" }, { "u": 4, "v": 20, "w": "1" }, { "u": 4, "v": 21, "w": "1" }, { "u": 5, "v": 1, "w": "1" }, { "u": 5, "v": 2, "w": "1" }, { "u": 5, "v": 6, "w": "1" }, { "u": 5, "v": 7, "w": "1" }, { "u": 5, "v": 8, "w": "1" }, { "u": 5, "v": 10, "w": "1" }, { "u": 5, "v": 11, "w": "1" }, { "u": 5, "v": 13, "w": "1" }, { "u": 5, "v": 14, "w": "1" }, { "u": 5, "v": 16, "w": "1" }, { "u": 5, "v": 17, "w": "1" }, { "u": 5, "v": 18, "w": "1" }, { "u": 5, "v": 19, "w": "1" }, { "u": 5, "v": 20, "w": "1" }, { "u": 5, "v": 21, "w": "1" }, { "u": 6, "v": 21, "w": "1" }, { "u": 7, "v": 2, "w": "1" }, { "u": 7, "v": 6, "w": "1" }, { "u": 7, "v": 11, "w": "1" }, { "u": 7, "v": 12, "w": "1" }, { "u": 7, "v": 14, "w": "1" }, { "u": 7, "v": 17, "w": "1" }, { "u": 7, "v": 18, "w": "1" }, { "u": 7, "v": 21, "w": "1" }, { "u": 8, "v": 2, "w": "1" }, { "u": 8, "v": 4, "w": "1" }, { "u": 8, "v": 6, "w": "1" }, { "u": 8, "v": 7, "w": "1" }, { "u": 8, "v": 10, "w": "1" }, { "u": 8, "v": 11, "w": "1" }, { "u": 8, "v": 18, "w": "1" }, { "u": 8, "v": 21, "w": "1" }, { "u": 9, "v": 1, "w": "1" }, { "u": 9, "v": 2, "w": "1" }, { "u": 9, "v": 6, "w": "1" }, { "u": 9, "v": 7, "w": "1" }, { "u": 9, "v": 8, "w": "1" }, { "u": 9, "v": 10, "w": "1" }, { "u": 9, "v": 11, "w": "1" }, { "u": 9, "v": 12, "w": "1" }, { "u": 9, "v": 14, "w": "1" }, { "u": 9, "v": 16, "w": "1" }, { "u": 9, "v": 17, "w": "1" }, { "u": 9, "v": 18, "w": "1" }, { "u": 9, "v": 21, "w": "1" }, { "u": 10, "v": 1, "w": "1" }, { "u": 10, "v": 2, "w": "1" }, { "u": 10, "v": 3, "w": "1" }, { "u": 10, "v": 4, "w": "1" }, { "u": 10, "v": 5, "w": "1" }, { "u": 10, "v": 8, "w": "1" }, { "u": 10, "v": 11, "w": "1" }, { "u": 10, "v": 13, "w": "1" }, { "u": 10, "v": 15, "w": "1" }, { "u": 10, "v": 16, "w": "1" }, { "u": 10, "v": 17, "w": "1" }, { "u": 10, "v": 18, "w": "1" }, { "u": 10, "v": 19, "w": "1" }, { "u": 10, "v": 20, "w": "1" }, { "u": 11, "v": 1, "w": "1" }, { "u": 11, "v": 2, "w": "1" }, { "u": 11, "v": 7, "w": "1" }, { "u": 12, "v": 7, "w": "1" }, { "u": 12, "v": 21, "w": "1" }, { "u": 13, "v": 1, "w": "1" }, { "u": 13, "v": 2, "w": "1" }, { "u": 13, "v": 5, "w": "1" }, { "u": 13, "v": 9, "w": "1" }, { "u": 13, "v": 14, "w": "1" }, { "u": 13, "v": 18, "w": "1" }, { "u": 14, "v": 2, "w": "1" }, { "u": 14, "v": 7, "w": "1" }, { "u": 14, "v": 18, "w": "1" }, { "u": 14, "v": 21, "w": "1" }, { "u": 15, "v": 1, "w": "1" }, { "u": 15, "v": 2, "w": "1" }, { "u": 15, "v": 3, "w": "1" }, { "u": 15, "v": 4, "w": "1" }, { "u": 15, "v": 5, "w": "1" }, { "u": 15, "v": 6, "w": "1" }, { "u": 15, "v": 7, "w": "1" }, { "u": 15, "v": 8, "w": "1" }, { "u": 15, "v": 9, "w": "1" }, { "u": 15, "v": 10, "w": "1" }, { "u": 15, "v": 11, "w": "1" }, { "u": 15, "v": 12, "w": "1" }, { "u": 15, "v": 13, "w": "1" }, { "u": 15, "v": 14, "w": "1" }, { "u": 15, "v": 16, "w": "1" }, { "u": 15, "v": 17, "w": "1" }, { "u": 15, "v": 18, "w": "1" }, { "u": 15, "v": 19, "w": "1" }, { "u": 15, "v": 20, "w": "1" }, { "u": 15, "v": 21, "w": "1" }, { "u": 16, "v": 1, "w": "1" }, { "u": 16, "v": 2, "w": "1" }, { "u": 16, "v": 10, "w": "1" }, { "u": 16, "v": 18, "w": "1" }, { "u": 17, "v": 1, "w": "1" }, { "u": 17, "v": 2, "w": "1" }, { "u": 17, "v": 4, "w": "1" }, { "u": 17, "v": 7, "w": "1" }, { "u": 17, "v": 21, "w": "1" }, { "u": 18, "v": 1, "w": "1" }, { "u": 18, "v": 2, "w": "1" }, { "u": 18, "v": 3, "w": "1" }, { "u": 18, "v": 4, "w": "1" }, { "u": 18, "v": 5, "w": "1" }, { "u": 18, "v": 7, "w": "1" }, { "u": 18, "v": 8, "w": "1" }, { "u": 18, "v": 9, "w": "1" }, { "u": 18, "v": 10, "w": "1" }, { "u": 18, "v": 11, "w": "1" }, { "u": 18, "v": 13, "w": "1" }, { "u": 18, "v": 14, "w": "1" }, { "u": 18, "v": 15, "w": "1" }, { "u": 18, "v": 16, "w": "1" }, { "u": 18, "v": 19, "w": "1" }, { "u": 18, "v": 20, "w": "1" }, { "u": 18, "v": 21, "w": "1" }, { "u": 19, "v": 1, "w": "1" }, { "u": 19, "v": 2, "w": "1" }, { "u": 19, "v": 3, "w": "1" }, { "u": 19, "v": 5, "w": "1" }, { "u": 19, "v": 7, "w": "1" }, { "u": 19, "v": 10, "w": "1" }, { "u": 19, "v": 11, "w": "1" }, { "u": 19, "v": 14, "w": "1" }, { "u": 19, "v": 15, "w": "1" }, { "u": 19, "v": 18, "w": "1" }, { "u": 19, "v": 20, "w": "1" }, { "u": 20, "v": 1, "w": "1" }, { "u": 20, "v": 2, "w": "1" }, { "u": 20, "v": 6, "w": "1" }, { "u": 20, "v": 8, "w": "1" }, { "u": 20, "v": 11, "w": "1" }, { "u": 20, "v": 12, "w": "1" }, { "u": 20, "v": 14, "w": "1" }, { "u": 20, "v": 15, "w": "1" }, { "u": 20, "v": 16, "w": "1" }, { "u": 20, "v": 17, "w": "1" }, { "u": 20, "v": 18, "w": "1" }, { "u": 20, "v": 21, "w": "1" }, { "u": 21, "v": 2, "w": "1" }, { "u": 21, "v": 3, "w": "1" }, { "u": 21, "v": 4, "w": "1" }, { "u": 21, "v": 6, "w": "1" }, { "u": 21, "v": 7, "w": "1" }, { "u": 21, "v": 8, "w": "1" }, { "u": 21, "v": 12, "w": "1" }, { "u": 21, "v": 14, "w": "1" }, { "u": 21, "v": 17, "w": "1" }, { "u": 21, "v": 18, "w": "1" }, { "u": 21, "v": 20, "w": "1" } ], "hash_sha256": "acaddccf56f872c895ba0e599da7d0a7a50c7f38a4d5d16aa6ca17cdacf9aa79", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", "v16", "v17", "v18", "v19", "v20", "v21" ] }, "timings": { "load_ms": 2, "reload_ms": 2, "save_ms": 1, "total_ms": 8 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/Padgett_Florentine_Families__FT2_multirel.json000066400000000000000000000460441524402210300323260ustar00rootroot00000000000000{ "counts": { "links_sna": 40, "nodes": 16, "ties_graph": 40 }, "dataset": { "filetype": 2, "name": "Padgett_Florentine_Families.paj", "path": "src/data/Padgett_Florentine_Families.paj" }, "graph": { "directed": true, "relations": 2, "weighted": false }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 2, "load_ms": 1, "load_msg": "", "net_name": "Padgett's Florentine Families", "ok": true }, "metrics": { "density": "0.16666666666666666" }, "relations_bundle": [ { "index": 0, "name": "Marital", "signature": { "edge_list": [ { "u": 1, "v": 9, "w": "1" }, { "u": 2, "v": 6, "w": "1" }, { "u": 2, "v": 7, "w": "1" }, { "u": 2, "v": 9, "w": "1" }, { "u": 3, "v": 5, "w": "1" }, { "u": 3, "v": 9, "w": "1" }, { "u": 4, "v": 7, "w": "1" }, { "u": 4, "v": 11, "w": "1" }, { "u": 4, "v": 15, "w": "1" }, { "u": 5, "v": 3, "w": "1" }, { "u": 5, "v": 11, "w": "1" }, { "u": 5, "v": 15, "w": "1" }, { "u": 6, "v": 2, "w": "1" }, { "u": 7, "v": 2, "w": "1" }, { "u": 7, "v": 4, "w": "1" }, { "u": 7, "v": 8, "w": "1" }, { "u": 7, "v": 16, "w": "1" }, { "u": 8, "v": 7, "w": "1" }, { "u": 9, "v": 1, "w": "1" }, { "u": 9, "v": 2, "w": "1" }, { "u": 9, "v": 3, "w": "1" }, { "u": 9, "v": 13, "w": "1" }, { "u": 9, "v": 14, "w": "1" }, { "u": 9, "v": 16, "w": "1" }, { "u": 10, "v": 14, "w": "1" }, { "u": 11, "v": 4, "w": "1" }, { "u": 11, "v": 5, "w": "1" }, { "u": 11, "v": 15, "w": "1" }, { "u": 13, "v": 9, "w": "1" }, { "u": 13, "v": 15, "w": "1" }, { "u": 13, "v": 16, "w": "1" }, { "u": 14, "v": 9, "w": "1" }, { "u": 14, "v": 10, "w": "1" }, { "u": 15, "v": 4, "w": "1" }, { "u": 15, "v": 5, "w": "1" }, { "u": 15, "v": 11, "w": "1" }, { "u": 15, "v": 13, "w": "1" }, { "u": 16, "v": 7, "w": "1" }, { "u": 16, "v": 9, "w": "1" }, { "u": 16, "v": 13, "w": "1" } ], "hash_sha256": "4d425fbbf5a03a1436d7bf9fc181e1bacb738968313b8a54ceb51ac5d84a387a", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "Acciaiuoli", "Albizzi", "Barbadori", "Bischeri", "Castellani", "Ginori", "Guadagni", "Lamberteschi", "Medici", "Pazzi", "Peruzzi", "Pucci", "Ridolfi", "Salviati", "Strozzi", "Tornabuoni" ] }, "ties_graph": 40 }, { "index": 1, "name": "Business", "signature": { "edge_list": [ { "u": 3, "v": 5, "w": "1" }, { "u": 3, "v": 6, "w": "1" }, { "u": 3, "v": 9, "w": "1" }, { "u": 3, "v": 11, "w": "1" }, { "u": 4, "v": 7, "w": "1" }, { "u": 4, "v": 8, "w": "1" }, { "u": 4, "v": 11, "w": "1" }, { "u": 5, "v": 3, "w": "1" }, { "u": 5, "v": 8, "w": "1" }, { "u": 5, "v": 11, "w": "1" }, { "u": 6, "v": 3, "w": "1" }, { "u": 6, "v": 9, "w": "1" }, { "u": 7, "v": 4, "w": "1" }, { "u": 7, "v": 8, "w": "1" }, { "u": 8, "v": 4, "w": "1" }, { "u": 8, "v": 5, "w": "1" }, { "u": 8, "v": 7, "w": "1" }, { "u": 8, "v": 11, "w": "1" }, { "u": 9, "v": 3, "w": "1" }, { "u": 9, "v": 6, "w": "1" }, { "u": 9, "v": 10, "w": "1" }, { "u": 9, "v": 14, "w": "1" }, { "u": 9, "v": 16, "w": "1" }, { "u": 10, "v": 9, "w": "1" }, { "u": 11, "v": 3, "w": "1" }, { "u": 11, "v": 4, "w": "1" }, { "u": 11, "v": 5, "w": "1" }, { "u": 11, "v": 8, "w": "1" }, { "u": 14, "v": 9, "w": "1" }, { "u": 16, "v": 9, "w": "1" } ], "hash_sha256": "7bff22ec1711241e352ca68ea7eb6f20b8c9e6e8385e66569ac2c6845d884a7d", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "Acciaiuoli", "Albizzi", "Barbadori", "Bischeri", "Castellani", "Ginori", "Guadagni", "Lamberteschi", "Medici", "Pazzi", "Peruzzi", "Pucci", "Ridolfi", "Salviati", "Strozzi", "Tornabuoni" ] }, "ties_graph": 40 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 9, "w": "1" }, { "u": 2, "v": 6, "w": "1" }, { "u": 2, "v": 7, "w": "1" }, { "u": 2, "v": 9, "w": "1" }, { "u": 3, "v": 5, "w": "1" }, { "u": 3, "v": 9, "w": "1" }, { "u": 4, "v": 7, "w": "1" }, { "u": 4, "v": 11, "w": "1" }, { "u": 4, "v": 15, "w": "1" }, { "u": 5, "v": 3, "w": "1" }, { "u": 5, "v": 11, "w": "1" }, { "u": 5, "v": 15, "w": "1" }, { "u": 6, "v": 2, "w": "1" }, { "u": 7, "v": 2, "w": "1" }, { "u": 7, "v": 4, "w": "1" }, { "u": 7, "v": 8, "w": "1" }, { "u": 7, "v": 16, "w": "1" }, { "u": 8, "v": 7, "w": "1" }, { "u": 9, "v": 1, "w": "1" }, { "u": 9, "v": 2, "w": "1" }, { "u": 9, "v": 3, "w": "1" }, { "u": 9, "v": 13, "w": "1" }, { "u": 9, "v": 14, "w": "1" }, { "u": 9, "v": 16, "w": "1" }, { "u": 10, "v": 14, "w": "1" }, { "u": 11, "v": 4, "w": "1" }, { "u": 11, "v": 5, "w": "1" }, { "u": 11, "v": 15, "w": "1" }, { "u": 13, "v": 9, "w": "1" }, { "u": 13, "v": 15, "w": "1" }, { "u": 13, "v": 16, "w": "1" }, { "u": 14, "v": 9, "w": "1" }, { "u": 14, "v": 10, "w": "1" }, { "u": 15, "v": 4, "w": "1" }, { "u": 15, "v": 5, "w": "1" }, { "u": 15, "v": 11, "w": "1" }, { "u": 15, "v": 13, "w": "1" }, { "u": 16, "v": 7, "w": "1" }, { "u": 16, "v": 9, "w": "1" }, { "u": 16, "v": 13, "w": "1" } ], "hash_sha256": "4d425fbbf5a03a1436d7bf9fc181e1bacb738968313b8a54ceb51ac5d84a387a", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "Acciaiuoli", "Albizzi", "Barbadori", "Bischeri", "Castellani", "Ginori", "Guadagni", "Lamberteschi", "Medici", "Pazzi", "Peruzzi", "Pucci", "Ridolfi", "Salviati", "Strozzi", "Tornabuoni" ] }, "timings": { "load_ms": 1, "reload_ms": 0, "save_ms": 0, "total_ms": 3 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/Stephenson_Zelen_5actors_6edges__FT2_multirel.json000066400000000000000000000170311524402210300331120ustar00rootroot00000000000000{ "counts": { "links_sna": 12, "nodes": 5, "ties_graph": 12 }, "dataset": { "filetype": 2, "name": "Stephenson_Zelen_5actors_6edges_IC_test_dataset.paj", "path": "src/data/Stephenson_Zelen_5actors_6edges_IC_test_dataset.paj" }, "graph": { "directed": true, "relations": 2, "weighted": true }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "Stephenson_and_Zelen_5_actors_6edges", "ok": true }, "metrics": { "density": "0.59999999999999998" }, "relations_bundle": [ { "index": 0, "name": "non-weighted", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 5, "w": "1" }, { "u": 2, "v": 1, "w": "1" }, { "u": 2, "v": 3, "w": "1" }, { "u": 2, "v": 5, "w": "1" }, { "u": 3, "v": 2, "w": "1" }, { "u": 3, "v": 4, "w": "1" }, { "u": 4, "v": 1, "w": "1" }, { "u": 4, "v": 3, "w": "1" }, { "u": 5, "v": 1, "w": "1" }, { "u": 5, "v": 2, "w": "1" } ], "hash_sha256": "4180eef83835b9bfab07fa93f057121f76dbaf95affcc24104fde4e8f1f4e4bb", "node_custom_attributes": [ { }, { }, { }, { }, { } ], "node_labels": [ "1", "2", "3", "4", "5" ] }, "ties_graph": 12 }, { "index": 1, "name": "weighted", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "2" }, { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 5, "w": "5" }, { "u": 2, "v": 1, "w": "2" }, { "u": 2, "v": 3, "w": "1" }, { "u": 2, "v": 5, "w": "5" }, { "u": 3, "v": 2, "w": "1" }, { "u": 3, "v": 4, "w": "10" }, { "u": 4, "v": 1, "w": "1" }, { "u": 4, "v": 3, "w": "10" }, { "u": 5, "v": 1, "w": "5" }, { "u": 5, "v": 2, "w": "5" } ], "hash_sha256": "fd6d30b562ce3c7a0a84c8302fa5e2d7b4d3d58e585dfe1ba126ab5eea99ac3f", "node_custom_attributes": [ { }, { }, { }, { }, { } ], "node_labels": [ "1", "2", "3", "4", "5" ] }, "ties_graph": 12 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 5, "w": "1" }, { "u": 2, "v": 1, "w": "1" }, { "u": 2, "v": 3, "w": "1" }, { "u": 2, "v": 5, "w": "1" }, { "u": 3, "v": 2, "w": "1" }, { "u": 3, "v": 4, "w": "1" }, { "u": 4, "v": 1, "w": "1" }, { "u": 4, "v": 3, "w": "1" }, { "u": 5, "v": 1, "w": "1" }, { "u": 5, "v": 2, "w": "1" } ], "hash_sha256": "4180eef83835b9bfab07fa93f057121f76dbaf95affcc24104fde4e8f1f4e4bb", "node_custom_attributes": [ { }, { }, { }, { }, { } ], "node_labels": [ "1", "2", "3", "4", "5" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 1 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/StokmanZiegler_Netherlands__FT5__IO__V5.json000066400000000000000000001005411524402210300315430ustar00rootroot00000000000000{ "counts": { "links_sna": 120, "nodes": 16, "ties_graph": 120 }, "dataset": { "filetype": 5, "name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "path": "src/data/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" }, "graph": { "directed": true, "relations": 1, "weighted": true }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 5, "load_ms": 0, "load_msg": "", "net_name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "ok": true }, "metrics": { "density": "0.5" }, "relations_bundle": [ { "index": 0, "name": "unnamed", "signature": { "edge_list": [ { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 5, "w": "2" }, { "u": 1, "v": 6, "w": "1" }, { "u": 1, "v": 7, "w": "2" }, { "u": 1, "v": 8, "w": "1" }, { "u": 1, "v": 9, "w": "1" }, { "u": 1, "v": 10, "w": "1" }, { "u": 1, "v": 11, "w": "2" }, { "u": 1, "v": 12, "w": "1" }, { "u": 1, "v": 13, "w": "4" }, { "u": 2, "v": 3, "w": "3" }, { "u": 2, "v": 4, "w": "2" }, { "u": 2, "v": 5, "w": "1" }, { "u": 2, "v": 6, "w": "2" }, { "u": 2, "v": 7, "w": "1" }, { "u": 2, "v": 8, "w": "2" }, { "u": 2, "v": 9, "w": "2" }, { "u": 2, "v": 11, "w": "3" }, { "u": 2, "v": 12, "w": "1" }, { "u": 2, "v": 13, "w": "2" }, { "u": 2, "v": 14, "w": "1" }, { "u": 2, "v": 15, "w": "2" }, { "u": 3, "v": 2, "w": "3" }, { "u": 3, "v": 4, "w": "3" }, { "u": 3, "v": 5, "w": "1" }, { "u": 3, "v": 7, "w": "1" }, { "u": 3, "v": 9, "w": "1" }, { "u": 3, "v": 14, "w": "1" }, { "u": 3, "v": 15, "w": "1" }, { "u": 4, "v": 1, "w": "1" }, { "u": 4, "v": 2, "w": "2" }, { "u": 4, "v": 3, "w": "3" }, { "u": 4, "v": 7, "w": "1" }, { "u": 4, "v": 8, "w": "1" }, { "u": 4, "v": 9, "w": "2" }, { "u": 4, "v": 13, "w": "1" }, { "u": 4, "v": 15, "w": "2" }, { "u": 5, "v": 1, "w": "2" }, { "u": 5, "v": 2, "w": "1" }, { "u": 5, "v": 3, "w": "1" }, { "u": 5, "v": 7, "w": "1" }, { "u": 5, "v": 10, "w": "1" }, { "u": 5, "v": 14, "w": "1" }, { "u": 6, "v": 1, "w": "1" }, { "u": 6, "v": 2, "w": "2" }, { "u": 6, "v": 8, "w": "2" }, { "u": 6, "v": 9, "w": "1" }, { "u": 6, "v": 11, "w": "1" }, { "u": 6, "v": 12, "w": "1" }, { "u": 7, "v": 1, "w": "2" }, { "u": 7, "v": 2, "w": "1" }, { "u": 7, "v": 3, "w": "1" }, { "u": 7, "v": 4, "w": "1" }, { "u": 7, "v": 5, "w": "1" }, { "u": 7, "v": 8, "w": "1" }, { "u": 7, "v": 9, "w": "2" }, { "u": 7, "v": 10, "w": "1" }, { "u": 7, "v": 12, "w": "1" }, { "u": 7, "v": 13, "w": "1" }, { "u": 7, "v": 15, "w": "2" }, { "u": 8, "v": 1, "w": "1" }, { "u": 8, "v": 2, "w": "2" }, { "u": 8, "v": 4, "w": "1" }, { "u": 8, "v": 6, "w": "2" }, { "u": 8, "v": 7, "w": "1" }, { "u": 8, "v": 9, "w": "1" }, { "u": 8, "v": 11, "w": "1" }, { "u": 8, "v": 12, "w": "1" }, { "u": 8, "v": 13, "w": "1" }, { "u": 9, "v": 1, "w": "1" }, { "u": 9, "v": 2, "w": "2" }, { "u": 9, "v": 3, "w": "1" }, { "u": 9, "v": 4, "w": "2" }, { "u": 9, "v": 6, "w": "1" }, { "u": 9, "v": 7, "w": "2" }, { "u": 9, "v": 8, "w": "1" }, { "u": 9, "v": 13, "w": "1" }, { "u": 9, "v": 15, "w": "1" }, { "u": 10, "v": 1, "w": "1" }, { "u": 10, "v": 5, "w": "1" }, { "u": 10, "v": 7, "w": "1" }, { "u": 10, "v": 12, "w": "1" }, { "u": 10, "v": 14, "w": "1" }, { "u": 11, "v": 1, "w": "2" }, { "u": 11, "v": 2, "w": "3" }, { "u": 11, "v": 6, "w": "1" }, { "u": 11, "v": 8, "w": "1" }, { "u": 11, "v": 12, "w": "1" }, { "u": 11, "v": 14, "w": "1" }, { "u": 11, "v": 15, "w": "1" }, { "u": 12, "v": 1, "w": "1" }, { "u": 12, "v": 2, "w": "1" }, { "u": 12, "v": 6, "w": "1" }, { "u": 12, "v": 7, "w": "1" }, { "u": 12, "v": 8, "w": "1" }, { "u": 12, "v": 10, "w": "1" }, { "u": 12, "v": 11, "w": "1" }, { "u": 12, "v": 13, "w": "1" }, { "u": 12, "v": 15, "w": "1" }, { "u": 13, "v": 1, "w": "4" }, { "u": 13, "v": 2, "w": "2" }, { "u": 13, "v": 4, "w": "1" }, { "u": 13, "v": 7, "w": "1" }, { "u": 13, "v": 8, "w": "1" }, { "u": 13, "v": 9, "w": "1" }, { "u": 13, "v": 12, "w": "1" }, { "u": 14, "v": 2, "w": "1" }, { "u": 14, "v": 3, "w": "1" }, { "u": 14, "v": 5, "w": "1" }, { "u": 14, "v": 10, "w": "1" }, { "u": 14, "v": 11, "w": "1" }, { "u": 14, "v": 15, "w": "1" }, { "u": 15, "v": 2, "w": "2" }, { "u": 15, "v": 3, "w": "1" }, { "u": 15, "v": 4, "w": "2" }, { "u": 15, "v": 7, "w": "2" }, { "u": 15, "v": 9, "w": "1" }, { "u": 15, "v": 11, "w": "1" }, { "u": 15, "v": 12, "w": "1" }, { "u": 15, "v": 14, "w": "1" } ], "hash_sha256": "d5a7775f57938275e6e07513e778752ffca173703ba7e281b0f8b38f4356da7f", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "ABN", "AMRO", "ENNIA", "NS", "BUHRT", "AGO", "AKZO", "NB", "SHV", "FGH", "HEINK", "PHLPS", "NATND", "OGEM", "RSV", "NSU" ] }, "ties_graph": 120 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 5, "w": "2" }, { "u": 1, "v": 6, "w": "1" }, { "u": 1, "v": 7, "w": "2" }, { "u": 1, "v": 8, "w": "1" }, { "u": 1, "v": 9, "w": "1" }, { "u": 1, "v": 10, "w": "1" }, { "u": 1, "v": 11, "w": "2" }, { "u": 1, "v": 12, "w": "1" }, { "u": 1, "v": 13, "w": "4" }, { "u": 2, "v": 3, "w": "3" }, { "u": 2, "v": 4, "w": "2" }, { "u": 2, "v": 5, "w": "1" }, { "u": 2, "v": 6, "w": "2" }, { "u": 2, "v": 7, "w": "1" }, { "u": 2, "v": 8, "w": "2" }, { "u": 2, "v": 9, "w": "2" }, { "u": 2, "v": 11, "w": "3" }, { "u": 2, "v": 12, "w": "1" }, { "u": 2, "v": 13, "w": "2" }, { "u": 2, "v": 14, "w": "1" }, { "u": 2, "v": 15, "w": "2" }, { "u": 3, "v": 2, "w": "3" }, { "u": 3, "v": 4, "w": "3" }, { "u": 3, "v": 5, "w": "1" }, { "u": 3, "v": 7, "w": "1" }, { "u": 3, "v": 9, "w": "1" }, { "u": 3, "v": 14, "w": "1" }, { "u": 3, "v": 15, "w": "1" }, { "u": 4, "v": 1, "w": "1" }, { "u": 4, "v": 2, "w": "2" }, { "u": 4, "v": 3, "w": "3" }, { "u": 4, "v": 7, "w": "1" }, { "u": 4, "v": 8, "w": "1" }, { "u": 4, "v": 9, "w": "2" }, { "u": 4, "v": 13, "w": "1" }, { "u": 4, "v": 15, "w": "2" }, { "u": 5, "v": 1, "w": "2" }, { "u": 5, "v": 2, "w": "1" }, { "u": 5, "v": 3, "w": "1" }, { "u": 5, "v": 7, "w": "1" }, { "u": 5, "v": 10, "w": "1" }, { "u": 5, "v": 14, "w": "1" }, { "u": 6, "v": 1, "w": "1" }, { "u": 6, "v": 2, "w": "2" }, { "u": 6, "v": 8, "w": "2" }, { "u": 6, "v": 9, "w": "1" }, { "u": 6, "v": 11, "w": "1" }, { "u": 6, "v": 12, "w": "1" }, { "u": 7, "v": 1, "w": "2" }, { "u": 7, "v": 2, "w": "1" }, { "u": 7, "v": 3, "w": "1" }, { "u": 7, "v": 4, "w": "1" }, { "u": 7, "v": 5, "w": "1" }, { "u": 7, "v": 8, "w": "1" }, { "u": 7, "v": 9, "w": "2" }, { "u": 7, "v": 10, "w": "1" }, { "u": 7, "v": 12, "w": "1" }, { "u": 7, "v": 13, "w": "1" }, { "u": 7, "v": 15, "w": "2" }, { "u": 8, "v": 1, "w": "1" }, { "u": 8, "v": 2, "w": "2" }, { "u": 8, "v": 4, "w": "1" }, { "u": 8, "v": 6, "w": "2" }, { "u": 8, "v": 7, "w": "1" }, { "u": 8, "v": 9, "w": "1" }, { "u": 8, "v": 11, "w": "1" }, { "u": 8, "v": 12, "w": "1" }, { "u": 8, "v": 13, "w": "1" }, { "u": 9, "v": 1, "w": "1" }, { "u": 9, "v": 2, "w": "2" }, { "u": 9, "v": 3, "w": "1" }, { "u": 9, "v": 4, "w": "2" }, { "u": 9, "v": 6, "w": "1" }, { "u": 9, "v": 7, "w": "2" }, { "u": 9, "v": 8, "w": "1" }, { "u": 9, "v": 13, "w": "1" }, { "u": 9, "v": 15, "w": "1" }, { "u": 10, "v": 1, "w": "1" }, { "u": 10, "v": 5, "w": "1" }, { "u": 10, "v": 7, "w": "1" }, { "u": 10, "v": 12, "w": "1" }, { "u": 10, "v": 14, "w": "1" }, { "u": 11, "v": 1, "w": "2" }, { "u": 11, "v": 2, "w": "3" }, { "u": 11, "v": 6, "w": "1" }, { "u": 11, "v": 8, "w": "1" }, { "u": 11, "v": 12, "w": "1" }, { "u": 11, "v": 14, "w": "1" }, { "u": 11, "v": 15, "w": "1" }, { "u": 12, "v": 1, "w": "1" }, { "u": 12, "v": 2, "w": "1" }, { "u": 12, "v": 6, "w": "1" }, { "u": 12, "v": 7, "w": "1" }, { "u": 12, "v": 8, "w": "1" }, { "u": 12, "v": 10, "w": "1" }, { "u": 12, "v": 11, "w": "1" }, { "u": 12, "v": 13, "w": "1" }, { "u": 12, "v": 15, "w": "1" }, { "u": 13, "v": 1, "w": "4" }, { "u": 13, "v": 2, "w": "2" }, { "u": 13, "v": 4, "w": "1" }, { "u": 13, "v": 7, "w": "1" }, { "u": 13, "v": 8, "w": "1" }, { "u": 13, "v": 9, "w": "1" }, { "u": 13, "v": 12, "w": "1" }, { "u": 14, "v": 2, "w": "1" }, { "u": 14, "v": 3, "w": "1" }, { "u": 14, "v": 5, "w": "1" }, { "u": 14, "v": 10, "w": "1" }, { "u": 14, "v": 11, "w": "1" }, { "u": 14, "v": 15, "w": "1" }, { "u": 15, "v": 2, "w": "2" }, { "u": 15, "v": 3, "w": "1" }, { "u": 15, "v": 4, "w": "2" }, { "u": 15, "v": 7, "w": "2" }, { "u": 15, "v": 9, "w": "1" }, { "u": 15, "v": 11, "w": "1" }, { "u": 15, "v": 12, "w": "1" }, { "u": 15, "v": 14, "w": "1" } ], "hash_sha256": "d5a7775f57938275e6e07513e778752ffca173703ba7e281b0f8b38f4356da7f", "node_custom_attributes": [ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { } ], "node_labels": [ "ABN", "AMRO", "ENNIA", "NS", "BUHRT", "AGO", "AKZO", "NB", "SHV", "FGH", "HEINK", "PHLPS", "NATND", "OGEM", "RSV", "NSU" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 2 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyAdj_Undir_N3__FT3.json000066400000000000000000000056341524402210300260670ustar00rootroot00000000000000{ "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 4 }, "dataset": { "filetype": 3, "name": "TinyAdj_Undir_N3.adj", "path": "src/data/TinyAdj_Undir_N3.adj" }, "graph": { "directed": true, "relations": 1, "weighted": false }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 3, "load_ms": 0, "load_msg": "", "net_name": "TinyAdj_Undir_N3.adj", "ok": true }, "metrics": { "density": "0.66666666666666663" }, "relations_bundle": [ { "index": 0, "name": "unnamed", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 2, "v": 1, "w": "1" }, { "u": 2, "v": 3, "w": "1" }, { "u": 3, "v": 2, "w": "1" } ], "hash_sha256": "985ee9e0f5789b14deb79487aab2d8ea39aba8e29a34813d145d23fc459fc0cc", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "ties_graph": 4 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 2, "v": 1, "w": "1" }, { "u": 2, "v": 3, "w": "1" }, { "u": 3, "v": 2, "w": "1" } ], "hash_sha256": "985ee9e0f5789b14deb79487aab2d8ea39aba8e29a34813d145d23fc459fc0cc", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyAdj_Weighted_Dir_N3__FT3.json000066400000000000000000000052641524402210300273430ustar00rootroot00000000000000{ "counts": { "links_sna": 3, "nodes": 3, "ties_graph": 3 }, "dataset": { "filetype": 3, "name": "TinyAdj_Weighted_Dir_N3.adj", "path": "src/data/TinyAdj_Weighted_Dir_N3.adj" }, "graph": { "directed": true, "relations": 1, "weighted": true }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 3, "load_ms": 0, "load_msg": "", "net_name": "TinyAdj_Weighted_Dir_N3.adj", "ok": true }, "metrics": { "density": "0.5" }, "relations_bundle": [ { "index": 0, "name": "unnamed", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "2.5" }, { "u": 2, "v": 3, "w": "1.5" }, { "u": 3, "v": 1, "w": "3" } ], "hash_sha256": "774e15f0235c758b1441dd2e766ee1cc9c35f768f36bd678183111126c4934bc", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "ties_graph": 3 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "2.5" }, { "u": 2, "v": 3, "w": "1.5" }, { "u": 3, "v": 1, "w": "3" } ], "hash_sha256": "774e15f0235c758b1441dd2e766ee1cc9c35f768f36bd678183111126c4934bc", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyDL_TwoMode_3x3_labeled__FT5.json000066400000000000000000000074751524402210300300010ustar00rootroot00000000000000{ "counts": { "links_sna": 6, "nodes": 6, "ties_graph": 6 }, "dataset": { "filetype": 5, "name": "TinyDL_TwoMode_3x3_labeled.dl", "path": "src/data/TinyDL_TwoMode_3x3_labeled.dl" }, "graph": { "directed": true, "relations": 1, "weighted": false }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 5, "load_ms": 0, "load_msg": "", "net_name": "TinyDL_TwoMode_3x3_labeled.dl", "ok": true }, "metrics": { "density": "0.20000000000000001" }, "relations_bundle": [ { "index": 0, "name": "unnamed", "signature": { "edge_list": [ { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 5, "w": "1" }, { "u": 2, "v": 4, "w": "1" }, { "u": 2, "v": 6, "w": "1" }, { "u": 3, "v": 5, "w": "1" }, { "u": 3, "v": 6, "w": "1" } ], "hash_sha256": "4579aeef81a5f9a8e5c04bf0e24a3e251c81c8b2d4dc94dac57654e5054413eb", "node_custom_attributes": [ { }, { }, { }, { }, { }, { } ], "node_labels": [ "person1", "person2", "person3", "event1", "event2", "event3" ] }, "ties_graph": 6 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 5, "w": "1" }, { "u": 2, "v": 4, "w": "1" }, { "u": 2, "v": 6, "w": "1" }, { "u": 3, "v": 5, "w": "1" }, { "u": 3, "v": 6, "w": "1" } ], "hash_sha256": "4579aeef81a5f9a8e5c04bf0e24a3e251c81c8b2d4dc94dac57654e5054413eb", "node_custom_attributes": [ { }, { }, { }, { }, { }, { } ], "node_labels": [ "person1", "person2", "person3", "event1", "event2", "event3" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyDL_TwoMode_3x3_nolabels__FT5.json000066400000000000000000000073621524402210300302030ustar00rootroot00000000000000{ "counts": { "links_sna": 6, "nodes": 6, "ties_graph": 6 }, "dataset": { "filetype": 5, "name": "TinyDL_TwoMode_3x3_nolabels.dl", "path": "src/data/TinyDL_TwoMode_3x3_nolabels.dl" }, "graph": { "directed": true, "relations": 1, "weighted": false }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 5, "load_ms": 0, "load_msg": "", "net_name": "TinyDL_TwoMode_3x3_nolabels.dl", "ok": true }, "metrics": { "density": "0.20000000000000001" }, "relations_bundle": [ { "index": 0, "name": "unnamed", "signature": { "edge_list": [ { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 5, "w": "1" }, { "u": 2, "v": 4, "w": "1" }, { "u": 2, "v": 6, "w": "1" }, { "u": 3, "v": 5, "w": "1" }, { "u": 3, "v": 6, "w": "1" } ], "hash_sha256": "440c65e56605557e10abd8f80d2721e9703ea7832fc0e8fb1da86ffd7a1d57fb", "node_custom_attributes": [ { }, { }, { }, { }, { }, { } ], "node_labels": [ "", "", "", "", "", "" ] }, "ties_graph": 6 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 4, "w": "1" }, { "u": 1, "v": 5, "w": "1" }, { "u": 2, "v": 4, "w": "1" }, { "u": 2, "v": 6, "w": "1" }, { "u": 3, "v": 5, "w": "1" }, { "u": 3, "v": 6, "w": "1" } ], "hash_sha256": "440c65e56605557e10abd8f80d2721e9703ea7832fc0e8fb1da86ffd7a1d57fb", "node_custom_attributes": [ { }, { }, { }, { }, { }, { } ], "node_labels": [ "", "", "", "", "", "" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyEdgeList_Simple_Dir_N3__FT8.json000066400000000000000000000047221524402210300300410ustar00rootroot00000000000000{ "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 8, "name": "TinyEdgeList_Simple_Dir_N3.lst", "path": "src/data/TinyEdgeList_Simple_Dir_N3.lst" }, "graph": { "directed": true, "relations": 1, "weighted": false }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 8, "load_ms": 0, "load_msg": "", "net_name": "TinyEdgeList_Simple_Dir_N3.lst", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "relations_bundle": [ { "index": 0, "name": "unnamed", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 2, "v": 3, "w": "1" } ], "hash_sha256": "6b2700675a6f5366c74caec85e9c7a728364fdaad47bdb8f02f0a9bd8517360d", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "ties_graph": 2 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 2, "v": 3, "w": "1" } ], "hash_sha256": "6b2700675a6f5366c74caec85e9c7a728364fdaad47bdb8f02f0a9bd8517360d", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyEdgeList_Symmetric_Undir_N3__FT8.json000066400000000000000000000057111524402210300311260ustar00rootroot00000000000000{ "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 4 }, "dataset": { "filetype": 8, "name": "TinyEdgeList_Symmetric_Undir_N3.lst", "path": "src/data/TinyEdgeList_Symmetric_Undir_N3.lst" }, "graph": { "directed": true, "relations": 1, "weighted": false }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 8, "load_ms": 0, "load_msg": "", "net_name": "TinyEdgeList_Symmetric_Undir_N3.lst", "ok": true }, "metrics": { "density": "0.66666666666666663" }, "relations_bundle": [ { "index": 0, "name": "unnamed", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 2, "v": 1, "w": "1" }, { "u": 2, "v": 3, "w": "1" }, { "u": 3, "v": 2, "w": "1" } ], "hash_sha256": "985ee9e0f5789b14deb79487aab2d8ea39aba8e29a34813d145d23fc459fc0cc", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "ties_graph": 4 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 2, "v": 1, "w": "1" }, { "u": 2, "v": 3, "w": "1" }, { "u": 3, "v": 2, "w": "1" } ], "hash_sha256": "985ee9e0f5789b14deb79487aab2d8ea39aba8e29a34813d145d23fc459fc0cc", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyEdgeList_Weighted_Dir_N3__FT7.json000066400000000000000000000053061524402210300303460ustar00rootroot00000000000000{ "counts": { "links_sna": 3, "nodes": 3, "ties_graph": 3 }, "dataset": { "filetype": 7, "name": "TinyEdgeList_Weighted_Dir_N3.wlst", "path": "src/data/TinyEdgeList_Weighted_Dir_N3.wlst" }, "graph": { "directed": true, "relations": 1, "weighted": true }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 7, "load_ms": 0, "load_msg": "", "net_name": "TinyEdgeList_Weighted_Dir_N3.wlst", "ok": true }, "metrics": { "density": "0.5" }, "relations_bundle": [ { "index": 0, "name": "unnamed", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "2.5" }, { "u": 2, "v": 3, "w": "1.5" }, { "u": 3, "v": 1, "w": "3" } ], "hash_sha256": "774e15f0235c758b1441dd2e766ee1cc9c35f768f36bd678183111126c4934bc", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "ties_graph": 3 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "2.5" }, { "u": 2, "v": 3, "w": "1.5" }, { "u": 3, "v": 1, "w": "3" } ], "hash_sha256": "774e15f0235c758b1441dd2e766ee1cc9c35f768f36bd678183111126c4934bc", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyGML_Dir_N3__FT6.json000066400000000000000000000051031524402210300254370ustar00rootroot00000000000000{ "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 6, "name": "TinyGML_Dir_N3.gml", "path": "src/data/TinyGML_Dir_N3.gml" }, "graph": { "directed": true, "relations": 1, "weighted": false }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 6, "load_ms": 0, "load_msg": "", "net_name": "TinyGML_Dir_N3.gml", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "relations_bundle": [ { "index": 0, "name": "unnamed", "signature": { "edge_list": [ { "label": "1->2", "u": 1, "v": 2, "w": "1" }, { "label": "2->3", "u": 2, "v": 3, "w": "1" } ], "hash_sha256": "2f05549545c77d217eb28be15bb595ff66fb3807b50a0af50cbdbbac243ad018", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "ties_graph": 2 } ], "roundtrip_result": { "equivalent": true, "performed": false }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "label": "1->2", "u": 1, "v": 2, "w": "1" }, { "label": "2->3", "u": 2, "v": 3, "w": "1" } ], "hash_sha256": "2f05549545c77d217eb28be15bb595ff66fb3807b50a0af50cbdbbac243ad018", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyGML_Weighted_Dir_N3__FT6_weighted.json000066400000000000000000000056231524402210300311460ustar00rootroot00000000000000{ "counts": { "links_sna": 3, "nodes": 3, "ties_graph": 3 }, "dataset": { "filetype": 6, "name": "TinyGML_Weighted_Dir_N3.gml", "path": "src/data/TinyGML_Weighted_Dir_N3.gml" }, "graph": { "directed": true, "relations": 1, "weighted": true }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 6, "load_ms": 0, "load_msg": "", "net_name": "TinyGML_Weighted_Dir_N3.gml", "ok": true }, "metrics": { "density": "0.5" }, "relations_bundle": [ { "index": 0, "name": "unnamed", "signature": { "edge_list": [ { "label": "1->2", "u": 1, "v": 2, "w": "2.5" }, { "label": "2->3", "u": 2, "v": 3, "w": "1.5" }, { "label": "3->1", "u": 3, "v": 1, "w": "3" } ], "hash_sha256": "a12a2beae71b7840a95188e3ebc5daab8b094545655205aa17332eed4d8fc4a8", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "ties_graph": 3 } ], "roundtrip_result": { "equivalent": true, "performed": false }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "label": "1->2", "u": 1, "v": 2, "w": "2.5" }, { "label": "2->3", "u": 2, "v": 3, "w": "1.5" }, { "label": "3->1", "u": 3, "v": 1, "w": "3" } ], "hash_sha256": "a12a2beae71b7840a95188e3ebc5daab8b094545655205aa17332eed4d8fc4a8", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyGraphML_Weighted_Dir_N3__FT1.json000066400000000000000000000053301524402210300301270ustar00rootroot00000000000000{ "counts": { "links_sna": 3, "nodes": 3, "ties_graph": 3 }, "dataset": { "filetype": 1, "name": "TinyGraphML_Weighted_Dir_N3.graphml", "path": "src/data/TinyGraphML_Weighted_Dir_N3.graphml" }, "graph": { "directed": true, "relations": 1, "weighted": true }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 1, "load_ms": 0, "load_msg": "", "net_name": "TinyGraphML_Weighted_Dir_N3", "ok": true }, "metrics": { "density": "0.5" }, "relations_bundle": [ { "index": 0, "name": "TinyGraphML_Weighted_Dir_N3", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "2.5" }, { "u": 2, "v": 3, "w": "1.5" }, { "u": 3, "v": 1, "w": "3" } ], "hash_sha256": "774e15f0235c758b1441dd2e766ee1cc9c35f768f36bd678183111126c4934bc", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "ties_graph": 3 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "2.5" }, { "u": 2, "v": 3, "w": "1.5" }, { "u": 3, "v": 1, "w": "3" } ], "hash_sha256": "774e15f0235c758b1441dd2e766ee1cc9c35f768f36bd678183111126c4934bc", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyGraphviz_Dir_N3__FT4.json000066400000000000000000000047051524402210300266170ustar00rootroot00000000000000{ "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 4, "name": "TinyGraphviz_Dir_N3.dot", "path": "src/data/TinyGraphviz_Dir_N3.dot" }, "graph": { "directed": true, "relations": 1, "weighted": false }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 4, "load_ms": 0, "load_msg": "", "net_name": "TinyGraphviz_Dir_N3", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "relations_bundle": [ { "index": 0, "name": "TinyGraphviz_Dir_N3", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 2, "v": 3, "w": "1" } ], "hash_sha256": "6b2700675a6f5366c74caec85e9c7a728364fdaad47bdb8f02f0a9bd8517360d", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "ties_graph": 2 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 2, "v": 3, "w": "1" } ], "hash_sha256": "6b2700675a6f5366c74caec85e9c7a728364fdaad47bdb8f02f0a9bd8517360d", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyGraphviz_Undir_N3__FT4.json000066400000000000000000000047161524402210300271640ustar00rootroot00000000000000{ "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 4, "name": "TinyGraphviz_Undir_N3.dot", "path": "src/data/TinyGraphviz_Undir_N3.dot" }, "graph": { "directed": false, "relations": 1, "weighted": false }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 4, "load_ms": 0, "load_msg": "", "net_name": "TinyGraphviz_Undir_N3", "ok": true }, "metrics": { "density": "0.66666666666666663" }, "relations_bundle": [ { "index": 0, "name": "TinyGraphviz_Undir_N3", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 2, "v": 3, "w": "1" } ], "hash_sha256": "6b2700675a6f5366c74caec85e9c7a728364fdaad47bdb8f02f0a9bd8517360d", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "ties_graph": 2 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "1" }, { "u": 2, "v": 3, "w": "1" } ], "hash_sha256": "6b2700675a6f5366c74caec85e9c7a728364fdaad47bdb8f02f0a9bd8517360d", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/io_roundtrip/TinyGraphviz_Weighted_Dir_N3__FT4_weighted.json000066400000000000000000000053241524402210300323150ustar00rootroot00000000000000{ "counts": { "links_sna": 3, "nodes": 3, "ties_graph": 3 }, "dataset": { "filetype": 4, "name": "TinyGraphviz_Weighted_Dir_N3.dot", "path": "src/data/TinyGraphviz_Weighted_Dir_N3.dot" }, "graph": { "directed": true, "relations": 1, "weighted": true }, "kernel": "io_roundtrip", "load_report": { "fileType_signal": 4, "load_ms": 0, "load_msg": "", "net_name": "TinyGraphviz_Weighted_Dir_N3", "ok": true }, "metrics": { "density": "0.5" }, "relations_bundle": [ { "index": 0, "name": "TinyGraphviz_Weighted_Dir_N3", "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "2.5" }, { "u": 2, "v": 3, "w": "1.5" }, { "u": 3, "v": 1, "w": "3" } ], "hash_sha256": "774e15f0235c758b1441dd2e766ee1cc9c35f768f36bd678183111126c4934bc", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "ties_graph": 3 } ], "roundtrip_result": { "equivalent": true, "performed": true }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "operation": "io_roundtrip_same_format" }, "schema_version": 5, "signature": { "edge_list": [ { "u": 1, "v": 2, "w": "2.5" }, { "u": 2, "v": 3, "w": "1.5" }, { "u": 3, "v": 1, "w": "3" } ], "hash_sha256": "774e15f0235c758b1441dd2e766ee1cc9c35f768f36bd678183111126c4934bc", "node_custom_attributes": [ { }, { }, { } ], "node_labels": [ "1", "2", "3" ] }, "timings": { "load_ms": 0, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } SocNetV-3.7/src/tools/baselines/matrix/000077500000000000000000000000001524402210300200755ustar00rootroot00000000000000Benchmark_BA_Directed_N500_m3__MATRIX__V8__FT2__W0_IW1_DI0.json000066400000000000000000005371331524402210300324570ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/matrix{ "counts": { "links_sna": 1219, "nodes": 500, "ties_graph": 1219 }, "dataset": { "filetype": 2, "name": "Benchmark_BA_Directed_N500_m3.paj", "path": "src/data/Benchmark_BA_Directed_N500_m3.paj" }, "graph": { "directed": true, "weighted": false }, "kernel": "matrix", "load_report": { "fileType_signal": 2, "load_ms": 7, "load_msg": "", "net_name": "scale-free", "ok": true }, "matrices": { "adjacency": { "col_sums": [ "308", "332", "171", "0", "54", "212", "0", "0", "0", "1", "134", "0", "5", "2", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" ], "cols": 500, "dump_mode": "summary", "row_sums": [ "0", "1", "2", "2", "2", "2", "2", "3", "2", "3", "3", "3", "3", "3", "2", "2", "3", "3", "3", "3", "2", "3", "2", "2", "3", "3", "1", "1", "2", "3", "2", "3", "3", "3", "3", "3", "3", "2", "2", "1", "3", "2", "3", "2", "2", "3", "3", "2", "3", "3", "3", "2", "2", "3", "3", "3", "3", "2", "2", "3", "2", "3", "2", "2", "3", "2", "3", "2", "3", "3", "3", "2", "3", "3", "2", "3", "3", "3", "3", "2", "2", "2", "2", "3", "3", "3", "3", "2", "3", "3", "3", "3", "3", "2", "2", "2", "2", "1", "2", "3", "3", "2", "2", "3", "2", "2", "2", "3", "3", "3", "2", "2", "2", "2", "3", "3", "2", "1", "3", "3", "3", "2", "2", "3", "2", "3", "2", "3", "3", "3", "3", "2", "2", "1", "3", "2", "1", "2", "2", "2", "2", "2", "2", "2", "2", "2", "3", "1", "2", "2", "2", "3", "2", "1", "3", "3", "3", "3", "2", "3", "2", "2", "2", "3", "2", "3", "3", "2", "3", "3", "2", "2", "2", "2", "3", "2", "2", "3", "2", "2", "3", "2", "2", "1", "3", "3", "2", "3", "2", "3", "2", "1", "3", "1", "2", "2", "2", "2", "3", "3", "2", "2", "3", "3", "3", "3", "3", "3", "2", "2", "2", "2", "2", "2", "3", "3", "2", "2", "2", "3", "3", "2", "2", "2", "3", "1", "3", "3", "2", "3", "3", "1", "2", "2", "3", "3", "2", "2", "3", "2", "3", "3", "3", "1", "2", "3", "3", "3", "2", "3", "2", "3", "3", "3", "2", "3", "2", "3", "3", "3", "3", "3", "2", "2", "3", "3", "3", "3", "1", "2", "3", "3", "2", "2", "3", "3", "3", "3", "3", "2", "2", "2", "3", "2", "2", "3", "2", "2", "2", "3", "2", "3", "3", "2", "3", "2", "3", "2", "2", "1", "2", "3", "3", "3", "2", "3", "2", "3", "3", "2", "2", "3", "2", "3", "3", "3", "3", "3", "2", "3", "3", "2", "2", "2", "1", "2", "3", "3", "2", "2", "2", "2", "3", "3", "3", "2", "2", "3", "3", "3", "2", "1", "1", "3", "3", "2", "2", "3", "3", "2", "3", "3", "2", "2", "2", "3", "2", "3", "2", "2", "2", "2", "3", "3", "3", "2", "3", "2", "2", "3", "3", "3", "2", "3", "2", "3", "2", "2", "3", "2", "3", "2", "2", "3", "3", "3", "2", "2", "2", "2", "3", "2", "2", "3", "3", "3", "2", "2", "3", "2", "2", "3", "3", "1", "3", "3", "3", "3", "3", "3", "3", "3", "2", "2", "3", "3", "3", "2", "3", "2", "2", "3", "2", "2", "3", "2", "2", "3", "3", "2", "3", "3", "2", "2", "2", "3", "3", "2", "3", "2", "2", "1", "3", "3", "3", "3", "3", "3", "3", "2", "3", "3", "3", "2", "2", "3", "3", "2", "2", "2", "3", "3", "2", "2", "2", "2", "2", "2", "3", "3", "3", "2", "3", "2", "3", "2", "3", "3", "2", "2", "3", "3", "2", "3", "2", "2", "2", "2", "2", "2", "3", "2", "3", "2", "1", "2", "3", "3", "2", "2" ], "rows": 500, "sample_cells": { "0,0": "0", "0,499": "0", "250,250": "0", "499,0": "1", "499,499": "0" }, "trace": "0" }, "adjacency_inverse": { "col_sums": [ "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" ], "cols": 500, "dump_mode": "summary", "invertible": false, "row_sums": [ "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" ], "rows": 500, "sample_cells": { "0,0": "0", "0,499": "0", "250,250": "0", "499,0": "0", "499,499": "0" }, "trace": "0" }, "clique_comembership": { "col_sums": [ "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1" ], "cols": 500, "dump_mode": "summary", "row_sums": [ "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1" ], "rows": 500, "sample_cells": { "0,0": "1", "0,499": "0", "250,250": "1", "499,0": "0", "499,499": "1" }, "trace": "500" }, "distances": { "col_sums": [ "690", "27917288056", "607737872362", "1071594339853", "955630222969", "414464344271", "1071594339853", "1071594339853", "1071594339853", "1069446856207", "783831531289", "1071594339853", "1060856921623", "1067299372561", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853", "1071594339853" ], "cols": 500, "dump_mode": "summary", "row_sums": [ "1071594339853", "1069446856207", "1067299372561", "1067299372561", "1065151888916", "1067299372561", "1063004405271", "1063004405270", "1065151888916", "1065151888915", "1065151888915", "1065151888915", "1065151888915", "1065151888915", "1063004405271", "1065151888916", "1063004405270", "1063004405270", "1058709437980", "1058709437980", "1063004405271", "1063004405270", "1063004405271", "1065151888916", "1060856921625", "1065151888915", "1069446856207", "1067299372562", "1063004405271", "1063004405270", "1065151888916", "1060856921625", "1063004405270", "1060856921625", "1065151888915", "1063004405270", "1060856921625", "1067299372561", "1067299372561", "1067299372562", "1063004405270", "1063004405271", "1063004405270", "1067299372561", "1063004405272", "1063004405270", "1058709437980", "1063004405271", "1060856921625", "1063004405270", "1063004405270", "1065151888916", "1063004405271", "1060856921625", "1063004405270", "1060856921625", "1063004405270", "1063004405271", "1063004405271", "1060856921625", "1063004405271", "1065151888915", "1063004405271", "1065151888916", "1060856921625", "1065151888916", "1063004405270", "1063004405271", "1063004405270", "1060856921625", "1063004405270", "1063004405271", "1063004405270", "1063004405270", "1065151888916", "1065151888915", "1063004405270", "1065151888915", "1063004405270", "1065151888916", "1065151888916", "1060856921626", "1065151888916", "1065151888915", "1063004405270", "1063004405270", "1063004405270", "1065151888916", "1063004405270", "1065151888915", "1063004405270", "1063004405270", "1063004405270", "1063004405271", "1067299372561", "1063004405271", "1063004405271", "1069446856207", "1067299372561", "1065151888915", "1065151888915", "1067299372561", "1063004405271", "1065151888915", "1065151888916", "1065151888916", "1065151888916", "1065151888915", "1060856921625", "1065151888915", "1067299372561", "1067299372561", "1067299372561", "1067299372561", "1063004405270", "1063004405270", "1065151888916", "1067299372562", "1065151888915", "1065151888915", "1063004405270", "1067299372561", "1065151888916", "1065151888915", "1067299372561", "1065151888915", "1067299372561", "1063004405270", "1058709437980", "1063004405270", "1060856921625", "1065151888916", "1063004405271", "1067299372562", "1063004405270", "1067299372561", "1069446856207", "1065151888916", "1063004405271", "1067299372561", "1063004405271", "1067299372561", "1063004405271", "1065151888916", "1065151888916", "1067299372561", "1063004405270", "1065151888917", "1065151888916", "1065151888916", "1060856921626", "1065151888915", "1065151888916", "1069446856207", "1065151888915", "1063004405270", "1065151888915", "1063004405270", "1063004405271", "1063004405270", "1065151888916", "1067299372561", "1065151888916", "1060856921625", "1067299372561", "1063004405270", "1065151888915", "1063004405272", "1065151888915", "1063004405270", "1067299372561", "1067299372561", "1067299372561", "1063004405271", "1063004405270", "1065151888916", "1063004405271", "1060856921625", "1065151888916", "1067299372561", "1063004405270", "1067299372561", "1067299372561", "1069446856207", "1063004405270", "1063004405270", "1065151888916", "1063004405270", "1063004405271", "1065151888915", "1065151888916", "1069446856207", "1063004405270", "1067299372562", "1063004405271", "1063004405271", "1065151888916", "1063004405271", "1065151888915", "1065151888915", "1063004405271", "1065151888916", "1065151888915", "1060856921625", "1063004405270", "1065151888915", "1063004405270", "1063004405270", "1067299372561", "1065151888916", "1067299372561", "1065151888916", "1065151888916", "1060856921626", "1060856921625", "1063004405270", "1067299372561", "1065151888916", "1065151888916", "1063004405270", "1063004405270", "1063004405271", "1063004405271", "1067299372561", "1060856921625", "1067299372562", "1065151888915", "1063004405270", "1065151888916", "1063004405270", "1065151888915", "1069446856207", "1065151888916", "1067299372561", "1063004405270", "1063004405270", "1063004405271", "1065151888916", "1063004405270", "1065151888916", "1060856921625", "1063004405270", "1063004405270", "1069446856207", "1060856921626", "1063004405270", "1060856921625", "1065151888915", "1060856921626", "1060856921625", "1065151888916", "1065151888915", "1063004405270", "1065151888915", "1063004405271", "1058709437980", "1063004405271", "1065151888915", "1065151888915", "1060856921625", "1063004405270", "1063004405270", "1065151888916", "1065151888916", "1065151888915", "1060856921625", "1063004405270", "1060856921625", "1069446856207", "1065151888916", "1063004405270", "1060856921625", "1065151888916", "1063004405271", "1060856921625", "1063004405270", "1065151888915", "1065151888915", "1063004405270", "1067299372561", "1065151888916", "1065151888916", "1065151888915", "1065151888916", "1063004405271", "1063004405270", "1065151888916", "1063004405271", "1063004405271", "1063004405270", "1065151888916", "1063004405270", "1060856921625", "1063004405271", "1063004405270", "1067299372561", "1063004405270", "1065151888916", "1065151888916", "1067299372562", "1065151888916", "1063004405270", "1065151888915", "1065151888915", "1067299372561", "1063004405270", "1067299372561", "1063004405270", "1060856921625", "1065151888916", "1067299372561", "1065151888915", "1067299372561", "1063004405270", "1063004405270", "1065151888915", "1063004405270", "1060856921625", "1065151888916", "1063004405270", "1060856921625", "1067299372561", "1067299372561", "1065151888916", "1069446856207", "1065151888916", "1063004405270", "1063004405270", "1067299372561", "1067299372561", "1067299372561", "1067299372561", "1060856921625", "1060856921625", "1065151888915", "1065151888916", "1065151888916", "1060856921625", "1065151888915", "1063004405270", "1060856921626", "1067299372562", "1069446856207", "1065151888915", "1065151888915", "1063004405271", "1065151888916", "1058709437980", "1063004405270", "1065151888916", "1063004405270", "1060856921625", "1067299372561", "1067299372561", "1063004405271", "1063004405270", "1065151888916", "1058709437980", "1063004405271", "1065151888916", "1067299372561", "1065151888916", "1063004405270", "1060856921625", "1063004405270", "1065151888916", "1063004405270", "1065151888916", "1065151888916", "1063004405270", "1065151888915", "1065151888915", "1067299372561", "1058709437980", "1065151888916", "1060856921625", "1067299372561", "1063004405271", "1063004405270", "1067299372561", "1065151888915", "1067299372561", "1067299372561", "1065151888915", "1060856921625", "1060856921625", "1065151888916", "1063004405271", "1063004405271", "1065151888916", "1065151888915", "1063004405271", "1063004405271", "1063004405270", "1065151888915", "1065151888915", "1063004405271", "1065151888916", "1063004405270", "1063004405271", "1065151888916", "1065151888915", "1063004405270", "1069446856207", "1065151888915", "1065151888915", "1065151888915", "1065151888915", "1063004405270", "1063004405270", "1063004405270", "1063004405270", "1067299372561", "1067299372561", "1065151888915", "1063004405270", "1060856921625", "1063004405271", "1063004405270", "1067299372561", "1065151888916", "1063004405270", "1065151888916", "1065151888916", "1065151888915", "1067299372561", "1063004405271", "1060856921625", "1063004405270", "1065151888916", "1063004405270", "1063004405270", "1067299372561", "1065151888916", "1065151888916", "1065151888915", "1063004405270", "1065151888916", "1065151888915", "1063004405271", "1065151888916", "1067299372562", "1058709437980", "1065151888915", "1063004405270", "1060856921625", "1063004405270", "1063004405270", "1063004405270", "1063004405271", "1065151888915", "1065151888915", "1065151888915", "1067299372561", "1065151888916", "1063004405270", "1063004405270", "1065151888916", "1065151888916", "1067299372561", "1065151888915", "1063004405270", "1063004405271", "1065151888916", "1067299372561", "1063004405271", "1065151888916", "1063004405272", "1065151888915", "1060856921625", "1063004405270", "1065151888916", "1065151888915", "1063004405271", "1063004405270", "1063004405272", "1065151888915", "1063004405270", "1067299372561", "1065151888916", "1058709437980", "1065151888915", "1067299372561", "1065151888915", "1067299372561", "1063004405272", "1065151888916", "1063004405271", "1063004405271", "1063004405271", "1063004405270", "1065151888916", "1063004405270", "1067299372561", "1067299372562", "1065151888916", "1063004405270", "1065151888915", "1063004405271", "1065151888916" ], "rows": 500, "sample_cells": { "0,0": "0", "0,499": "2147483647", "250,250": "0", "499,0": "1", "499,499": "0" }, "trace": "0" }, "reachability": { "col_sums": [ "500", "487", "217", "1", "55", "307", "1", "1", "1", "2", "135", "1", "6", "3", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1" ], "cols": 500, "dump_mode": "summary", "row_sums": [ "1", "2", "3", "3", "4", "3", "5", "5", "4", "4", "4", "4", "4", "4", "5", "4", "5", "5", "7", "7", "5", "5", "5", "4", "6", "4", "2", "3", "5", "5", "4", "6", "5", "6", "4", "5", "6", "3", "3", "3", "5", "5", "5", "3", "5", "5", "7", "5", "6", "5", "5", "4", "5", "6", "5", "6", "5", "5", "5", "6", "5", "4", "5", "4", "6", "4", "5", "5", "5", "6", "5", "5", "5", "5", "4", "4", "5", "4", "5", "4", "4", "6", "4", "4", "5", "5", "5", "4", "5", "4", "5", "5", "5", "5", "3", "5", "5", "2", "3", "4", "4", "3", "5", "4", "4", "4", "4", "4", "6", "4", "3", "3", "3", "3", "5", "5", "4", "3", "4", "4", "5", "3", "4", "4", "3", "4", "3", "5", "7", "5", "6", "4", "5", "3", "5", "3", "2", "4", "5", "3", "5", "3", "5", "4", "4", "3", "5", "4", "4", "4", "6", "4", "4", "2", "4", "5", "4", "5", "5", "5", "4", "3", "4", "6", "3", "5", "4", "5", "4", "5", "3", "3", "3", "5", "5", "4", "5", "6", "4", "3", "5", "3", "3", "2", "5", "5", "4", "5", "5", "4", "4", "2", "5", "3", "5", "5", "4", "5", "4", "4", "5", "4", "4", "6", "5", "4", "5", "5", "3", "4", "3", "4", "4", "6", "6", "5", "3", "4", "4", "5", "5", "5", "5", "3", "6", "3", "4", "5", "4", "5", "4", "2", "4", "3", "5", "5", "5", "4", "5", "4", "6", "5", "5", "2", "6", "5", "6", "4", "6", "6", "4", "4", "5", "4", "5", "7", "5", "4", "4", "6", "5", "5", "4", "4", "4", "6", "5", "6", "2", "4", "5", "6", "4", "5", "6", "5", "4", "4", "5", "3", "4", "4", "4", "4", "5", "5", "4", "5", "5", "5", "4", "5", "6", "5", "5", "3", "5", "4", "4", "3", "4", "5", "4", "4", "3", "5", "3", "5", "6", "4", "3", "4", "3", "5", "5", "4", "5", "6", "4", "5", "6", "3", "3", "4", "2", "4", "5", "5", "3", "3", "3", "3", "6", "6", "4", "4", "4", "6", "4", "5", "6", "3", "2", "4", "4", "5", "4", "7", "5", "4", "5", "6", "3", "3", "5", "5", "4", "7", "5", "4", "3", "4", "5", "6", "5", "4", "5", "4", "4", "5", "4", "4", "3", "7", "4", "6", "3", "5", "5", "3", "4", "3", "3", "4", "6", "6", "4", "5", "5", "4", "4", "5", "5", "5", "4", "4", "5", "4", "5", "5", "4", "4", "5", "2", "4", "4", "4", "4", "5", "5", "5", "5", "3", "3", "4", "5", "6", "5", "5", "3", "4", "5", "4", "4", "4", "3", "5", "6", "5", "4", "5", "5", "3", "4", "4", "4", "5", "4", "4", "5", "4", "3", "7", "4", "5", "6", "5", "5", "5", "5", "4", "4", "4", "3", "4", "5", "5", "4", "4", "3", "4", "5", "5", "4", "3", "5", "4", "5", "4", "6", "5", "4", "4", "5", "5", "5", "4", "5", "3", "4", "7", "4", "3", "4", "3", "5", "4", "5", "5", "5", "5", "4", "5", "3", "3", "4", "5", "4", "5", "4" ], "rows": 500, "sample_cells": { "0,0": "1", "0,499": "0", "250,250": "1", "499,0": "1", "499,499": "1" }, "trace": "500" }, "similarity": { "col_sums": [ "498.17068273092292", "498.45381526104353", "498.46184738955759", "498.11847389558159", "497.58032128513963", "498.5441767068267", "497.08634538152501", "496.78313253011925", "497.56827309236849", "497.96987951807148", "498.23694779116397", "497.80321285140474", "497.81325301204731", "497.80722891566177", "497.41967871485838", "497.73293172690671", "497.41767068272992", "497.41767068272992", "496.34136546184595", "495.89156626505866", "497.09839357429604", "497.65461847389469", "497.09839357429604", "497.73293172690671", "496.30120481927571", "497.80321285140474", "497.78714859437662", "497.88353413654534", "496.79317269076182", "497.17269076305115", "497.73293172690671", "497.00803212851292", "496.48995983935612", "496.85140562248876", "497.80321285140474", "497.33333333333229", "496.85140562248876", "498.11847389558159", "498.11847389558159", "497.88353413654534", "497.65461847389469", "497.08634538152501", "497.41767068272992", "498.11847389558159", "497.00200803212738", "497.65461847389469", "496.43775100401467", "497.32329317268972", "497.00803212851292", "497.65461847389469", "497.65461847389469", "497.73293172690671", "497.32329317268972", "497.10441767068158", "497.26907630521981", "496.94779116465742", "497.41767068272992", "496.93775100401484", "497.41967871485838", "497.10441767068158", "497.41967871485838", "497.96787148594296", "497.09839357429604", "497.73293172690671", "496.65461847389429", "497.63654618473805", "497.17269076305115", "496.90160642570157", "497.17269076305115", "497.10441767068158", "497.17269076305115", "497.09839357429604", "497.32128514056126", "497.17269076305115", "497.56827309236849", "497.96787148594296", "497.26907630521981", "497.96787148594296", "497.17269076305115", "497.56827309236849", "497.47188755019982", "496.77309236947667", "497.63654618473805", "497.96787148594296", "497.32128514056126", "497.65461847389469", "497.41767068272992", "497.73293172690671", "497.41767068272992", "497.96787148594296", "497.26907630521981", "497.41767068272992", "497.65461847389469", "497.41967871485838", "498.11847389558159", "497.09839357429604", "496.45180722891428", "497.78714859437662", "498.11847389558159", "497.96787148594296", "497.80321285140474", "498.11847389558159", "497.08634538152501", "497.96787148594296", "497.63654618473805", "497.73293172690671", "497.73293172690671", "497.80321285140474", "496.94779116465742", "497.80321285140474", "498.11847389558159", "498.11847389558159", "498.11847389558159", "498.11847389558159", "497.26907630521981", "497.26907630521981", "497.73293172690671", "497.88353413654534", "497.96787148594296", "497.96787148594296", "497.26907630521981", "498.11847389558159", "497.73293172690671", "497.80321285140474", "498.11847389558159", "497.96787148594296", "498.11847389558159", "496.78313253011925", "496.53815261044042", "497.41767068272992", "497.10441767068158", "497.47188755019982", "497.09839357429604", "497.88353413654534", "497.65461847389469", "498.11847389558159", "497.78714859437662", "497.47188755019982", "497.41967871485838", "498.11847389558159", "497.08634538152501", "498.11847389558159", "496.45180722891428", "497.73293172690671", "497.56827309236849", "498.11847389558159", "497.65461847389469", "497.4016064257018", "497.56827309236849", "497.63654618473805", "496.77309236947667", "497.80321285140474", "497.63654618473805", "497.78714859437662", "497.96787148594296", "497.26907630521981", "497.80321285140474", "497.17269076305115", "497.32329317268972", "497.32128514056126", "497.73293172690671", "498.11847389558159", "497.73293172690671", "496.30120481927571", "498.11847389558159", "497.41767068272992", "497.80321285140474", "497.00200803212738", "497.96787148594296", "497.41767068272992", "498.11847389558159", "498.11847389558159", "498.11847389558159", "497.41967871485838", "497.17269076305115", "497.73293172690671", "497.32329317268972", "497.10441767068158", "497.47188755019982", "498.11847389558159", "497.17269076305115", "498.11847389558159", "498.11847389558159", "497.78714859437662", "496.78313253011925", "497.17269076305115", "497.63654618473805", "496.68674698795053", "497.32329317268972", "497.96787148594296", "497.73293172690671", "497.78714859437662", "497.26907630521981", "497.88353413654534", "497.41967871485838", "497.32329317268972", "497.63654618473805", "497.09839357429604", "497.96787148594296", "497.80321285140474", "497.41967871485838", "497.73293172690671", "497.96787148594296", "496.94779116465742", "497.65461847389469", "497.80321285140474", "497.32128514056126", "497.32128514056126", "498.11847389558159", "497.47188755019982", "498.11847389558159", "497.63654618473805", "497.73293172690671", "496.77309236947667", "497.10441767068158", "497.32128514056126", "498.11847389558159", "497.73293172690671", "497.73293172690671", "497.41767068272992", "497.65461847389469", "497.41967871485838", "497.41967871485838", "498.11847389558159", "496.62248995983811", "497.88353413654534", "497.80321285140474", "497.33333333333229", "497.73293172690671", "497.33333333333229", "497.80321285140474", "497.78714859437662", "497.73293172690671", "498.11847389558159", "497.41767068272992", "497.65461847389469", "497.32329317268972", "497.56827309236849", "497.41767068272992", "497.56827309236849", "497.10441767068158", "497.17269076305115", "497.32128514056126", "497.78714859437662", "496.77309236947667", "496.78313253011925", "497.10441767068158", "497.96787148594296", "496.77309236947667", "497.10441767068158", "497.63654618473805", "497.96787148594296", "497.41767068272992", "497.80321285140474", "497.41967871485838", "495.98795180722738", "497.32329317268972", "497.96787148594296", "497.80321285140474", "496.85140562248876", "496.78313253011925", "497.41767068272992", "497.73293172690671", "497.63654618473805", "497.80321285140474", "497.10441767068158", "497.33333333333229", "497.00803212851292", "497.78714859437662", "497.47188755019982", "497.65461847389469", "496.94779116465742", "497.47188755019982", "497.41967871485838", "497.00803212851292", "497.41767068272992", "497.80321285140474", "497.80321285140474", "497.32128514056126", "498.11847389558159", "497.56827309236849", "497.63654618473805", "497.80321285140474", "497.47188755019982", "497.32329317268972", "497.32128514056126", "497.47188755019982", "497.32329317268972", "497.08634538152501", "497.17269076305115", "497.56827309236849", "496.78313253011925", "497.00803212851292", "497.09839357429604", "497.32128514056126", "498.11847389558159", "497.33333333333229", "497.73293172690671", "497.47188755019982", "497.88353413654534", "497.63654618473805", "497.33333333333229", "497.96787148594296", "497.96787148594296", "498.11847389558159", "497.41767068272992", "498.11847389558159", "497.65461847389469", "496.85140562248876", "497.63654618473805", "498.11847389558159", "497.96787148594296", "498.11847389558159", "497.32128514056126", "497.17269076305115", "497.80321285140474", "497.32128514056126", "497.10441767068158", "497.56827309236849", "497.41767068272992", "496.94779116465742", "498.11847389558159", "498.11847389558159", "497.63654618473805", "497.78714859437662", "497.63654618473805", "497.32128514056126", "497.17269076305115", "498.11847389558159", "498.11847389558159", "498.11847389558159", "498.11847389558159", "497.10441767068158", "497.10441767068158", "497.96787148594296", "497.73293172690671", "497.73293172690671", "496.62248995983811", "497.96787148594296", "497.32128514056126", "496.6164658634525", "497.88353413654534", "497.78714859437662", "497.80321285140474", "497.96787148594296", "497.32329317268972", "497.73293172690671", "496.63453815260908", "497.65461847389469", "497.47188755019982", "497.26907630521981", "497.10441767068158", "498.11847389558159", "498.11847389558159", "497.32329317268972", "497.41767068272992", "497.56827309236849", "496.63453815260908", "497.41967871485838", "497.47188755019982", "498.11847389558159", "497.56827309236849", "497.17269076305115", "496.30120481927571", "497.33333333333229", "497.56827309236849", "497.65461847389469", "497.63654618473805", "497.63654618473805", "497.41767068272992", "497.96787148594296", "497.96787148594296", "498.11847389558159", "496.53815261044042", "497.73293172690671", "496.94779116465742", "498.11847389558159", "497.32329317268972", "497.17269076305115", "498.11847389558159", "497.96787148594296", "498.11847389558159", "498.11847389558159", "497.96787148594296", "496.62248995983811", "497.00803212851292", "497.73293172690671", "497.41967871485838", "497.32329317268972", "497.47188755019982", "497.80321285140474", "497.08634538152501", "496.93775100401484", "497.26907630521981", "497.96787148594296", "497.96787148594296", "497.41967871485838", "497.73293172690671", "497.41767068272992", "497.32329317268972", "497.73293172690671", "497.80321285140474", "497.65461847389469", "497.78714859437662", "497.80321285140474", "497.80321285140474", "497.96787148594296", "497.80321285140474", "497.26907630521981", "497.33333333333229", "497.32128514056126", "497.41767068272992", "498.11847389558159", "498.11847389558159", "497.96787148594296", "497.26907630521981", "497.10441767068158", "497.32329317268972", "497.32128514056126", "498.11847389558159", "497.63654618473805", "497.41767068272992", "497.47188755019982", "497.63654618473805", "497.96787148594296", "498.11847389558159", "497.32329317268972", "496.62248995983811", "497.65461847389469", "497.56827309236849", "497.41767068272992", "497.41767068272992", "498.11847389558159", "497.56827309236849", "497.63654618473805", "497.80321285140474", "497.26907630521981", "497.63654618473805", "497.96787148594296", "497.08634538152501", "497.73293172690671", "497.88353413654534", "496.1526104417656", "497.80321285140474", "497.41767068272992", "497.10441767068158", "497.26907630521981", "496.78313253011925", "497.17269076305115", "497.08634538152501", "497.96787148594296", "497.96787148594296", "497.96787148594296", "498.11847389558159", "497.47188755019982", "497.41767068272992", "497.32128514056126", "497.56827309236849", "497.63654618473805", "498.11847389558159", "497.80321285140474", "496.68674698795053", "497.32329317268972", "497.63654618473805", "498.11847389558159", "497.32329317268972", "497.63654618473805", "497.00200803212738", "497.80321285140474", "497.10441767068158", "497.32128514056126", "497.47188755019982", "497.96787148594296", "496.93775100401484", "497.26907630521981", "497.00200803212738", "497.96787148594296", "497.41767068272992", "498.11847389558159", "497.63654618473805", "496.53815261044042", "497.80321285140474", "498.11847389558159", "497.96787148594296", "498.11847389558159", "497.00200803212738", "497.56827309236849", "497.41967871485838", "497.09839357429604", "497.32329317268972", "497.17269076305115", "497.63654618473805", "497.65461847389469", "498.11847389558159", "497.88353413654534", "497.56827309236849", "497.65461847389469", "497.96787148594296", "497.32329317268972", "497.63654618473805" ], "cols": 500, "dump_mode": "summary", "metric": "simple_matching", "row_sums": [ "498.17068273092292", "498.45381526104353", "498.46184738955759", "498.11847389558159", "497.58032128513963", "498.5441767068267", "497.08634538152501", "496.78313253011925", "497.56827309236849", "497.96987951807148", "498.23694779116397", "497.80321285140474", "497.81325301204731", "497.80722891566177", "497.41967871485838", "497.73293172690671", "497.41767068272992", "497.41767068272992", "496.34136546184595", "495.89156626505866", "497.09839357429604", "497.65461847389469", "497.09839357429604", "497.73293172690671", "496.30120481927571", "497.80321285140474", "497.78714859437662", "497.88353413654534", "496.79317269076182", "497.17269076305115", "497.73293172690671", "497.00803212851292", "496.48995983935612", "496.85140562248876", "497.80321285140474", "497.33333333333229", "496.85140562248876", "498.11847389558159", "498.11847389558159", "497.88353413654534", "497.65461847389469", "497.08634538152501", "497.41767068272992", "498.11847389558159", "497.00200803212738", "497.65461847389469", "496.43775100401467", "497.32329317268972", "497.00803212851292", "497.65461847389469", "497.65461847389469", "497.73293172690671", "497.32329317268972", "497.10441767068158", "497.26907630521981", "496.94779116465742", "497.41767068272992", "496.93775100401484", "497.41967871485838", "497.10441767068158", "497.41967871485838", "497.96787148594296", "497.09839357429604", "497.73293172690671", "496.65461847389429", "497.63654618473805", "497.17269076305115", "496.90160642570157", "497.17269076305115", "497.10441767068158", "497.17269076305115", "497.09839357429604", "497.32128514056126", "497.17269076305115", "497.56827309236849", "497.96787148594296", "497.26907630521981", "497.96787148594296", "497.17269076305115", "497.56827309236849", "497.47188755019982", "496.77309236947667", "497.63654618473805", "497.96787148594296", "497.32128514056126", "497.65461847389469", "497.41767068272992", "497.73293172690671", "497.41767068272992", "497.96787148594296", "497.26907630521981", "497.41767068272992", "497.65461847389469", "497.41967871485838", "498.11847389558159", "497.09839357429604", "496.45180722891428", "497.78714859437662", "498.11847389558159", "497.96787148594296", "497.80321285140474", "498.11847389558159", "497.08634538152501", "497.96787148594296", "497.63654618473805", "497.73293172690671", "497.73293172690671", "497.80321285140474", "496.94779116465742", "497.80321285140474", "498.11847389558159", "498.11847389558159", "498.11847389558159", "498.11847389558159", "497.26907630521981", "497.26907630521981", "497.73293172690671", "497.88353413654534", "497.96787148594296", "497.96787148594296", "497.26907630521981", "498.11847389558159", "497.73293172690671", "497.80321285140474", "498.11847389558159", "497.96787148594296", "498.11847389558159", "496.78313253011925", "496.53815261044042", "497.41767068272992", "497.10441767068158", "497.47188755019982", "497.09839357429604", "497.88353413654534", "497.65461847389469", "498.11847389558159", "497.78714859437662", "497.47188755019982", "497.41967871485838", "498.11847389558159", "497.08634538152501", "498.11847389558159", "496.45180722891428", "497.73293172690671", "497.56827309236849", "498.11847389558159", "497.65461847389469", "497.4016064257018", "497.56827309236849", "497.63654618473805", "496.77309236947667", "497.80321285140474", "497.63654618473805", "497.78714859437662", "497.96787148594296", "497.26907630521981", "497.80321285140474", "497.17269076305115", "497.32329317268972", "497.32128514056126", "497.73293172690671", "498.11847389558159", "497.73293172690671", "496.30120481927571", "498.11847389558159", "497.41767068272992", "497.80321285140474", "497.00200803212738", "497.96787148594296", "497.41767068272992", "498.11847389558159", "498.11847389558159", "498.11847389558159", "497.41967871485838", "497.17269076305115", "497.73293172690671", "497.32329317268972", "497.10441767068158", "497.47188755019982", "498.11847389558159", "497.17269076305115", "498.11847389558159", "498.11847389558159", "497.78714859437662", "496.78313253011925", "497.17269076305115", "497.63654618473805", "496.68674698795053", "497.32329317268972", "497.96787148594296", "497.73293172690671", "497.78714859437662", "497.26907630521981", "497.88353413654534", "497.41967871485838", "497.32329317268972", "497.63654618473805", "497.09839357429604", "497.96787148594296", "497.80321285140474", "497.41967871485838", "497.73293172690671", "497.96787148594296", "496.94779116465742", "497.65461847389469", "497.80321285140474", "497.32128514056126", "497.32128514056126", "498.11847389558159", "497.47188755019982", "498.11847389558159", "497.63654618473805", "497.73293172690671", "496.77309236947667", "497.10441767068158", "497.32128514056126", "498.11847389558159", "497.73293172690671", "497.73293172690671", "497.41767068272992", "497.65461847389469", "497.41967871485838", "497.41967871485838", "498.11847389558159", "496.62248995983811", "497.88353413654534", "497.80321285140474", "497.33333333333229", "497.73293172690671", "497.33333333333229", "497.80321285140474", "497.78714859437662", "497.73293172690671", "498.11847389558159", "497.41767068272992", "497.65461847389469", "497.32329317268972", "497.56827309236849", "497.41767068272992", "497.56827309236849", "497.10441767068158", "497.17269076305115", "497.32128514056126", "497.78714859437662", "496.77309236947667", "496.78313253011925", "497.10441767068158", "497.96787148594296", "496.77309236947667", "497.10441767068158", "497.63654618473805", "497.96787148594296", "497.41767068272992", "497.80321285140474", "497.41967871485838", "495.98795180722738", "497.32329317268972", "497.96787148594296", "497.80321285140474", "496.85140562248876", "496.78313253011925", "497.41767068272992", "497.73293172690671", "497.63654618473805", "497.80321285140474", "497.10441767068158", "497.33333333333229", "497.00803212851292", "497.78714859437662", "497.47188755019982", "497.65461847389469", "496.94779116465742", "497.47188755019982", "497.41967871485838", "497.00803212851292", "497.41767068272992", "497.80321285140474", "497.80321285140474", "497.32128514056126", "498.11847389558159", "497.56827309236849", "497.63654618473805", "497.80321285140474", "497.47188755019982", "497.32329317268972", "497.32128514056126", "497.47188755019982", "497.32329317268972", "497.08634538152501", "497.17269076305115", "497.56827309236849", "496.78313253011925", "497.00803212851292", "497.09839357429604", "497.32128514056126", "498.11847389558159", "497.33333333333229", "497.73293172690671", "497.47188755019982", "497.88353413654534", "497.63654618473805", "497.33333333333229", "497.96787148594296", "497.96787148594296", "498.11847389558159", "497.41767068272992", "498.11847389558159", "497.65461847389469", "496.85140562248876", "497.63654618473805", "498.11847389558159", "497.96787148594296", "498.11847389558159", "497.32128514056126", "497.17269076305115", "497.80321285140474", "497.32128514056126", "497.10441767068158", "497.56827309236849", "497.41767068272992", "496.94779116465742", "498.11847389558159", "498.11847389558159", "497.63654618473805", "497.78714859437662", "497.63654618473805", "497.32128514056126", "497.17269076305115", "498.11847389558159", "498.11847389558159", "498.11847389558159", "498.11847389558159", "497.10441767068158", "497.10441767068158", "497.96787148594296", "497.73293172690671", "497.73293172690671", "496.62248995983811", "497.96787148594296", "497.32128514056126", "496.6164658634525", "497.88353413654534", "497.78714859437662", "497.80321285140474", "497.96787148594296", "497.32329317268972", "497.73293172690671", "496.63453815260908", "497.65461847389469", "497.47188755019982", "497.26907630521981", "497.10441767068158", "498.11847389558159", "498.11847389558159", "497.32329317268972", "497.41767068272992", "497.56827309236849", "496.63453815260908", "497.41967871485838", "497.47188755019982", "498.11847389558159", "497.56827309236849", "497.17269076305115", "496.30120481927571", "497.33333333333229", "497.56827309236849", "497.65461847389469", "497.63654618473805", "497.63654618473805", "497.41767068272992", "497.96787148594296", "497.96787148594296", "498.11847389558159", "496.53815261044042", "497.73293172690671", "496.94779116465742", "498.11847389558159", "497.32329317268972", "497.17269076305115", "498.11847389558159", "497.96787148594296", "498.11847389558159", "498.11847389558159", "497.96787148594296", "496.62248995983811", "497.00803212851292", "497.73293172690671", "497.41967871485838", "497.32329317268972", "497.47188755019982", "497.80321285140474", "497.08634538152501", "496.93775100401484", "497.26907630521981", "497.96787148594296", "497.96787148594296", "497.41967871485838", "497.73293172690671", "497.41767068272992", "497.32329317268972", "497.73293172690671", "497.80321285140474", "497.65461847389469", "497.78714859437662", "497.80321285140474", "497.80321285140474", "497.96787148594296", "497.80321285140474", "497.26907630521981", "497.33333333333229", "497.32128514056126", "497.41767068272992", "498.11847389558159", "498.11847389558159", "497.96787148594296", "497.26907630521981", "497.10441767068158", "497.32329317268972", "497.32128514056126", "498.11847389558159", "497.63654618473805", "497.41767068272992", "497.47188755019982", "497.63654618473805", "497.96787148594296", "498.11847389558159", "497.32329317268972", "496.62248995983811", "497.65461847389469", "497.56827309236849", "497.41767068272992", "497.41767068272992", "498.11847389558159", "497.56827309236849", "497.63654618473805", "497.80321285140474", "497.26907630521981", "497.63654618473805", "497.96787148594296", "497.08634538152501", "497.73293172690671", "497.88353413654534", "496.1526104417656", "497.80321285140474", "497.41767068272992", "497.10441767068158", "497.26907630521981", "496.78313253011925", "497.17269076305115", "497.08634538152501", "497.96787148594296", "497.96787148594296", "497.96787148594296", "498.11847389558159", "497.47188755019982", "497.41767068272992", "497.32128514056126", "497.56827309236849", "497.63654618473805", "498.11847389558159", "497.80321285140474", "496.68674698795053", "497.32329317268972", "497.63654618473805", "498.11847389558159", "497.32329317268972", "497.63654618473805", "497.00200803212738", "497.80321285140474", "497.10441767068158", "497.32128514056126", "497.47188755019982", "497.96787148594296", "496.93775100401484", "497.26907630521981", "497.00200803212738", "497.96787148594296", "497.41767068272992", "498.11847389558159", "497.63654618473805", "496.53815261044042", "497.80321285140474", "498.11847389558159", "497.96787148594296", "498.11847389558159", "497.00200803212738", "497.56827309236849", "497.41967871485838", "497.09839357429604", "497.32329317268972", "497.17269076305115", "497.63654618473805", "497.65461847389469", "498.11847389558159", "497.88353413654534", "497.56827309236849", "497.65461847389469", "497.96787148594296", "497.32329317268972", "497.63654618473805" ], "rows": 500, "sample_cells": { "0,0": "1", "0,499": "0.99799196787148592", "250,250": "1", "499,0": "0.99799196787148592", "499,499": "1" }, "trace": "500" }, "walks": { "col_sums": [ "911", "525", "61", "0", "0", "135", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" ], "cols": 500, "dump_mode": "summary", "length": 2, "row_sums": [ "0", "0", "1", "1", "2", "1", "4", "5", "3", "3", "3", "3", "3", "3", "4", "3", "5", "5", "6", "7", "3", "4", "3", "3", "6", "3", "0", "1", "3", "5", "3", "5", "5", "4", "3", "3", "4", "1", "1", "1", "4", "4", "5", "1", "2", "4", "7", "3", "5", "4", "4", "3", "3", "6", "6", "5", "5", "5", "4", "6", "4", "3", "3", "3", "5", "2", "5", "4", "5", "6", "5", "3", "4", "5", "3", "3", "6", "3", "5", "3", "2", "5", "2", "3", "4", "4", "5", "3", "5", "3", "6", "5", "4", "4", "1", "3", "4", "0", "1", "3", "3", "1", "4", "3", "2", "3", "3", "3", "5", "3", "1", "1", "1", "1", "6", "6", "3", "1", "3", "3", "6", "1", "3", "3", "1", "3", "1", "5", "5", "5", "6", "2", "3", "1", "4", "1", "0", "2", "4", "1", "4", "1", "4", "3", "3", "1", "4", "2", "3", "2", "5", "3", "2", "0", "3", "6", "3", "5", "3", "4", "3", "1", "3", "6", "1", "5", "3", "2", "3", "5", "1", "1", "1", "4", "5", "3", "3", "6", "2", "1", "5", "1", "1", "0", "5", "5", "2", "4", "3", "3", "3", "0", "6", "1", "4", "3", "2", "3", "3", "3", "4", "3", "3", "5", "4", "3", "4", "4", "1", "2", "1", "2", "3", "5", "6", "4", "1", "3", "3", "5", "4", "4", "4", "1", "7", "1", "3", "3", "3", "3", "3", "0", "3", "1", "5", "4", "3", "3", "5", "3", "6", "5", "4", "0", "5", "5", "6", "3", "5", "6", "2", "3", "5", "3", "4", "7", "3", "3", "3", "4", "5", "5", "3", "2", "3", "6", "3", "5", "0", "2", "4", "5", "2", "4", "5", "5", "3", "3", "4", "1", "3", "2", "3", "2", "3", "4", "2", "3", "4", "5", "3", "5", "5", "3", "4", "1", "3", "3", "2", "1", "2", "3", "3", "3", "1", "5", "1", "4", "4", "2", "1", "3", "1", "4", "5", "3", "4", "6", "3", "5", "5", "1", "1", "2", "0", "2", "4", "5", "1", "1", "1", "1", "6", "6", "3", "3", "3", "7", "3", "4", "4", "1", "0", "3", "3", "3", "3", "6", "4", "2", "6", "6", "1", "1", "3", "5", "3", "6", "4", "2", "1", "3", "5", "6", "3", "3", "4", "2", "2", "5", "3", "3", "1", "5", "3", "5", "1", "3", "5", "1", "3", "1", "1", "3", "7", "5", "3", "4", "3", "2", "3", "4", "5", "6", "3", "3", "4", "3", "5", "3", "3", "3", "4", "0", "3", "3", "3", "3", "6", "3", "4", "5", "1", "1", "3", "6", "6", "3", "4", "1", "2", "5", "2", "2", "3", "1", "3", "7", "4", "3", "5", "5", "1", "3", "2", "3", "6", "2", "3", "4", "3", "1", "7", "3", "5", "6", "6", "5", "5", "4", "3", "3", "3", "1", "2", "5", "4", "3", "2", "1", "3", "4", "3", "2", "1", "3", "2", "2", "3", "6", "4", "2", "3", "5", "6", "2", "3", "5", "1", "2", "5", "3", "1", "3", "1", "2", "3", "4", "3", "3", "5", "2", "4", "1", "1", "3", "4", "3", "3", "2" ], "rows": 500, "sample_cells": { "0,0": "0", "0,499": "0", "250,250": "0", "499,0": "1", "499,499": "0" }, "trace": "0" } }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 8 } TinyDisconnected_Undir_N6_E4__MATRIX__V8__FT2__W0_IW1_DI0.json000066400000000000000000000252021524402210300326060ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/matrix{ "counts": { "links_sna": 6, "nodes": 6, "ties_graph": 3 }, "dataset": { "filetype": 2, "name": "TinyDisconnected_Undir_N6_E4.paj", "path": "src/data/TinyDisconnected_Undir_N6_E4.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "matrix", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyDisconnected_Undir_N6_E4.paj", "ok": true }, "matrices": { "adjacency": { "cols": 6, "data": [ [ "0", "1", "0", "0", "0", "0" ], [ "1", "0", "1", "0", "0", "0" ], [ "0", "1", "0", "0", "0", "0" ], [ "0", "0", "0", "0", "1", "0" ], [ "0", "0", "0", "1", "0", "0" ], [ "0", "0", "0", "0", "0", "0" ] ], "dump_mode": "full", "rows": 6 }, "adjacency_inverse": { "cols": 5, "data": [ [ "0", "0", "0", "0", "0" ], [ "0", "0", "0", "0", "0" ], [ "0", "0", "0", "0", "0" ], [ "0", "0", "0", "0", "0" ], [ "0", "0", "0", "0", "0" ] ], "dump_mode": "full", "invertible": false, "rows": 5 }, "clique_comembership": { "cols": 6, "data": [ [ "1", "1", "0", "0", "0", "0" ], [ "1", "2", "1", "0", "0", "0" ], [ "0", "1", "1", "0", "0", "0" ], [ "0", "0", "0", "1", "1", "0" ], [ "0", "0", "0", "1", "1", "0" ], [ "0", "0", "0", "0", "0", "1" ] ], "dump_mode": "full", "rows": 6 }, "distances": { "cols": 6, "data": [ [ "0", "1", "2", "2147483647", "2147483647", "2147483647" ], [ "1", "0", "1", "2147483647", "2147483647", "2147483647" ], [ "2", "1", "0", "2147483647", "2147483647", "2147483647" ], [ "2147483647", "2147483647", "2147483647", "0", "1", "2147483647" ], [ "2147483647", "2147483647", "2147483647", "1", "0", "2147483647" ], [ "2147483647", "2147483647", "2147483647", "2147483647", "2147483647", "0" ] ], "dump_mode": "full", "rows": 6 }, "reachability": { "cols": 6, "data": [ [ "1", "1", "1", "0", "0", "0" ], [ "1", "1", "1", "0", "0", "0" ], [ "1", "1", "1", "0", "0", "0" ], [ "0", "0", "0", "1", "1", "0" ], [ "0", "0", "0", "1", "1", "0" ], [ "0", "0", "0", "0", "0", "1" ] ], "dump_mode": "full", "rows": 6 }, "similarity": { "cols": 6, "data": [ [ "1", "0.75", "1", "0.5", "0.5", "0.75" ], [ "0.75", "1", "0.75", "0.25", "0.25", "0.5" ], [ "1", "0.75", "1", "0.5", "0.5", "0.75" ], [ "0.5", "0.25", "0.5", "1", "1", "0.75" ], [ "0.5", "0.25", "0.5", "1", "1", "0.75" ], [ "0.75", "0.5", "0.75", "0.75", "0.75", "1" ] ], "dump_mode": "full", "metric": "simple_matching", "rows": 6 }, "total_walks": { "cols": 6, "data": [ [ "3", "7", "3", "0", "0", "0" ], [ "7", "6", "7", "0", "0", "0" ], [ "3", "7", "3", "0", "0", "0" ], [ "0", "0", "0", "2", "3", "0" ], [ "0", "0", "0", "3", "2", "0" ], [ "0", "0", "0", "0", "0", "0" ] ], "dump_mode": "full", "rows": 6 }, "walks": { "cols": 6, "data": [ [ "1", "0", "1", "0", "0", "0" ], [ "0", "2", "0", "0", "0", "0" ], [ "1", "0", "1", "0", "0", "0" ], [ "0", "0", "0", "1", "0", "0" ], [ "0", "0", "0", "0", "1", "0" ], [ "0", "0", "0", "0", "0", "0" ] ], "dump_mode": "full", "length": 2, "rows": 6 } }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 8 } SocNetV-3.7/src/tools/baselines/matrix/TinyPath_N3_E2__MATRIX__V8__FT2__W0_IW1_DI0.json000066400000000000000000000110151524402210300300060ustar00rootroot00000000000000{ "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "src/data/TinyPath_N3_E2.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "matrix", "load_report": { "fileType_signal": 2, "load_ms": 4, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "matrices": { "adjacency": { "cols": 3, "data": [ [ "0", "1", "0" ], [ "1", "0", "1" ], [ "0", "1", "0" ] ], "dump_mode": "full", "rows": 3 }, "adjacency_inverse": { "cols": 3, "data": [ [ "0", "0", "0" ], [ "0", "0", "0" ], [ "0", "0", "0" ] ], "dump_mode": "full", "invertible": false, "rows": 3 }, "clique_comembership": { "cols": 3, "data": [ [ "1", "1", "0" ], [ "1", "2", "1" ], [ "0", "1", "1" ] ], "dump_mode": "full", "rows": 3 }, "distances": { "cols": 3, "data": [ [ "0", "1", "2" ], [ "1", "0", "1" ], [ "2", "1", "0" ] ], "dump_mode": "full", "rows": 3 }, "reachability": { "cols": 3, "data": [ [ "1", "1", "1" ], [ "1", "1", "1" ], [ "1", "1", "1" ] ], "dump_mode": "full", "rows": 3 }, "similarity": { "cols": 3, "data": [ [ "1", "0", "1" ], [ "0", "1", "0" ], [ "1", "0", "1" ] ], "dump_mode": "full", "metric": "simple_matching", "rows": 3 }, "total_walks": { "cols": 3, "data": [ [ "1", "1", "1" ], [ "1", "2", "1" ], [ "1", "1", "1" ] ], "dump_mode": "full", "rows": 3 }, "walks": { "cols": 3, "data": [ [ "1", "0", "1" ], [ "0", "2", "0" ], [ "1", "0", "1" ] ], "dump_mode": "full", "length": 2, "rows": 3 } }, "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 8 } SocNetV-3.7/src/tools/baselines/prominence/000077500000000000000000000000001524402210300207305ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence/Krackhardt_Kite_N10__PROM__V4__FT2__W0_IW1_DI0.json000066400000000000000000000242001524402210300314300ustar00rootroot00000000000000{ "counts": { "links_sna": 36, "nodes": 10, "ties_graph": 18 }, "dataset": { "filetype": 2, "name": "Krackhardt_Kite_N10.paj", "path": "src/data/Krackhardt_Kite_N10.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "Krackhardt_Kite_N10.paj", "ok": true }, "per_node": [ { "BC": "0.83333333333333326", "CC": "0.058823529411764705", "DC": "4", "DP": "4", "EVC": "0.35220939285728092", "IC": "1.1821086261980831", "IRCC": "0.52941176470588236", "PC": "6.0833333333333339", "PP": "0.52941176470588236", "PRP": "0.10193628067552184", "SBC": "0.023148148148148147", "SC": "2", "SCC": "0.52941176470588236", "SDC": "0.44444444444444442", "SDP": "0.44444444444444442", "SEVC": "0.7322123169490995", "SIC": "0.10938702044459107", "SIRCC": "0.52941176470588236", "SPC": "0.67592592592592593", "SPP": "0.52941176470588236", "SPRP": "0.69265056101565692", "SSC": "0.054054054054054057", "eccentricity": "4", "eccentricity_inf": false, "id": 1, "label": "Andre" }, { "BC": "0.83333333333333326", "CC": "0.058823529411764705", "DC": "4", "DP": "4", "EVC": "0.35220939285728092", "IC": "1.1821086261980831", "IRCC": "0.52941176470588236", "PC": "6.0833333333333339", "PP": "0.52941176470588236", "PRP": "0.10193441184414075", "SBC": "0.023148148148148147", "SC": "2", "SCC": "0.52941176470588236", "SDC": "0.44444444444444442", "SDP": "0.44444444444444442", "SEVC": "0.7322123169490995", "SIC": "0.10938702044459107", "SIRCC": "0.52941176470588236", "SPC": "0.67592592592592593", "SPP": "0.52941176470588236", "SPRP": "0.69263786242496894", "SSC": "0.054054054054054057", "eccentricity": "4", "eccentricity_inf": false, "id": 2, "label": "Beverley" }, { "BC": "0", "CC": "0.055555555555555552", "DC": "3", "DP": "3", "EVC": "0.28583498858278172", "IC": "1.0787172011661805", "IRCC": "0.5", "PC": "5.5833333333333339", "PP": "0.5", "PRP": "0.079429331031323006", "SBC": "0", "SC": "0", "SCC": "0.5", "SDC": "0.33333333333333331", "SDP": "0.33333333333333331", "SEVC": "0.59422577449581349", "SIC": "0.099819642563139938", "SIRCC": "0.5", "SPC": "0.62037037037037035", "SPP": "0.5", "SPRP": "0.53971726587779545", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 3, "label": "Carol" }, { "BC": "3.6666666666666661", "CC": "0.066666666666666666", "DC": "6", "DP": "6", "EVC": "0.48102085242819692", "IC": "1.3226094727435209", "IRCC": "0.59999999999999998", "PC": "7.083333333333333", "PP": "0.59999999999999998", "PRP": "0.14716840844833681", "SBC": "0.10185185185185183", "SC": "7", "SCC": "0.59999999999999998", "SDC": "0.66666666666666663", "SDP": "0.66666666666666663", "SEVC": "1", "SIC": "0.12238833744113316", "SIRCC": "0.59999999999999998", "SPC": "0.78703703703703698", "SPP": "0.59999999999999998", "SPRP": "1", "SSC": "0.1891891891891892", "eccentricity": "4", "eccentricity_inf": false, "id": 4, "label": "Diane" }, { "BC": "0", "CC": "0.055555555555555552", "DC": "3", "DP": "3", "EVC": "0.28583498858278172", "IC": "1.0787172011661808", "IRCC": "0.5", "PC": "5.5833333333333339", "PP": "0.5", "PRP": "0.079427463831839462", "SBC": "0", "SC": "0", "SCC": "0.5", "SDC": "0.33333333333333331", "SDP": "0.33333333333333331", "SEVC": "0.59422577449581349", "SIC": "0.099819642563139951", "SIRCC": "0.5", "SPC": "0.62037037037037035", "SPP": "0.5", "SPRP": "0.53970457837574781", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 5, "label": "Ed" }, { "BC": "8.3333333333333339", "CC": "0.071428571428571425", "DC": "5", "DP": "5", "EVC": "0.39769064296088436", "IC": "1.3321332133213319", "IRCC": "0.64285714285714279", "PC": "6.833333333333333", "PP": "0.64285714285714279", "PRP": "0.12892286613180182", "SBC": "0.23148148148148151", "SC": "8", "SCC": "0.64285714285714279", "SDC": "0.55555555555555558", "SDP": "0.55555555555555558", "SEVC": "0.82676383145000676", "SIC": "0.12326962159912511", "SIRCC": "0.64285714285714279", "SPC": "0.75925925925925919", "SPP": "0.64285714285714279", "SPRP": "0.87602269733765548", "SSC": "0.21621621621621623", "eccentricity": "3", "eccentricity_inf": false, "id": 6, "label": "Fernando" }, { "BC": "8.3333333333333339", "CC": "0.071428571428571425", "DC": "5", "DP": "5", "EVC": "0.39769064296088436", "IC": "1.3321332133213319", "IRCC": "0.64285714285714279", "PC": "6.833333333333333", "PP": "0.64285714285714279", "PRP": "0.12892128375624079", "SBC": "0.23148148148148151", "SC": "8", "SCC": "0.64285714285714279", "SDC": "0.55555555555555558", "SDP": "0.55555555555555558", "SEVC": "0.82676383145000676", "SIC": "0.12326962159912511", "SIRCC": "0.64285714285714279", "SPC": "0.75925925925925919", "SPP": "0.64285714285714279", "SPRP": "0.87601194519609393", "SSC": "0.21621621621621623", "eccentricity": "3", "eccentricity_inf": false, "id": 7, "label": "Garth" }, { "BC": "14", "CC": "0.066666666666666666", "DC": "3", "DP": "3", "EVC": "0.19586058913287382", "IC": "1.1681136543014994", "IRCC": "0.59999999999999998", "PC": "6", "PP": "0.59999999999999998", "PRP": "0.095255994094674334", "SBC": "0.3888888888888889", "SC": "5.5", "SCC": "0.59999999999999998", "SDC": "0.33333333333333331", "SDP": "0.33333333333333331", "SEVC": "0.40717691996961897", "SIC": "0.10809198863190844", "SIRCC": "0.59999999999999998", "SPC": "0.66666666666666663", "SPP": "0.59999999999999998", "SPRP": "0.64725843745272116", "SSC": "0.14864864864864866", "eccentricity": "2", "eccentricity_inf": false, "id": 8, "label": "Heather" }, { "BC": "8", "CC": "0.047619047619047616", "DC": "2", "DP": "2", "EVC": "0.048073501491671694", "IC": "0.6867749419953596", "IRCC": "0.42857142857142855", "PC": "4.6666666666666661", "PP": "0.42857142857142855", "PRP": "0.085698313648475438", "SBC": "0.22222222222222221", "SC": "4.5", "SCC": "0.42857142857142855", "SDC": "0.22222222222222221", "SDP": "0.22222222222222221", "SEVC": "0.099940576898062308", "SIC": "0.063551067098203254", "SIRCC": "0.42857142857142855", "SPC": "0.51851851851851838", "SPP": "0.42857142857142855", "SPRP": "0.58231460509787103", "SSC": "0.12162162162162163", "eccentricity": "3", "eccentricity_inf": false, "id": 9, "label": "Ike" }, { "BC": "0", "CC": "0.034482758620689655", "DC": "1", "DP": "1", "EVC": "0.011163260312035342", "IC": "0.44324648098232999", "IRCC": "0.31034482758620691", "PC": "3.4166666666666665", "PP": "0.31034482758620691", "PRP": "0.05142178330060207", "SBC": "0", "SC": "0", "SCC": "0.31034482758620691", "SDC": "0.1111111111111111", "SDP": "0.1111111111111111", "SEVC": "0.023207435302821319", "SIC": "0.041016037615042827", "SIRCC": "0.31034482758620691", "SPC": "0.37962962962962959", "SPP": "0.31034482758620691", "SPRP": "0.34940775566417565", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 10, "label": "Jane" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 4 } Krackhardt_Kite_N10__PROM__V4__FT2__W0_IW1_DI0_BA1_BB0.1.json000066400000000000000000000261751524402210300325730ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 36, "nodes": 10, "ties_graph": 18 }, "dataset": { "filetype": 2, "name": "Krackhardt_Kite_N10.paj", "path": "src/data/Krackhardt_Kite_N10.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "Krackhardt_Kite_N10.paj", "ok": true }, "metrics": { "density": "0.40000000000000002" }, "per_node": [ { "BC": "0.83333333333333326", "BPC": "7.1759360924738811", "CC": "0.058823529411764705", "DC": "4", "DP": "4", "EVC": "0.35220939285728092", "IC": "1.1821086261980831", "IRCC": "0.52941176470588236", "PC": "6.083333333333333", "PP": "0.52941176470588236", "PRP": "0.10193628067552184", "SBC": "0.023148148148148147", "SBPC": "0.69715126436155483", "SC": "2", "SCC": "0.52941176470588236", "SDC": "0.44444444444444442", "SDP": "0.44444444444444442", "SEVC": "0.7322123169490995", "SIC": "0.10938702044459107", "SIRCC": "0.52941176470588236", "SPC": "0.67592592592592582", "SPP": "0.52941176470588236", "SPRP": "0.69265056101565703", "SSC": "0.054054054054054057", "eccentricity": "4", "eccentricity_inf": false, "id": 1, "label": "Andre" }, { "BC": "0.83333333333333326", "BPC": "7.175936092473882", "CC": "0.058823529411764705", "DC": "4", "DP": "4", "EVC": "0.35220939285728092", "IC": "1.1821086261980831", "IRCC": "0.52941176470588236", "PC": "6.083333333333333", "PP": "0.52941176470588236", "PRP": "0.10193441184414076", "SBC": "0.023148148148148147", "SBPC": "0.69715126436155495", "SC": "2", "SCC": "0.52941176470588236", "SDC": "0.44444444444444442", "SDP": "0.44444444444444442", "SEVC": "0.7322123169490995", "SIC": "0.10938702044459107", "SIRCC": "0.52941176470588236", "SPC": "0.67592592592592582", "SPP": "0.52941176470588236", "SPRP": "0.69263786242496916", "SSC": "0.054054054054054057", "eccentricity": "4", "eccentricity_inf": false, "id": 2, "label": "Beverley" }, { "BC": "0", "BPC": "5.6144873567944398", "CC": "0.055555555555555552", "DC": "3", "DP": "3", "EVC": "0.28583498858278172", "IC": "1.0787172011661808", "IRCC": "0.5", "PC": "5.583333333333333", "PP": "0.5", "PRP": "0.079429331031323006", "SBC": "0", "SBPC": "0.54545454545454541", "SC": "0", "SCC": "0.5", "SDC": "0.33333333333333331", "SDP": "0.33333333333333331", "SEVC": "0.59422577449581349", "SIC": "0.099819642563139951", "SIRCC": "0.5", "SPC": "0.62037037037037035", "SPP": "0.5", "SPRP": "0.53971726587779556", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 3, "label": "Carol" }, { "BC": "3.6666666666666661", "BPC": "10.293226820789807", "CC": "0.066666666666666666", "DC": "6", "DP": "6", "EVC": "0.48102085242819692", "IC": "1.3226094727435209", "IRCC": "0.59999999999999998", "PC": "7.083333333333333", "PP": "0.59999999999999998", "PRP": "0.14716840844833678", "SBC": "0.10185185185185183", "SBPC": "1", "SC": "7", "SCC": "0.59999999999999998", "SDC": "0.66666666666666663", "SDP": "0.66666666666666663", "SEVC": "1", "SIC": "0.12238833744113316", "SIRCC": "0.59999999999999998", "SPC": "0.78703703703703698", "SPP": "0.59999999999999998", "SPRP": "1", "SSC": "0.1891891891891892", "eccentricity": "4", "eccentricity_inf": false, "id": 4, "label": "Diane" }, { "BC": "0", "BPC": "5.6144873567944389", "CC": "0.055555555555555552", "DC": "3", "DP": "3", "EVC": "0.28583498858278172", "IC": "1.0787172011661808", "IRCC": "0.5", "PC": "5.583333333333333", "PP": "0.5", "PRP": "0.079427463831839476", "SBC": "0", "SBPC": "0.5454545454545453", "SC": "0", "SCC": "0.5", "SDC": "0.33333333333333331", "SDP": "0.33333333333333331", "SEVC": "0.59422577449581349", "SIC": "0.099819642563139951", "SIRCC": "0.5", "SPC": "0.62037037037037035", "SPP": "0.5", "SPRP": "0.53970457837574803", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 5, "label": "Ed" }, { "BC": "8.3333333333333339", "BPC": "8.6757106546806995", "CC": "0.071428571428571425", "DC": "5", "DP": "5", "EVC": "0.39769064296088436", "IC": "1.3321332133213319", "IRCC": "0.64285714285714279", "PC": "6.833333333333333", "PP": "0.64285714285714279", "PRP": "0.12892286613180182", "SBC": "0.23148148148148151", "SBPC": "0.84285625933724506", "SC": "8", "SCC": "0.64285714285714279", "SDC": "0.55555555555555558", "SDP": "0.55555555555555558", "SEVC": "0.82676383145000676", "SIC": "0.12326962159912511", "SIRCC": "0.64285714285714279", "SPC": "0.75925925925925919", "SPP": "0.64285714285714279", "SPRP": "0.87602269733765559", "SSC": "0.21621621621621623", "eccentricity": "3", "eccentricity_inf": false, "id": 6, "label": "Fernando" }, { "BC": "8.3333333333333339", "BPC": "8.6757106546806977", "CC": "0.071428571428571425", "DC": "5", "DP": "5", "EVC": "0.39769064296088436", "IC": "1.3321332133213319", "IRCC": "0.64285714285714279", "PC": "6.833333333333333", "PP": "0.64285714285714279", "PRP": "0.12892128375624079", "SBC": "0.23148148148148151", "SBPC": "0.84285625933724484", "SC": "8", "SCC": "0.64285714285714279", "SDC": "0.55555555555555558", "SDP": "0.55555555555555558", "SEVC": "0.82676383145000676", "SIC": "0.12326962159912511", "SIRCC": "0.64285714285714279", "SPC": "0.75925925925925919", "SPP": "0.64285714285714279", "SPRP": "0.87601194519609404", "SSC": "0.21621621621621623", "eccentricity": "3", "eccentricity_inf": false, "id": 7, "label": "Garth" }, { "BC": "14", "BPC": "4.9977456220681411", "CC": "0.066666666666666666", "DC": "3", "DP": "3", "EVC": "0.19586058913287382", "IC": "1.1681136543014994", "IRCC": "0.59999999999999998", "PC": "6", "PP": "0.59999999999999998", "PRP": "0.095255994094674348", "SBC": "0.3888888888888889", "SBPC": "0.48553730614134671", "SC": "5.5", "SCC": "0.59999999999999998", "SDC": "0.33333333333333331", "SDP": "0.33333333333333331", "SEVC": "0.40717691996961897", "SIC": "0.10809198863190844", "SIRCC": "0.59999999999999998", "SPC": "0.66666666666666663", "SPP": "0.59999999999999998", "SPRP": "0.64725843745272138", "SSC": "0.14864864864864866", "eccentricity": "2", "eccentricity_inf": false, "id": 8, "label": "Heather" }, { "BC": "8", "BPC": "2.6260349113200148", "CC": "0.047619047619047616", "DC": "2", "DP": "2", "EVC": "0.048073501491671694", "IC": "0.6867749419953596", "IRCC": "0.42857142857142855", "PC": "4.666666666666667", "PP": "0.42857142857142855", "PRP": "0.085698313648475438", "SBC": "0.22222222222222221", "SBPC": "0.25512261189232371", "SC": "4.5", "SCC": "0.42857142857142855", "SDC": "0.22222222222222221", "SDP": "0.22222222222222221", "SEVC": "0.099940576898062308", "SIC": "0.063551067098203254", "SIRCC": "0.42857142857142855", "SPC": "0.51851851851851849", "SPP": "0.42857142857142855", "SPRP": "0.58231460509787114", "SSC": "0.12162162162162163", "eccentricity": "3", "eccentricity_inf": false, "id": 9, "label": "Ike" }, { "BC": "0", "BPC": "1.2626034911320017", "CC": "0.034482758620689655", "DC": "1", "DP": "1", "EVC": "0.011163260312035342", "IC": "0.44324648098232999", "IRCC": "0.31034482758620691", "PC": "3.4166666666666665", "PP": "0.31034482758620691", "PRP": "0.051421783300602063", "SBC": "0", "SBPC": "0.12266352555078751", "SC": "0", "SCC": "0.31034482758620691", "SDC": "0.1111111111111111", "SDP": "0.1111111111111111", "SEVC": "0.023207435302821319", "SIC": "0.041016037615042827", "SIRCC": "0.31034482758620691", "SPC": "0.37962962962962959", "SPP": "0.31034482758620691", "SPRP": "0.34940775566417565", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 10, "label": "Jane" } ], "run": { "bonacichAlpha": "1", "bonacichBeta": "0.10000000000000001", "bonacichEnabled": true, "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "katzEnabled": false }, "schema_version": 4 } SocNetV-3.7/src/tools/baselines/prominence/Krackhardt_Kite_N10__PROM__V4__FT2__W0_IW1_DI0_KA0.1.json000066400000000000000000000260651524402210300322350ustar00rootroot00000000000000{ "counts": { "links_sna": 36, "nodes": 10, "ties_graph": 18 }, "dataset": { "filetype": 2, "name": "Krackhardt_Kite_N10.paj", "path": "src/data/Krackhardt_Kite_N10.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "Krackhardt_Kite_N10.paj", "ok": true }, "metrics": { "density": "0.40000000000000002" }, "per_node": [ { "BC": "0.83333333333333326", "CC": "0.058823529411764705", "DC": "4", "DP": "4", "EVC": "0.35220939285728092", "IC": "1.1821086261980831", "IRCC": "0.52941176470588236", "KC": "0.71759360924738824", "PC": "6.0833333333333339", "PP": "0.52941176470588236", "PRP": "0.10193628067552184", "SBC": "0.023148148148148147", "SC": "2", "SCC": "0.52941176470588236", "SDC": "0.44444444444444442", "SDP": "0.44444444444444442", "SEVC": "0.7322123169490995", "SIC": "0.10938702044459107", "SIRCC": "0.52941176470588236", "SKC": "0.69715126436155506", "SPC": "0.67592592592592593", "SPP": "0.52941176470588236", "SPRP": "0.69265056101565703", "SSC": "0.054054054054054057", "eccentricity": "4", "eccentricity_inf": false, "id": 1, "label": "Andre" }, { "BC": "0.83333333333333326", "CC": "0.058823529411764705", "DC": "4", "DP": "4", "EVC": "0.35220939285728092", "IC": "1.1821086261980831", "IRCC": "0.52941176470588236", "KC": "0.71759360924738846", "PC": "6.0833333333333339", "PP": "0.52941176470588236", "PRP": "0.10193441184414075", "SBC": "0.023148148148148147", "SC": "2", "SCC": "0.52941176470588236", "SDC": "0.44444444444444442", "SDP": "0.44444444444444442", "SEVC": "0.7322123169490995", "SIC": "0.10938702044459107", "SIRCC": "0.52941176470588236", "SKC": "0.69715126436155528", "SPC": "0.67592592592592593", "SPP": "0.52941176470588236", "SPRP": "0.69263786242496905", "SSC": "0.054054054054054057", "eccentricity": "4", "eccentricity_inf": false, "id": 2, "label": "Beverley" }, { "BC": "0", "CC": "0.055555555555555552", "DC": "3", "DP": "3", "EVC": "0.28583498858278172", "IC": "1.0787172011661808", "IRCC": "0.5", "KC": "0.5614487356794442", "PC": "5.5833333333333339", "PP": "0.5", "PRP": "0.079429331031323006", "SBC": "0", "SC": "0", "SCC": "0.5", "SDC": "0.33333333333333331", "SDP": "0.33333333333333331", "SEVC": "0.59422577449581349", "SIC": "0.099819642563139951", "SIRCC": "0.5", "SKC": "0.54545454545454575", "SPC": "0.62037037037037035", "SPP": "0.5", "SPRP": "0.53971726587779556", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 3, "label": "Carol" }, { "BC": "3.6666666666666665", "CC": "0.066666666666666666", "DC": "6", "DP": "6", "EVC": "0.48102085242819692", "IC": "1.3226094727435209", "IRCC": "0.59999999999999998", "KC": "1.0293226820789805", "PC": "7.083333333333333", "PP": "0.59999999999999998", "PRP": "0.14716840844833678", "SBC": "0.10185185185185185", "SC": "7", "SCC": "0.59999999999999998", "SDC": "0.66666666666666663", "SDP": "0.66666666666666663", "SEVC": "1", "SIC": "0.12238833744113316", "SIRCC": "0.59999999999999998", "SKC": "1", "SPC": "0.78703703703703698", "SPP": "0.59999999999999998", "SPRP": "1", "SSC": "0.1891891891891892", "eccentricity": "4", "eccentricity_inf": false, "id": 4, "label": "Diane" }, { "BC": "0", "CC": "0.055555555555555552", "DC": "3", "DP": "3", "EVC": "0.28583498858278172", "IC": "1.0787172011661808", "IRCC": "0.5", "KC": "0.56144873567944398", "PC": "5.5833333333333339", "PP": "0.5", "PRP": "0.079427463831839476", "SBC": "0", "SC": "0", "SCC": "0.5", "SDC": "0.33333333333333331", "SDP": "0.33333333333333331", "SEVC": "0.59422577449581349", "SIC": "0.099819642563139951", "SIRCC": "0.5", "SKC": "0.54545454545454553", "SPC": "0.62037037037037035", "SPP": "0.5", "SPRP": "0.53970457837574803", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 5, "label": "Ed" }, { "BC": "8.3333333333333339", "CC": "0.071428571428571425", "DC": "5", "DP": "5", "EVC": "0.39769064296088436", "IC": "1.3321332133213319", "IRCC": "0.64285714285714279", "KC": "0.86757106546807017", "PC": "6.833333333333333", "PP": "0.64285714285714279", "PRP": "0.12892286613180182", "SBC": "0.23148148148148151", "SC": "8", "SCC": "0.64285714285714279", "SDC": "0.55555555555555558", "SDP": "0.55555555555555558", "SEVC": "0.82676383145000676", "SIC": "0.12326962159912511", "SIRCC": "0.64285714285714279", "SKC": "0.84285625933724539", "SPC": "0.75925925925925919", "SPP": "0.64285714285714279", "SPRP": "0.87602269733765559", "SSC": "0.21621621621621623", "eccentricity": "3", "eccentricity_inf": false, "id": 6, "label": "Fernando" }, { "BC": "8.3333333333333339", "CC": "0.071428571428571425", "DC": "5", "DP": "5", "EVC": "0.39769064296088436", "IC": "1.3321332133213319", "IRCC": "0.64285714285714279", "KC": "0.86757106546806995", "PC": "6.833333333333333", "PP": "0.64285714285714279", "PRP": "0.12892128375624079", "SBC": "0.23148148148148151", "SC": "8", "SCC": "0.64285714285714279", "SDC": "0.55555555555555558", "SDP": "0.55555555555555558", "SEVC": "0.82676383145000676", "SIC": "0.12326962159912511", "SIRCC": "0.64285714285714279", "SKC": "0.84285625933724517", "SPC": "0.75925925925925919", "SPP": "0.64285714285714279", "SPRP": "0.87601194519609404", "SSC": "0.21621621621621623", "eccentricity": "3", "eccentricity_inf": false, "id": 7, "label": "Garth" }, { "BC": "14", "CC": "0.066666666666666666", "DC": "3", "DP": "3", "EVC": "0.19586058913287382", "IC": "1.1681136543014994", "IRCC": "0.59999999999999998", "KC": "0.49977456220681415", "PC": "6", "PP": "0.59999999999999998", "PRP": "0.095255994094674348", "SBC": "0.3888888888888889", "SC": "5.5", "SCC": "0.59999999999999998", "SDC": "0.33333333333333331", "SDP": "0.33333333333333331", "SEVC": "0.40717691996961897", "SIC": "0.10809198863190844", "SIRCC": "0.59999999999999998", "SKC": "0.48553730614134682", "SPC": "0.66666666666666663", "SPP": "0.59999999999999998", "SPRP": "0.64725843745272138", "SSC": "0.14864864864864866", "eccentricity": "2", "eccentricity_inf": false, "id": 8, "label": "Heather" }, { "BC": "8", "CC": "0.047619047619047616", "DC": "2", "DP": "2", "EVC": "0.048073501491671694", "IC": "0.6867749419953596", "IRCC": "0.42857142857142855", "KC": "0.26260349113200143", "PC": "4.6666666666666661", "PP": "0.42857142857142855", "PRP": "0.085698313648475438", "SBC": "0.22222222222222221", "SC": "4.5", "SCC": "0.42857142857142855", "SDC": "0.22222222222222221", "SDP": "0.22222222222222221", "SEVC": "0.099940576898062308", "SIC": "0.063551067098203254", "SIRCC": "0.42857142857142855", "SKC": "0.25512261189232371", "SPC": "0.51851851851851838", "SPP": "0.42857142857142855", "SPRP": "0.58231460509787114", "SSC": "0.12162162162162163", "eccentricity": "3", "eccentricity_inf": false, "id": 9, "label": "Ike" }, { "BC": "0", "CC": "0.034482758620689655", "DC": "1", "DP": "1", "EVC": "0.011163260312035342", "IC": "0.44324648098232999", "IRCC": "0.31034482758620691", "KC": "0.12626034911320017", "PC": "3.4166666666666665", "PP": "0.31034482758620691", "PRP": "0.051421783300602063", "SBC": "0", "SC": "0", "SCC": "0.31034482758620691", "SDC": "0.1111111111111111", "SDP": "0.1111111111111111", "SEVC": "0.023207435302821319", "SIC": "0.041016037615042827", "SIRCC": "0.31034482758620691", "SKC": "0.12266352555078752", "SPC": "0.37962962962962959", "SPP": "0.31034482758620691", "SPRP": "0.34940775566417565", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 10, "label": "Jane" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "katzAlpha": "0.10000000000000001", "katzEnabled": true }, "schema_version": 4 } SocNetV-3.7/src/tools/baselines/prominence/Krackhardt_Kite_N10__PROM__V4__FT2__W1_IW1_DI0.json000066400000000000000000000240631524402210300314400ustar00rootroot00000000000000{ "counts": { "links_sna": 36, "nodes": 10, "ties_graph": 18 }, "dataset": { "filetype": 2, "name": "Krackhardt_Kite_N10.paj", "path": "src/data/Krackhardt_Kite_N10.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "Krackhardt_Kite_N10.paj", "ok": true }, "per_node": [ { "BC": "0.83333333333333326", "CC": "0.058823529411764705", "DC": "4", "DP": "4", "EVC": "0.35220939285728092", "IC": "1.1821086261980831", "IRCC": "0.52941176470588236", "PC": "6.083333333333333", "PP": "0.52941176470588236", "PRP": "0.10193628067552184", "SBC": "0.023148148148148147", "SC": "0", "SCC": "0.52941176470588236", "SDC": "0.1111111111111111", "SDP": "0.1111111111111111", "SEVC": "0.7322123169490995", "SIC": "0.10938702044459107", "SIRCC": "0.52941176470588236", "SPC": "0.67592592592592582", "SPP": "0.52941176470588236", "SPRP": "0.69265056101565692", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 1, "label": "Andre" }, { "BC": "0.83333333333333326", "CC": "0.058823529411764705", "DC": "4", "DP": "4", "EVC": "0.35220939285728092", "IC": "1.1821086261980831", "IRCC": "0.52941176470588236", "PC": "6.083333333333333", "PP": "0.52941176470588236", "PRP": "0.10193441184414076", "SBC": "0.023148148148148147", "SC": "0", "SCC": "0.52941176470588236", "SDC": "0.1111111111111111", "SDP": "0.1111111111111111", "SEVC": "0.7322123169490995", "SIC": "0.10938702044459107", "SIRCC": "0.52941176470588236", "SPC": "0.67592592592592582", "SPP": "0.52941176470588236", "SPRP": "0.69263786242496905", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 2, "label": "Beverley" }, { "BC": "0", "CC": "0.055555555555555552", "DC": "3", "DP": "3", "EVC": "0.28583498858278172", "IC": "1.0787172011661805", "IRCC": "0.5", "PC": "5.583333333333333", "PP": "0.5", "PRP": "0.079429331031323006", "SBC": "0", "SC": "0", "SCC": "0.5", "SDC": "0.083333333333333329", "SDP": "0.083333333333333329", "SEVC": "0.59422577449581349", "SIC": "0.099819642563139938", "SIRCC": "0.5", "SPC": "0.62037037037037035", "SPP": "0.5", "SPRP": "0.53971726587779545", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 3, "label": "Carol" }, { "BC": "3.6666666666666661", "CC": "0.066666666666666666", "DC": "6", "DP": "6", "EVC": "0.48102085242819692", "IC": "1.3226094727435209", "IRCC": "0.59999999999999998", "PC": "7.083333333333333", "PP": "0.59999999999999998", "PRP": "0.14716840844833681", "SBC": "0.10185185185185183", "SC": "4", "SCC": "0.59999999999999998", "SDC": "0.16666666666666666", "SDP": "0.16666666666666666", "SEVC": "1", "SIC": "0.12238833744113316", "SIRCC": "0.59999999999999998", "SPC": "0.78703703703703698", "SPP": "0.59999999999999998", "SPRP": "1", "SSC": "0.40000000000000002", "eccentricity": "4", "eccentricity_inf": false, "id": 4, "label": "Diane" }, { "BC": "0", "CC": "0.055555555555555552", "DC": "3", "DP": "3", "EVC": "0.28583498858278172", "IC": "1.0787172011661808", "IRCC": "0.5", "PC": "5.583333333333333", "PP": "0.5", "PRP": "0.079427463831839476", "SBC": "0", "SC": "0", "SCC": "0.5", "SDC": "0.083333333333333329", "SDP": "0.083333333333333329", "SEVC": "0.59422577449581349", "SIC": "0.099819642563139951", "SIRCC": "0.5", "SPC": "0.62037037037037035", "SPP": "0.5", "SPRP": "0.53970457837574792", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 5, "label": "Ed" }, { "BC": "8.8333333333333339", "CC": "0.071428571428571425", "DC": "5", "DP": "5", "EVC": "0.39769064296088436", "IC": "1.3321332133213319", "IRCC": "0.64285714285714279", "PC": "6.833333333333333", "PP": "0.64285714285714279", "PRP": "0.12892286613180182", "SBC": "0.24537037037037038", "SC": "2", "SCC": "0.64285714285714279", "SDC": "0.1388888888888889", "SDP": "0.1388888888888889", "SEVC": "0.82676383145000676", "SIC": "0.12326962159912511", "SIRCC": "0.64285714285714279", "SPC": "0.75925925925925919", "SPP": "0.64285714285714279", "SPRP": "0.87602269733765548", "SSC": "0.20000000000000001", "eccentricity": "3", "eccentricity_inf": false, "id": 6, "label": "Fernando" }, { "BC": "8.8333333333333339", "CC": "0.071428571428571425", "DC": "5", "DP": "5", "EVC": "0.39769064296088436", "IC": "1.3321332133213319", "IRCC": "0.64285714285714279", "PC": "6.833333333333333", "PP": "0.64285714285714279", "PRP": "0.12892128375624079", "SBC": "0.24537037037037038", "SC": "4", "SCC": "0.64285714285714279", "SDC": "0.1388888888888889", "SDP": "0.1388888888888889", "SEVC": "0.82676383145000676", "SIC": "0.12326962159912511", "SIRCC": "0.64285714285714279", "SPC": "0.75925925925925919", "SPP": "0.64285714285714279", "SPRP": "0.87601194519609393", "SSC": "0.40000000000000002", "eccentricity": "3", "eccentricity_inf": false, "id": 7, "label": "Garth" }, { "BC": "15", "CC": "0.066666666666666666", "DC": "3", "DP": "3", "EVC": "0.19586058913287382", "IC": "1.1681136543014994", "IRCC": "0.59999999999999998", "PC": "6", "PP": "0.59999999999999998", "PRP": "0.095255994094674334", "SBC": "0.41666666666666669", "SC": "0", "SCC": "0.59999999999999998", "SDC": "0.083333333333333329", "SDP": "0.083333333333333329", "SEVC": "0.40717691996961897", "SIC": "0.10809198863190844", "SIRCC": "0.59999999999999998", "SPC": "0.66666666666666663", "SPP": "0.59999999999999998", "SPRP": "0.64725843745272116", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 8, "label": "Heather" }, { "BC": "8", "CC": "0.047619047619047616", "DC": "2", "DP": "2", "EVC": "0.048073501491671694", "IC": "0.6867749419953596", "IRCC": "0.42857142857142855", "PC": "4.6666666666666661", "PP": "0.42857142857142855", "PRP": "0.085698313648475438", "SBC": "0.22222222222222221", "SC": "0", "SCC": "0.42857142857142855", "SDC": "0.055555555555555552", "SDP": "0.055555555555555552", "SEVC": "0.099940576898062308", "SIC": "0.063551067098203254", "SIRCC": "0.42857142857142855", "SPC": "0.51851851851851838", "SPP": "0.42857142857142855", "SPRP": "0.58231460509787103", "SSC": "0", "eccentricity": "3", "eccentricity_inf": false, "id": 9, "label": "Ike" }, { "BC": "0", "CC": "0.034482758620689655", "DC": "1", "DP": "1", "EVC": "0.011163260312035342", "IC": "0.44324648098232999", "IRCC": "0.31034482758620691", "PC": "3.4166666666666665", "PP": "0.31034482758620691", "PRP": "0.05142178330060207", "SBC": "0", "SC": "0", "SCC": "0.31034482758620691", "SDC": "0.027777777777777776", "SDP": "0.027777777777777776", "SEVC": "0.023207435302821319", "SIC": "0.041016037615042827", "SIRCC": "0.31034482758620691", "SPC": "0.37962962962962959", "SPP": "0.31034482758620691", "SPRP": "0.34940775566417565", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 10, "label": "Jane" } ], "run": { "considerWeights": true, "dropIsolates": false, "inverseWeights": true }, "schema_version": 4 } SocNetV-3.7/src/tools/baselines/prominence/Sampson_Monks_N18__PROM__V4__FT2__W0_IW1_DI0.json000066400000000000000000000433421524402210300312050ustar00rootroot00000000000000{ "counts": { "links_sna": 57, "nodes": 18, "ties_graph": 57 }, "dataset": { "filetype": 2, "name": "Sampson_Monks_N18.net", "path": "src/data/Sampson_Monks_N18.net" }, "graph": { "directed": true, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "Sampson_Monks_N18.net", "ok": true }, "per_node": [ { "BC": "90.416666666666686", "CC": "0", "DC": "3", "DP": "4", "EVC": "0.27525800740230782", "IC": "1.6591818393454247", "IRCC": "0.35020519835841313", "PC": "7.8166666666666673", "PP": "0.47222222222222221", "PRP": "0.10189795197163828", "SBC": "0.33241421568627461", "SC": "18", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.23529411764705882", "SEVC": "0.84472753076655405", "SIC": "0.055583242968039806", "SIRCC": "0.35020519835841313", "SPC": "0.48854166666666671", "SPP": "0.47222222222222221", "SPRP": "0.72541904751593522", "SSC": "0.063380281690140844", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 1, "label": "JohnBosco" }, { "BC": "33.166666666666664", "CC": "0", "DC": "3", "DP": "6", "EVC": "0.26349951678733774", "IC": "1.9609345827366089", "IRCC": "0.28959276018099545", "PC": "7.0166666666666666", "PP": "0.48571428571428577", "PRP": "0.10520270488694911", "SBC": "0.12193627450980392", "SC": "11", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.35294117647058826", "SEVC": "0.80864240163093526", "SIC": "0.065692078331619816", "SIRCC": "0.28959276018099545", "SPC": "0.43854166666666666", "SPP": "0.48571428571428577", "SPRP": "0.74894582764952944", "SSC": "0.03873239436619718", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "Gregory" }, { "BC": "97.416666666666657", "CC": "0", "DC": "4", "DP": "3", "EVC": "0.30359906361390243", "IC": "1.5669431222643135", "IRCC": "0.40699523052464226", "PC": "8.6666666666666661", "PP": "0.37777777777777777", "PRP": "0.055589841528706607", "SBC": "0.35814950980392152", "SC": "44", "SCC": "0", "SDC": "0.23529411764705882", "SDP": "0.17647058823529413", "SEVC": "0.93170218650452796", "SIC": "0.052493209735394043", "SIRCC": "0.40699523052464226", "SPC": "0.54166666666666663", "SPP": "0.37777777777777777", "SPRP": "0.39574818838891113", "SSC": "0.15492957746478872", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "Basil" }, { "BC": "17.583333333333332", "CC": "0", "DC": "3", "DP": "4", "EVC": "0.15621201458207118", "IC": "1.6022824977925143", "IRCC": "0.30117647058823527", "PC": "7.0499999999999998", "PP": "0.2931034482758621", "PRP": "0.045108787996560346", "SBC": "0.064644607843137247", "SC": "17", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.23529411764705882", "SEVC": "0.47939237299323545", "SIC": "0.053677092688872927", "SIRCC": "0.30117647058823527", "SPC": "0.44062499999999999", "SPP": "0.2931034482758621", "SPRP": "0.32113279403467238", "SSC": "0.059859154929577461", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 4, "label": "Peter" }, { "BC": "61.833333333333329", "CC": "0", "DC": "3", "DP": "6", "EVC": "0.16552308456865303", "IC": "1.8236935429486281", "IRCC": "0.32736572890025573", "PC": "7.4333333333333327", "PP": "0.36956521739130432", "PRP": "0.061335394105730634", "SBC": "0.22732843137254899", "SC": "40", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.35294117647058826", "SEVC": "0.507966717597366", "SIC": "0.061094449621597748", "SIRCC": "0.32736572890025573", "SPC": "0.46458333333333329", "SPP": "0.36956521739130432", "SPRP": "0.43665120162157722", "SSC": "0.14084507042253522", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 5, "label": "Bonaventure" }, { "BC": "1.9166666666666665", "CC": "0", "DC": "3", "DP": "2", "EVC": "0.16062136423336693", "IC": "1.4651691583681512", "IRCC": "0.31372549019607843", "PC": "7.1833333333333327", "PP": "0.23943661971830985", "PRP": "0.030186850449862353", "SBC": "0.0070465686274509796", "SC": "2", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.11764705882352941", "SEVC": "0.49292403762445369", "SIC": "0.049083741991163762", "SIRCC": "0.31372549019607843", "SPC": "0.44895833333333329", "SPP": "0.23943661971830985", "SPRP": "0.21490241832279544", "SSC": "0.0070422535211267607", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 6, "label": "Berthold" }, { "BC": "21.499999999999996", "CC": "0", "DC": "3", "DP": "5", "EVC": "0.25510294166925207", "IC": "1.7785648518526203", "IRCC": "0.24686595949855353", "PC": "6.5690476190476188", "PP": "0.48571428571428577", "PRP": "0.095713878045667972", "SBC": "0.079044117647058806", "SC": "20", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.29411764705882354", "SEVC": "0.78287451123118468", "SIC": "0.059582620753576489", "SIRCC": "0.24686595949855353", "SPC": "0.41056547619047618", "SPP": "0.48571428571428577", "SPRP": "0.68139416840556666", "SSC": "0.070422535211267609", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 7, "label": "Mark" }, { "BC": "3.5", "CC": "0", "DC": "3", "DP": "2", "EVC": "0.15911286399923527", "IC": "1.4644802942656017", "IRCC": "0.31372549019607843", "PC": "7.1833333333333327", "PP": "0.23943661971830985", "PRP": "0.032020594039843593", "SBC": "0.012867647058823529", "SC": "5", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.11764705882352941", "SEVC": "0.48829466574908309", "SIC": "0.049060664773298915", "SIRCC": "0.31372549019607843", "SPC": "0.44895833333333329", "SPP": "0.23943661971830985", "SPRP": "0.22795697440261628", "SSC": "0.017605633802816902", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 8, "label": "Victor" }, { "BC": "39.583333333333329", "CC": "0", "DC": "3", "DP": "4", "EVC": "0.20018782037627159", "IC": "1.7505818899509169", "IRCC": "0.3962848297213622", "PC": "8.25", "PP": "0.2931034482758621", "PRP": "0.045107970935373692", "SBC": "0.14552696078431371", "SC": "20", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.23529411764705882", "SEVC": "0.61434784329027492", "SIC": "0.058645180544514536", "SIRCC": "0.3962848297213622", "SPC": "0.515625", "SPP": "0.2931034482758621", "SPRP": "0.32112697731572648", "SSC": "0.070422535211267609", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 9, "label": "Ambrose" }, { "BC": "0", "CC": "0.026315789473684209", "DC": "4", "DP": "0", "EVC": "0.22433600878664386", "IC": "1.607431685789632", "IRCC": "0.44736842105263158", "PC": "9.25", "PP": "0", "PRP": "0.008333333333333335", "SBC": "0", "SC": "0", "SCC": "0.44736842105263153", "SDC": "0.23529411764705882", "SDP": "0", "SEVC": "0.6884551862914372", "SIC": "0.053849592508208477", "SIRCC": "0.44736842105263158", "SPC": "0.54411764705882348", "SPP": "0", "SPRP": "0.059325615601990088", "SSC": "0", "eccentricity": "4", "eccentricity_inf": false, "id": 10, "label": "Romuald" }, { "BC": "24.333333333333332", "CC": "0", "DC": "3", "DP": "2", "EVC": "0.18145073309562348", "IC": "1.5902753552227435", "IRCC": "0.37647058823529411", "PC": "8", "PP": "0.28333333333333333", "PRP": "0.038492518262315784", "SBC": "0.089460784313725492", "SC": "19", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.11764705882352941", "SEVC": "0.55684639720443596", "SIC": "0.053274848699105801", "SIRCC": "0.37647058823529411", "SPC": "0.5", "SPP": "0.28333333333333333", "SPRP": "0.2740310810379275", "SSC": "0.066901408450704219", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 11, "label": "Louis" }, { "BC": "59.833333333333336", "CC": "0", "DC": "4", "DP": "6", "EVC": "0.32585419247851788", "IC": "1.9851221122342262", "IRCC": "0.30117647058823527", "PC": "7.6833333333333336", "PP": "0.5862068965517242", "PRP": "0.1404677094164665", "SBC": "0.21997549019607843", "SC": "36", "SCC": "0", "SDC": "0.23529411764705882", "SDP": "0.35294117647058826", "SEVC": "1", "SIC": "0.06650237006516066", "SIRCC": "0.30117647058823527", "SPC": "0.48020833333333335", "SPP": "0.5862068965517242", "SPRP": "1", "SSC": "0.12676056338028169", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 12, "label": "Winifrid" }, { "BC": "67.916666666666657", "CC": "0", "DC": "3", "DP": "2", "EVC": "0.20703431677866974", "IC": "1.9088007740221025", "IRCC": "0.44290657439446368", "PC": "8.6666666666666661", "PP": "0.2982456140350877", "PRP": "0.021917007991516822", "SBC": "0.24969362745098037", "SC": "13", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.11764705882352941", "SEVC": "0.63535876339021968", "SIC": "0.063945575273358923", "SIRCC": "0.44290657439446368", "SPC": "0.54166666666666663", "SPP": "0.2982456140350877", "SPRP": "0.15602879895005659", "SSC": "0.045774647887323945", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 13, "label": "Amand" }, { "BC": "30.25", "CC": "0", "DC": "3", "DP": "3", "EVC": "0.26496991439292217", "IC": "1.6943242280961932", "IRCC": "0.28959276018099545", "PC": "7.0166666666666666", "PP": "0.44736842105263158", "PRP": "0.077960021483952771", "SBC": "0.11121323529411764", "SC": "18", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.17647058823529413", "SEVC": "0.81315484197856513", "SIC": "0.056760526787143023", "SIRCC": "0.28959276018099545", "SPC": "0.43854166666666666", "SPP": "0.44736842105263158", "SPRP": "0.55500315202558437", "SSC": "0.063380281690140844", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 14, "label": "Hugh" }, { "BC": "4.333333333333333", "CC": "0", "DC": "3", "DP": "2", "EVC": "0.25988085057801719", "IC": "1.7324132381039177", "IRCC": "0.24686595949855353", "PC": "6.5690476190476188", "PP": "0.35416666666666663", "PRP": "0.040467361181193623", "SBC": "0.015931372549019607", "SC": "7", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.11764705882352941", "SEVC": "0.79753723161057677", "SIC": "0.058036523575118161", "SIRCC": "0.24686595949855353", "SPC": "0.41056547619047618", "SPP": "0.35416666666666663", "SPRP": "0.28809013366348657", "SSC": "0.02464788732394366", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 15, "label": "Boniface" }, { "BC": "3.083333333333333", "CC": "0", "DC": "3", "DP": "1", "EVC": "0.23957760793466412", "IC": "1.3606429166053482", "IRCC": "0.24288425047438331", "PC": "6.4023809523809527", "PP": "0.33333333333333331", "PRP": "0.035452265446272592", "SBC": "0.01133578431372549", "SC": "6", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.058823529411764705", "SEVC": "0.73522947829022767", "SIC": "0.045582071857931068", "SIRCC": "0.24288425047438331", "SPC": "0.40014880952380955", "SPP": "0.33333333333333331", "SPRP": "0.25238729665023396", "SSC": "0.021126760563380281", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 16, "label": "Albert" }, { "BC": "0.5", "CC": "0", "DC": "3", "DP": "2", "EVC": "0.25211112473939606", "IC": "1.3377448887996271", "IRCC": "0.32736572890025573", "PC": "7.4000000000000004", "PP": "0.2931034482758621", "PRP": "0.030024353581338377", "SBC": "0.001838235294117647", "SC": "1", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.11764705882352941", "SEVC": "0.77369305216478579", "SIC": "0.044814978937292277", "SIRCC": "0.32736572890025573", "SPC": "0.46250000000000002", "SPP": "0.2931034482758621", "SPRP": "0.21374559111176575", "SSC": "0.0035211267605633804", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 17, "label": "Elias" }, { "BC": "5.8333333333333339", "CC": "0", "DC": "3", "DP": "3", "EVC": "0.25211112473939606", "IC": "1.5618094855042166", "IRCC": "0.32736572890025573", "PC": "7.4000000000000004", "PP": "0.3035714285714286", "PRP": "0.034862893770492467", "SBC": "0.021446078431372553", "SC": "7", "SCC": "0", "SDC": "0.17647058823529413", "SDP": "0.17647058823529413", "SEVC": "0.77369305216478579", "SIC": "0.052321230888603687", "SIRCC": "0.32736572890025573", "SPC": "0.46250000000000002", "SPP": "0.3035714285714286", "SPRP": "0.24819151615215007", "SSC": "0.02464788732394366", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 18, "label": "Simplicius" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 4 } SocNetV-3.7/src/tools/baselines/prominence/TinyDirChain_N3__PROM__V4__FT2__W0_IW1_DI0.json000066400000000000000000000056001524402210300306500ustar00rootroot00000000000000{ "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyDirChain_N3.paj", "path": "src/data/TinyDirChain_N3.paj" }, "graph": { "directed": true, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyDirChain_N3.paj", "ok": true }, "per_node": [ { "BC": "0", "CC": "0.33333333333333331", "DC": "1", "DP": "0", "EVC": "0", "IC": "1", "IRCC": "0.66666666666666663", "PC": "1.5", "PP": "0", "PRP": "0.05000000000000001", "SBC": "0", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0", "SEVC": "0", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SPC": "0.75", "SPP": "0", "SPRP": "0.38872691933916426", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 1, "label": "A" }, { "BC": "1", "CC": "0", "DC": "1", "DP": "1", "EVC": "0", "IC": "1.5", "IRCC": "0.5", "PC": "1", "PP": "0.5", "PRP": "0.092500000000000027", "SBC": "0.5", "SC": "1", "SCC": "0", "SDC": "0.5", "SDP": "0.5", "SEVC": "0", "SIC": "0.42857142857142855", "SIRCC": "0.5", "SPC": "1", "SPP": "0.5", "SPRP": "0.71914480077745391", "SSC": "1", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "B" }, { "BC": "0", "CC": "0", "DC": "0", "DP": "1", "EVC": "0", "IC": "1", "IRCC": "0", "PC": "0", "PP": "0.66666666666666663", "PRP": "0.12862500000000002", "SBC": "0", "SC": "0", "SCC": "0", "SDC": "0", "SDP": "0.5", "SEVC": "0", "SIC": "0.2857142857142857", "SIRCC": "0", "SPC": "0", "SPP": "0.66666666666666663", "SPRP": "1", "SSC": "0", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "C" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 4 } TinyDirChain_N3__PROM__V4__FT2__W0_IW1_DI0_BA1_BB-0.5.json000066400000000000000000000063241524402210300320630ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyDirChain_N3.paj", "path": "src/data/TinyDirChain_N3.paj" }, "graph": { "directed": true, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyDirChain_N3.paj", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "per_node": [ { "BC": "0", "BPC": "0", "CC": "0.33333333333333331", "DC": "1", "DP": "0", "EVC": "0", "IC": "1", "IRCC": "0.66666666666666663", "PC": "1.5", "PP": "0", "PRP": "0.05000000000000001", "SBC": "0", "SBPC": "0", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0", "SEVC": "0", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SPC": "0.75", "SPP": "0", "SPRP": "0.38872691933916426", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 1, "label": "A" }, { "BC": "1", "BPC": "1", "CC": "0", "DC": "1", "DP": "1", "EVC": "0", "IC": "1.5", "IRCC": "0.5", "PC": "1", "PP": "0.5", "PRP": "0.092500000000000013", "SBC": "0.5", "SBPC": "1", "SC": "1", "SCC": "0", "SDC": "0.5", "SDP": "0.5", "SEVC": "0", "SIC": "0.42857142857142855", "SIRCC": "0.5", "SPC": "1", "SPP": "0.5", "SPRP": "0.7191448007774538", "SSC": "1", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "B" }, { "BC": "0", "BPC": "0.5", "CC": "0", "DC": "0", "DP": "1", "EVC": "0", "IC": "1", "IRCC": "0", "PC": "0", "PP": "0.66666666666666663", "PRP": "0.12862500000000002", "SBC": "0", "SBPC": "0.5", "SC": "0", "SCC": "0", "SDC": "0", "SDP": "0.5", "SEVC": "0", "SIC": "0.2857142857142857", "SIRCC": "0", "SPC": "0", "SPP": "0.66666666666666663", "SPRP": "1", "SSC": "0", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "C" } ], "run": { "bonacichAlpha": "1", "bonacichBeta": "-0.5", "bonacichEnabled": true, "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "katzEnabled": false }, "schema_version": 4 } SocNetV-3.7/src/tools/baselines/prominence/TinyDirChain_N3__PROM__V4__FT2__W0_IW1_DI0_KA0.5.json000066400000000000000000000062351524402210300314530ustar00rootroot00000000000000{ "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyDirChain_N3.paj", "path": "src/data/TinyDirChain_N3.paj" }, "graph": { "directed": true, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyDirChain_N3.paj", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "per_node": [ { "BC": "0", "CC": "0.33333333333333331", "DC": "1", "DP": "0", "EVC": "0", "IC": "1", "IRCC": "0.66666666666666663", "KC": "0", "PC": "1.5", "PP": "0", "PRP": "0.05000000000000001", "SBC": "0", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0", "SEVC": "0", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SKC": "0", "SPC": "0.75", "SPP": "0", "SPRP": "0.38872691933916426", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 1, "label": "A" }, { "BC": "1", "CC": "0", "DC": "1", "DP": "1", "EVC": "0", "IC": "1.5", "IRCC": "0.5", "KC": "0.5", "PC": "1", "PP": "0.5", "PRP": "0.092500000000000013", "SBC": "0.5", "SC": "1", "SCC": "0", "SDC": "0.5", "SDP": "0.5", "SEVC": "0", "SIC": "0.42857142857142855", "SIRCC": "0.5", "SKC": "0.66666666666666663", "SPC": "1", "SPP": "0.5", "SPRP": "0.7191448007774538", "SSC": "1", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "B" }, { "BC": "0", "CC": "0", "DC": "0", "DP": "1", "EVC": "0", "IC": "1", "IRCC": "0", "KC": "0.75", "PC": "0", "PP": "0.66666666666666663", "PRP": "0.12862500000000002", "SBC": "0", "SC": "0", "SCC": "0", "SDC": "0", "SDP": "0.5", "SEVC": "0", "SIC": "0.2857142857142857", "SIRCC": "0", "SKC": "1", "SPC": "0", "SPP": "0.66666666666666663", "SPRP": "1", "SSC": "0", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "C" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "katzAlpha": "0.5", "katzEnabled": true }, "schema_version": 4 } TinyDirWeighted_N3__PROM__V4__FT2__W1_IW0_DI0_KA1_BA1_BB1.json000066400000000000000000000071161524402210300330160ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyDirWeighted_N3.paj", "path": "src/data/TinyDirWeighted_N3.paj" }, "graph": { "directed": true, "weighted": true }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 3, "load_msg": "", "net_name": "TinyDirWeighted_N3.paj", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "per_node": [ { "BC": "0", "BPC": "0", "CC": "0.14285714285714285", "DC": "2", "DP": "0", "EVC": "0", "IC": "2.25", "IRCC": "0.2857142857142857", "KC": "0", "PC": "0.69999999999999996", "PP": "0", "PRP": "0.05000000000000001", "SBC": "0", "SBPC": "0", "SC": "0", "SCC": "0.2857142857142857", "SDC": "0.40000000000000002", "SDP": "0", "SEVC": "0", "SIC": "0.26717557251908391", "SIRCC": "0.2857142857142857", "SKC": "0", "SPC": "0.34999999999999998", "SPP": "0", "SPRP": "0.38872691933916426", "SSC": "nan", "eccentricity": "5", "eccentricity_inf": false, "id": 1, "label": "A" }, { "BC": "1", "BPC": "2", "CC": "0", "DC": "3", "DP": "2", "EVC": "0", "IC": "3.6000000000000005", "IRCC": "0.16666666666666666", "KC": "2", "PC": "0.33333333333333331", "PP": "0.25", "PRP": "0.092500000000000013", "SBC": "0.5", "SBPC": "0.22222222222222221", "SC": "0", "SCC": "0", "SDC": "0.59999999999999998", "SDP": "0.40000000000000002", "SEVC": "0", "SIC": "0.42748091603053434", "SIRCC": "0.16666666666666666", "SKC": "0.22222222222222221", "SPC": "0.33333333333333331", "SPP": "0.25", "SPRP": "0.7191448007774538", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "B" }, { "BC": "0", "BPC": "9", "CC": "0", "DC": "0", "DP": "3", "EVC": "0", "IC": "2.5714285714285716", "IRCC": "0", "KC": "9", "PC": "0", "PP": "0.25", "PRP": "0.12862500000000002", "SBC": "0", "SBPC": "1", "SC": "0", "SCC": "0", "SDC": "0", "SDP": "0.59999999999999998", "SEVC": "0", "SIC": "0.30534351145038163", "SIRCC": "0", "SKC": "1", "SPC": "0", "SPP": "0.25", "SPRP": "1", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "C" } ], "run": { "bonacichAlpha": "1", "bonacichBeta": "1", "bonacichEnabled": true, "considerWeights": true, "dropIsolates": false, "inverseWeights": false, "katzAlpha": "1", "katzEnabled": true }, "schema_version": 4 } TinyDirWeighted_N3__PROM__V4__FT2__W1_IW1_DI0_KA1_BA1_BB1.json000066400000000000000000000067501524402210300330220ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyDirWeighted_N3.paj", "path": "src/data/TinyDirWeighted_N3.paj" }, "graph": { "directed": true, "weighted": true }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyDirWeighted_N3.paj", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "per_node": [ { "BC": "0", "BPC": "0", "CC": "0.75", "DC": "2", "DP": "0", "EVC": "0", "IC": "0.42857142857142855", "IRCC": "1.5", "KC": "0", "PC": "3.2000000000000002", "PP": "0", "PRP": "0.05000000000000001", "SBC": "0", "SBPC": "0", "SC": "0", "SCC": "0.5", "SDC": "0.40000000000000002", "SDP": "0", "SEVC": "0", "SIC": "0.30534351145038169", "SIRCC": "1.5", "SKC": "0", "SPC": "1.6000000000000001", "SPP": "0", "SPRP": "0.38872691933916426", "SSC": "nan", "eccentricity": "0.83333333333333326", "eccentricity_inf": false, "id": 1, "label": "A" }, { "BC": "1", "BPC": "0.5", "CC": "0", "DC": "3", "DP": "2", "EVC": "0", "IC": "0.59999999999999987", "IRCC": "1.5", "KC": "0.5", "PC": "3", "PP": "1", "PRP": "0.092500000000000013", "SBC": "0.5", "SBPC": "1", "SC": "0", "SCC": "0", "SDC": "0.59999999999999998", "SDP": "0.40000000000000002", "SEVC": "0", "SIC": "0.42748091603053429", "SIRCC": "1.5", "SKC": "1", "SPC": "3", "SPP": "1", "SPRP": "0.7191448007774538", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "B" }, { "BC": "0", "BPC": "0.5", "CC": "0", "DC": "0", "DP": "3", "EVC": "0", "IC": "0.37499999999999994", "IRCC": "0", "KC": "0.5", "PC": "0", "PP": "1.7142857142857144", "PRP": "0.12862500000000002", "SBC": "0", "SBPC": "1", "SC": "0", "SCC": "0", "SDC": "0", "SDP": "0.59999999999999998", "SEVC": "0", "SIC": "0.26717557251908397", "SIRCC": "0", "SKC": "1", "SPC": "0", "SPP": "1.7142857142857144", "SPRP": "1", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "C" } ], "run": { "bonacichAlpha": "1", "bonacichBeta": "1", "bonacichEnabled": true, "considerWeights": true, "dropIsolates": false, "inverseWeights": true, "katzAlpha": "1", "katzEnabled": true }, "schema_version": 4 } SocNetV-3.7/src/tools/baselines/prominence/TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0.json000066400000000000000000000060151524402210300304120ustar00rootroot00000000000000{ "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "src/data/TinyPath_N3_E2.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "per_node": [ { "BC": "0", "CC": "0.33333333333333331", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.2567686987380754", "SBC": "0", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52778030060034231", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 1, "label": "A" }, { "BC": "1", "CC": "0.5", "DC": "2", "DP": "2", "EVC": "0.81649658092772603", "IC": "1.5", "IRCC": "1", "PC": "2", "PP": "1", "PRP": "0.48650678785472817", "SBC": "1", "SC": "1", "SCC": "1", "SDC": "1", "SDP": "1", "SEVC": "1", "SIC": "0.42857142857142855", "SIRCC": "1", "SPC": "1", "SPP": "1", "SPRP": "1", "SSC": "1", "eccentricity": "1", "eccentricity_inf": false, "id": 2, "label": "B" }, { "BC": "0", "CC": "0.33333333333333331", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676538483825945", "SBC": "0", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.527773488979418", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 3, "label": "C" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 4 } TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0_BA1_BB-0.6_negflip.json000066400000000000000000000066771524402210300333440ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "src/data/TinyPath_N3_E2.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "metrics": { "density": "0.66666666666666663" }, "per_node": [ { "BC": "0", "BPC": "-0.71428571428571463", "CC": "0.33333333333333331", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676869873807551", "SBC": "0", "SBPC": "-0.25000000000000006", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52778030060034231", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 1, "label": "A" }, { "BC": "1", "BPC": "2.8571428571428577", "CC": "0.5", "DC": "2", "DP": "2", "EVC": "0.81649658092772603", "IC": "1.5", "IRCC": "1", "PC": "2", "PP": "1", "PRP": "0.48650678785472834", "SBC": "1", "SBPC": "1", "SC": "1", "SCC": "1", "SDC": "1", "SDP": "1", "SEVC": "1", "SIC": "0.42857142857142855", "SIRCC": "1", "SPC": "1", "SPP": "1", "SPRP": "1", "SSC": "1", "eccentricity": "1", "eccentricity_inf": false, "id": 2, "label": "B" }, { "BC": "0", "BPC": "-0.71428571428571441", "CC": "0.33333333333333331", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676538483825956", "SBC": "0", "SBPC": "-0.25", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52777348897941812", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 3, "label": "C" } ], "run": { "bonacichAlpha": "1", "bonacichBeta": "-0.59999999999999998", "bonacichEnabled": true, "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "katzEnabled": false }, "schema_version": 4 } SocNetV-3.7/src/tools/baselines/prominence/TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0_BA1_BB0.5.json000066400000000000000000000066251524402210300316320ustar00rootroot00000000000000{ "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "src/data/TinyPath_N3_E2.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 2, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "metrics": { "density": "0.66666666666666663" }, "per_node": [ { "BC": "0", "BPC": "4", "CC": "0.33333333333333331", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676869873807551", "SBC": "0", "SBPC": "0.66666666666666663", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52778030060034231", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 1, "label": "A" }, { "BC": "1", "BPC": "6", "CC": "0.5", "DC": "2", "DP": "2", "EVC": "0.81649658092772603", "IC": "1.5", "IRCC": "1", "PC": "2", "PP": "1", "PRP": "0.48650678785472834", "SBC": "1", "SBPC": "1", "SC": "1", "SCC": "1", "SDC": "1", "SDP": "1", "SEVC": "1", "SIC": "0.42857142857142855", "SIRCC": "1", "SPC": "1", "SPP": "1", "SPRP": "1", "SSC": "1", "eccentricity": "1", "eccentricity_inf": false, "id": 2, "label": "B" }, { "BC": "0", "BPC": "3.9999999999999991", "CC": "0.33333333333333331", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676538483825956", "SBC": "0", "SBPC": "0.66666666666666652", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52777348897941812", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 3, "label": "C" } ], "run": { "bonacichAlpha": "1", "bonacichBeta": "0.5", "bonacichEnabled": true, "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "katzEnabled": false }, "schema_version": 4 } TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0_BA1_BB0.75_reject.json000066400000000000000000000065411524402210300331730ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "src/data/TinyPath_N3_E2.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "metrics": { "density": "0.66666666666666663" }, "per_node": [ { "BC": "0", "BPC": "0", "CC": "0.33333333333333331", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676869873807551", "SBC": "0", "SBPC": "0", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52778030060034231", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 1, "label": "A" }, { "BC": "1", "BPC": "0", "CC": "0.5", "DC": "2", "DP": "2", "EVC": "0.81649658092772603", "IC": "1.5", "IRCC": "1", "PC": "2", "PP": "1", "PRP": "0.48650678785472834", "SBC": "1", "SBPC": "0", "SC": "1", "SCC": "1", "SDC": "1", "SDP": "1", "SEVC": "1", "SIC": "0.42857142857142855", "SIRCC": "1", "SPC": "1", "SPP": "1", "SPRP": "1", "SSC": "1", "eccentricity": "1", "eccentricity_inf": false, "id": 2, "label": "B" }, { "BC": "0", "BPC": "0", "CC": "0.33333333333333331", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676538483825956", "SBC": "0", "SBPC": "0", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52777348897941812", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 3, "label": "C" } ], "run": { "bonacichAlpha": "1", "bonacichBeta": "0.75", "bonacichEnabled": true, "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "katzEnabled": false }, "schema_version": 4 } SocNetV-3.7/src/tools/baselines/prominence/TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0_KA0.2.json000066400000000000000000000066011524402210300312060ustar00rootroot00000000000000{ "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "src/data/TinyPath_N3_E2.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 1, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "metrics": { "density": "0.66666666666666663" }, "per_node": [ { "BC": "0", "CC": "0.33333333333333331", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "KC": "0.30434782608695654", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676869873807551", "SBC": "0", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SKC": "0.58333333333333326", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52778030060034231", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 1, "label": "A" }, { "BC": "1", "CC": "0.5", "DC": "2", "DP": "2", "EVC": "0.81649658092772603", "IC": "1.5", "IRCC": "1", "KC": "0.52173913043478271", "PC": "2", "PP": "1", "PRP": "0.48650678785472834", "SBC": "1", "SC": "1", "SCC": "1", "SDC": "1", "SDP": "1", "SEVC": "1", "SIC": "0.42857142857142855", "SIRCC": "1", "SKC": "1", "SPC": "1", "SPP": "1", "SPRP": "1", "SSC": "1", "eccentricity": "1", "eccentricity_inf": false, "id": 2, "label": "B" }, { "BC": "0", "CC": "0.33333333333333331", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "KC": "0.30434782608695654", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676538483825956", "SBC": "0", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SKC": "0.58333333333333326", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52777348897941812", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 3, "label": "C" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "katzAlpha": "0.20000000000000001", "katzEnabled": true }, "schema_version": 4 } TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0_KA0.8_reject.json000066400000000000000000000064471524402210300325010ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "src/data/TinyPath_N3_E2.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "metrics": { "density": "0.66666666666666663" }, "per_node": [ { "BC": "0", "CC": "0.33333333333333331", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "KC": "0", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676869873807551", "SBC": "0", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SKC": "0", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52778030060034231", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 1, "label": "A" }, { "BC": "1", "CC": "0.5", "DC": "2", "DP": "2", "EVC": "0.81649658092772603", "IC": "1.5", "IRCC": "1", "KC": "0", "PC": "2", "PP": "1", "PRP": "0.48650678785472834", "SBC": "1", "SC": "1", "SCC": "1", "SDC": "1", "SDP": "1", "SEVC": "1", "SIC": "0.42857142857142855", "SIRCC": "1", "SKC": "0", "SPC": "1", "SPP": "1", "SPRP": "1", "SSC": "1", "eccentricity": "1", "eccentricity_inf": false, "id": 2, "label": "B" }, { "BC": "0", "CC": "0.33333333333333331", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "KC": "0", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676538483825956", "SBC": "0", "SC": "0", "SCC": "0.66666666666666663", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SKC": "0", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52777348897941812", "SSC": "0", "eccentricity": "2", "eccentricity_inf": false, "id": 3, "label": "C" } ], "run": { "considerWeights": false, "dropIsolates": false, "inverseWeights": true, "katzAlpha": "0.80000000000000004", "katzEnabled": true }, "schema_version": 4 } TinyWeightedIsolate_Undir_N4_E2__PROM__V4__FT2__W0_IW1_DI1_KA0.2_BA1_BB0.3.json000066400000000000000000000107521524402210300356700ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 4, "nodes": 4, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyWeightedIsolate_Undir_N4_E2.paj", "path": "src/data/TinyWeightedIsolate_Undir_N4_E2.paj" }, "graph": { "directed": false, "weighted": true }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 1, "load_msg": "", "net_name": "TinyWeightedIsolate_Undir_N4_E2.paj", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "per_node": [ { "BC": "0", "BPC": "1.9512195121951217", "CC": "0", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "KC": "0.30434782608695654", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676869873807551", "SBC": "0", "SBPC": "0.61538461538461531", "SC": "0", "SCC": "0", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SKC": "0.58333333333333326", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52778030060034231", "SSC": "0", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 1, "label": "A" }, { "BC": "1", "BPC": "3.1707317073170729", "CC": "0", "DC": "2", "DP": "2", "EVC": "0.81649658092772603", "IC": "1.5", "IRCC": "1", "KC": "0.52173913043478271", "PC": "2", "PP": "1", "PRP": "0.48650678785472834", "SBC": "1", "SBPC": "1", "SC": "1", "SCC": "0", "SDC": "1", "SDP": "1", "SEVC": "1", "SIC": "0.42857142857142855", "SIRCC": "1", "SKC": "1", "SPC": "1", "SPP": "1", "SPRP": "1", "SSC": "1", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "B" }, { "BC": "0", "BPC": "1.9512195121951219", "CC": "0", "DC": "1", "DP": "1", "EVC": "0.40824829046386302", "IC": "1", "IRCC": "0.66666666666666663", "KC": "0.30434782608695654", "PC": "1.5", "PP": "0.66666666666666663", "PRP": "0.25676538483825956", "SBC": "0", "SBPC": "0.61538461538461542", "SC": "0", "SCC": "0", "SDC": "0.5", "SDP": "0.5", "SEVC": "0.5", "SIC": "0.2857142857142857", "SIRCC": "0.66666666666666663", "SKC": "0.58333333333333326", "SPC": "0.75", "SPP": "0.66666666666666663", "SPRP": "0.52777348897941812", "SSC": "0", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "C" }, { "BC": "0", "BPC": "0", "CC": "0", "DC": "0", "DP": "0", "EVC": "0", "IC": "0", "IRCC": "0", "KC": "0", "PC": "0", "PP": "0", "PRP": "0.33333333333333331", "SBC": "0", "SBPC": "0", "SC": "0", "SCC": "0", "SDC": "0", "SDP": "0", "SEVC": "0", "SIC": "0", "SIRCC": "0", "SKC": "0", "SPC": "0", "SPP": "0", "SPRP": "0", "SSC": "0", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 4, "label": "D" } ], "run": { "bonacichAlpha": "1", "bonacichBeta": "0.29999999999999999", "bonacichEnabled": true, "considerWeights": false, "dropIsolates": true, "inverseWeights": true, "katzAlpha": "0.20000000000000001", "katzEnabled": true }, "schema_version": 4 } TinyWeightedIsolate_Undir_N4_E2__PROM__V4__FT2__W1_IW0_DI0.json000066400000000000000000000100771524402210300336650ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 4, "nodes": 4, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyWeightedIsolate_Undir_N4_E2.paj", "path": "src/data/TinyWeightedIsolate_Undir_N4_E2.paj" }, "graph": { "directed": false, "weighted": true }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyWeightedIsolate_Undir_N4_E2.paj", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "per_node": [ { "BC": "0", "CC": "0", "DC": "2.5", "DP": "2.5", "EVC": "0.50507627227610541", "IC": "2.0454545454545454", "IRCC": "0.20512820512820512", "PC": "0.65000000000000002", "PP": "0.20512820512820512", "PRP": "0.1925799640879676", "SBC": "0", "SC": "0", "SCC": "0", "SDC": "0.3125", "SDP": "0.3125", "SEVC": "0.62500000000000022", "SIC": "0.31044776119402984", "SIRCC": "0.20512820512820512", "SPC": "0.32500000000000001", "SPP": "0.20512820512820512", "SPRP": "0.52778126951775151", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 1, "label": "A" }, { "BC": "1", "CC": "0", "DC": "4", "DP": "4", "EVC": "0.80812203564176843", "IC": "2.8125", "IRCC": "0.33333333333333331", "PC": "1.0666666666666667", "PP": "0.33333333333333331", "PRP": "0.36488593894954491", "SBC": "0.33333333333333331", "SC": "0", "SCC": "0", "SDC": "0.5", "SDP": "0.5", "SEVC": "1", "SIC": "0.42686567164179107", "SIRCC": "0.33333333333333331", "SPC": "0.53333333333333333", "SPP": "0.33333333333333331", "SPRP": "1", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "B" }, { "BC": "0", "CC": "0", "DC": "1.5", "DP": "1.5", "EVC": "0.3030457633656633", "IC": "1.7307692307692308", "IRCC": "0.2424242424242424", "PC": "0.91666666666666663", "PP": "0.2424242424242424", "PRP": "0.19257652405355657", "SBC": "0", "SC": "0", "SCC": "0", "SDC": "0.1875", "SDP": "0.1875", "SEVC": "0.37500000000000017", "SIC": "0.26268656716417915", "SIRCC": "0.2424242424242424", "SPC": "0.45833333333333331", "SPP": "0.2424242424242424", "SPRP": "0.5277718418198224", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "C" }, { "BC": "0", "CC": "0", "DC": "0", "DP": "0", "EVC": "0", "IC": "0", "IRCC": "0", "PC": "0", "PP": "0", "PRP": "0.25", "SBC": "0", "SC": "0", "SCC": "0", "SDC": "0", "SDP": "0", "SEVC": "0", "SIC": "0", "SIRCC": "0", "SPC": "0", "SPP": "0", "SPRP": "0.68514561213214931", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 4, "label": "D" } ], "run": { "considerWeights": true, "dropIsolates": false, "inverseWeights": false, "katzEnabled": false }, "schema_version": 4 } TinyWeightedIsolate_Undir_N4_E2__PROM__V4__FT2__W1_IW0_DI1_KA0.171499_BA1_BB0.171499.json000066400000000000000000000112661524402210300367220ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 4, "nodes": 4, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyWeightedIsolate_Undir_N4_E2.paj", "path": "src/data/TinyWeightedIsolate_Undir_N4_E2.paj" }, "graph": { "directed": false, "weighted": true }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyWeightedIsolate_Undir_N4_E2.paj", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "per_node": [ { "BC": "0", "BPC": "5.6199957299101415", "CC": "0", "DC": "2.5", "DP": "2.5", "EVC": "0.50507627227610541", "IC": "2.0454545454545454", "IRCC": "0.30769230769230771", "KC": "0.96382364768385931", "PC": "0.65000000000000002", "PP": "0.30769230769230771", "PRP": "0.25676869873807551", "SBC": "0", "SBPC": "0.77229564646841564", "SC": "0", "SCC": "0", "SDC": "0.3125", "SDP": "0.3125", "SEVC": "0.62500000000000022", "SIC": "0.31044776119402984", "SIRCC": "0.30769230769230771", "SKC": "0.77229564646841553", "SPC": "0.32500000000000001", "SPP": "0.30769230769230771", "SPRP": "0.52778030060034231", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 1, "label": "A" }, { "BC": "1", "BPC": "7.2770004021251218", "CC": "0", "DC": "4", "DP": "4", "EVC": "0.80812203564176843", "IC": "2.8125", "IRCC": "0.5", "KC": "1.2479982919640564", "PC": "1.0666666666666667", "PP": "0.5", "PRP": "0.48650678785472834", "SBC": "1", "SBPC": "1", "SC": "0", "SCC": "0", "SDC": "0.5", "SDP": "0.5", "SEVC": "1", "SIC": "0.42686567164179107", "SIRCC": "0.5", "SKC": "1", "SPC": "0.53333333333333333", "SPP": "0.5", "SPRP": "1", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "B" }, { "BC": "0", "BPC": "3.3719974379460842", "CC": "0", "DC": "1.5", "DP": "1.5", "EVC": "0.3030457633656633", "IC": "1.7307692307692308", "IRCC": "0.36363636363636365", "KC": "0.57829418861031567", "PC": "0.91666666666666663", "PP": "0.36363636363636365", "PRP": "0.25676538483825956", "SBC": "0", "SBPC": "0.46337738788104926", "SC": "0", "SCC": "0", "SDC": "0.1875", "SDP": "0.1875", "SEVC": "0.37500000000000017", "SIC": "0.26268656716417915", "SIRCC": "0.36363636363636365", "SKC": "0.46337738788104937", "SPC": "0.45833333333333331", "SPP": "0.36363636363636365", "SPRP": "0.52777348897941812", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "C" }, { "BC": "0", "BPC": "0", "CC": "0", "DC": "0", "DP": "0", "EVC": "0", "IC": "0", "IRCC": "0", "KC": "0", "PC": "0", "PP": "0", "PRP": "0.33333333333333331", "SBC": "0", "SBPC": "0", "SC": "0", "SCC": "0", "SDC": "0", "SDP": "0", "SEVC": "0", "SIC": "0", "SIRCC": "0", "SKC": "0", "SPC": "0", "SPP": "0", "SPRP": "0", "SSC": "0", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 4, "label": "D" } ], "run": { "bonacichAlpha": "1", "bonacichBeta": "0.17149900000000001", "bonacichEnabled": true, "considerWeights": true, "dropIsolates": true, "inverseWeights": false, "katzAlpha": "0.17149900000000001", "katzEnabled": true }, "schema_version": 4 } TinyWeightedIsolate_Undir_N4_E2__PROM__V4__FT2__W1_IW1_DI0.json000066400000000000000000000076421524402210300336720ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 4, "nodes": 4, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyWeightedIsolate_Undir_N4_E2.paj", "path": "src/data/TinyWeightedIsolate_Undir_N4_E2.paj" }, "graph": { "directed": false, "weighted": true }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 3, "load_msg": "", "net_name": "TinyWeightedIsolate_Undir_N4_E2.paj", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "per_node": [ { "BC": "0", "CC": "0", "DC": "2.5", "DP": "2.5", "EVC": "0.30304576336566319", "IC": "0.46153846153846145", "IRCC": "0.90909090909090895", "PC": "3.4375", "PP": "0.90909090909090895", "PRP": "0.1925799640879676", "SBC": "0", "SC": "0", "SCC": "0", "SDC": "0.3125", "SDP": "0.3125", "SEVC": "0.375", "SIC": "0.2626865671641791", "SIRCC": "0.90909090909090895", "SPC": "1.71875", "SPP": "0.90909090909090895", "SPRP": "0.52778126951775151", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 1, "label": "A" }, { "BC": "1", "CC": "0", "DC": "4", "DP": "4", "EVC": "0.80812203564176854", "IC": "0.74999999999999967", "IRCC": "1.25", "PC": "4", "PP": "1.25", "PRP": "0.36488593894954491", "SBC": "0.33333333333333331", "SC": "0", "SCC": "0", "SDC": "0.5", "SDP": "0.5", "SEVC": "1", "SIC": "0.42686567164179096", "SIRCC": "1.25", "SPC": "2", "SPP": "1.25", "SPRP": "1", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "B" }, { "BC": "0", "CC": "0", "DC": "1.5", "DP": "1.5", "EVC": "0.5050762722761053", "IC": "0.5454545454545453", "IRCC": "0.76923076923076916", "PC": "2.4375", "PP": "0.76923076923076916", "PRP": "0.19257652405355657", "SBC": "0", "SC": "0", "SCC": "0", "SDC": "0.1875", "SDP": "0.1875", "SEVC": "0.625", "SIC": "0.31044776119402984", "SIRCC": "0.76923076923076916", "SPC": "1.21875", "SPP": "0.76923076923076916", "SPRP": "0.5277718418198224", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "C" }, { "BC": "0", "CC": "0", "DC": "0", "DP": "0", "EVC": "0", "IC": "0", "IRCC": "0", "PC": "0", "PP": "0", "PRP": "0.25", "SBC": "0", "SC": "0", "SCC": "0", "SDC": "0", "SDP": "0", "SEVC": "0", "SIC": "0", "SIRCC": "0", "SPC": "0", "SPP": "0", "SPRP": "0.68514561213214931", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 4, "label": "D" } ], "run": { "considerWeights": true, "dropIsolates": false, "inverseWeights": true, "katzEnabled": false }, "schema_version": 4 } TinyWeightedIsolate_Undir_N4_E2__PROM__V4__FT2__W1_IW1_DI1.json000066400000000000000000000076101524402210300336660ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 4, "nodes": 4, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyWeightedIsolate_Undir_N4_E2.paj", "path": "src/data/TinyWeightedIsolate_Undir_N4_E2.paj" }, "graph": { "directed": false, "weighted": true }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyWeightedIsolate_Undir_N4_E2.paj", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "per_node": [ { "BC": "0", "CC": "0", "DC": "2.5", "DP": "2.5", "EVC": "0.30304576336566319", "IC": "0.46153846153846145", "IRCC": "1.3636363636363635", "PC": "3.4375", "PP": "1.3636363636363635", "PRP": "0.25676869873807551", "SBC": "0", "SC": "0", "SCC": "0", "SDC": "0.3125", "SDP": "0.3125", "SEVC": "0.375", "SIC": "0.2626865671641791", "SIRCC": "1.3636363636363635", "SPC": "1.71875", "SPP": "1.3636363636363635", "SPRP": "0.52778030060034231", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 1, "label": "A" }, { "BC": "1", "CC": "0", "DC": "4", "DP": "4", "EVC": "0.80812203564176854", "IC": "0.74999999999999967", "IRCC": "1.875", "PC": "4", "PP": "1.875", "PRP": "0.48650678785472834", "SBC": "1", "SC": "0", "SCC": "0", "SDC": "0.5", "SDP": "0.5", "SEVC": "1", "SIC": "0.42686567164179096", "SIRCC": "1.875", "SPC": "2", "SPP": "1.875", "SPRP": "1", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "B" }, { "BC": "0", "CC": "0", "DC": "1.5", "DP": "1.5", "EVC": "0.5050762722761053", "IC": "0.5454545454545453", "IRCC": "1.1538461538461537", "PC": "2.4375", "PP": "1.1538461538461537", "PRP": "0.25676538483825956", "SBC": "0", "SC": "0", "SCC": "0", "SDC": "0.1875", "SDP": "0.1875", "SEVC": "0.625", "SIC": "0.31044776119402984", "SIRCC": "1.1538461538461537", "SPC": "1.21875", "SPP": "1.1538461538461537", "SPRP": "0.52777348897941812", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "C" }, { "BC": "0", "CC": "0", "DC": "0", "DP": "0", "EVC": "0", "IC": "0", "IRCC": "0", "PC": "0", "PP": "0", "PRP": "0.33333333333333331", "SBC": "0", "SC": "0", "SCC": "0", "SDC": "0", "SDP": "0", "SEVC": "0", "SIC": "0", "SIRCC": "0", "SPC": "0", "SPP": "0", "SPRP": "0", "SSC": "0", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 4, "label": "D" } ], "run": { "considerWeights": true, "dropIsolates": true, "inverseWeights": true, "katzEnabled": false }, "schema_version": 4 } TinyWeightedIsolate_Undir_N4_E2__PROM__V4__FT2__W1_IW1_DI1_KA0.2_BA1_BB0.3.json000066400000000000000000000111241524402210300356630ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/prominence{ "counts": { "links_sna": 4, "nodes": 4, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyWeightedIsolate_Undir_N4_E2.paj", "path": "src/data/TinyWeightedIsolate_Undir_N4_E2.paj" }, "graph": { "directed": false, "weighted": true }, "kernel": "prominence", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyWeightedIsolate_Undir_N4_E2.paj", "ok": true }, "metrics": { "density": "0.33333333333333331" }, "per_node": [ { "BC": "0", "BPC": "0.55837563451776651", "CC": "0", "DC": "2.5", "DP": "2.5", "EVC": "0.30304576336566319", "IC": "0.46153846153846145", "IRCC": "1.3636363636363635", "KC": "0.099471670613955121", "PC": "3.4375", "PP": "1.3636363636363635", "PRP": "0.25676869873807551", "SBC": "0", "SBPC": "0.42307692307692302", "SC": "0", "SCC": "0", "SDC": "0.3125", "SDP": "0.3125", "SEVC": "0.375", "SIC": "0.2626865671641791", "SIRCC": "1.3636363636363635", "SKC": "0.40868263473053867", "SPC": "1.71875", "SPP": "1.3636363636363635", "SPRP": "0.52778030060034231", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 1, "label": "A" }, { "BC": "1", "BPC": "1.319796954314721", "CC": "0", "DC": "4", "DP": "4", "EVC": "0.80812203564176854", "IC": "0.74999999999999967", "IRCC": "1.875", "KC": "0.24339588267443979", "PC": "4", "PP": "1.875", "PRP": "0.48650678785472834", "SBC": "1", "SBPC": "1", "SC": "0", "SCC": "0", "SDC": "0.5", "SDP": "0.5", "SEVC": "1", "SIC": "0.42686567164179096", "SIRCC": "1.875", "SKC": "1", "SPC": "2", "SPP": "1.875", "SPRP": "1", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 2, "label": "B" }, { "BC": "0", "BPC": "0.93062605752961081", "CC": "0", "DC": "1.5", "DP": "1.5", "EVC": "0.5050762722761053", "IC": "0.5454545454545453", "IRCC": "1.1538461538461537", "KC": "0.16578611768992535", "PC": "2.4375", "PP": "1.1538461538461537", "PRP": "0.25676538483825956", "SBC": "0", "SBPC": "0.70512820512820507", "SC": "0", "SCC": "0", "SDC": "0.1875", "SDP": "0.1875", "SEVC": "0.625", "SIC": "0.31044776119402984", "SIRCC": "1.1538461538461537", "SKC": "0.68113772455089838", "SPC": "1.21875", "SPP": "1.1538461538461537", "SPRP": "0.52777348897941812", "SSC": "nan", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 3, "label": "C" }, { "BC": "0", "BPC": "0", "CC": "0", "DC": "0", "DP": "0", "EVC": "0", "IC": "0", "IRCC": "0", "KC": "0", "PC": "0", "PP": "0", "PRP": "0.33333333333333331", "SBC": "0", "SBPC": "0", "SC": "0", "SCC": "0", "SDC": "0", "SDP": "0", "SEVC": "0", "SIC": "0", "SIRCC": "0", "SKC": "0", "SPC": "0", "SPP": "0", "SPRP": "0", "SSC": "0", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 4, "label": "D" } ], "run": { "bonacichAlpha": "1", "bonacichBeta": "0.29999999999999999", "bonacichEnabled": true, "considerWeights": true, "dropIsolates": true, "inverseWeights": true, "katzAlpha": "0.20000000000000001", "katzEnabled": true }, "schema_version": 4 } SocNetV-3.7/src/tools/baselines/reachability/000077500000000000000000000000001524402210300212315ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/reachability/DunbarGelada_H22a__REACH__V2.json000066400000000000000000000101501524402210300267550ustar00rootroot00000000000000{ "counts": { "links_sna": 48, "nodes": 12, "ties_graph": 24 }, "dataset": { "filetype": 2, "name": "Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj", "path": "src/data/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" }, "graph": { "directed": false, "weighted": true }, "kernel": "reachability", "load_report": { "fileType_signal": 2, "load_ms": 1, "load_msg": "", "net_name": "Dunbar&Dunbar_Gelada_baboon_colony_H22a", "ok": true }, "reachability": { "matrix": [ [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] ], "nodes": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ], "reachable_density": "1", "reachable_pairs": 144 }, "run": { "considerWeights": true, "dropIsolates": false, "inverseWeights": true, "operation": "reachability_matrix" }, "schema_version": 2 } SocNetV-3.7/src/tools/baselines/reachability/StokmanZiegler_Netherlands__REACH__V2.json000066400000000000000000000145451524402210300311710ustar00rootroot00000000000000{ "counts": { "links_sna": 120, "nodes": 16, "ties_graph": 120 }, "dataset": { "filetype": 5, "name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "path": "src/data/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" }, "graph": { "directed": true, "weighted": true }, "kernel": "reachability", "load_report": { "fileType_signal": 5, "load_ms": 1, "load_msg": "", "net_name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "ok": true }, "reachability": { "matrix": [ [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ] ], "nodes": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ], "reachable_density": "0.8828125", "reachable_pairs": 226 }, "run": { "considerWeights": true, "dropIsolates": false, "inverseWeights": true, "operation": "reachability_matrix" }, "schema_version": 2 } SocNetV-3.7/src/tools/baselines/vertex_connectivity/000077500000000000000000000000001524402210300227045ustar00rootroot00000000000000TinyComplete_Undir_N4_E6__VCONN__V9__FT2__global.json000066400000000000000000000012251524402210300343000ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/vertex_connectivity{ "connectivity_type": "undirected", "counts": { "links_sna": 12, "nodes": 4, "ties_graph": 6 }, "dataset": { "filetype": 2, "name": "TinyComplete_Undir_N4_E6.paj", "path": "src/data/TinyComplete_Undir_N4_E6.paj" }, "global": { "value": 3 }, "graph": { "directed": false, "weighted": false }, "kernel": "vertex_connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyComplete_Undir_N4_E6.paj", "ok": true }, "mode": "global", "schema_version": 9 } TinyDisconnected_Undir_N6_E4__VCONN__V9__FT2__global.json000066400000000000000000000012401524402210300351270ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/vertex_connectivity{ "connectivity_type": "undirected", "counts": { "links_sna": 6, "nodes": 6, "ties_graph": 3 }, "dataset": { "filetype": 2, "name": "TinyDisconnected_Undir_N6_E4.paj", "path": "src/data/TinyDisconnected_Undir_N6_E4.paj" }, "global": { "value": 0 }, "graph": { "directed": false, "weighted": false }, "kernel": "vertex_connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyDisconnected_Undir_N6_E4.paj", "ok": true }, "mode": "global", "schema_version": 9 } SocNetV-3.7/src/tools/baselines/vertex_connectivity/TinyPath_N3_E2__VCONN__V9__FT2__global.json000066400000000000000000000011661524402210300323410ustar00rootroot00000000000000{ "connectivity_type": "undirected", "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "src/data/TinyPath_N3_E2.paj" }, "global": { "value": 1 }, "graph": { "directed": false, "weighted": false }, "kernel": "vertex_connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "mode": "global", "schema_version": 9 } TinyPath_N3_E2__VCONN__V9__FT2__local_1_2_adjacent.json000066400000000000000000000012501524402210300344000ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/vertex_connectivity{ "connectivity_type": "undirected", "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "src/data/TinyPath_N3_E2.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "vertex_connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "local": { "source": 1, "status": "adjacent", "target": 2 }, "mode": "local", "schema_version": 9 } SocNetV-3.7/src/tools/baselines/vertex_connectivity/TinyPath_N3_E2__VCONN__V9__FT2__local_1_3.json000066400000000000000000000012661524402210300326360ustar00rootroot00000000000000{ "connectivity_type": "undirected", "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "src/data/TinyPath_N3_E2.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "vertex_connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "local": { "source": 1, "status": "ok", "target": 3, "value": 1 }, "mode": "local", "schema_version": 9 } TinyWeaklyConn_Dir_N3_E2__VCONN__V9__FT2__global_strong.json000066400000000000000000000012171524402210300356270ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/vertex_connectivity{ "connectivity_type": "strong", "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyWeaklyConn_Dir_N3_E2.paj", "path": "src/data/TinyWeaklyConn_Dir_N3_E2.paj" }, "global": { "value": 0 }, "graph": { "directed": true, "weighted": false }, "kernel": "vertex_connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyWeaklyConn_Dir_N3_E2.paj", "ok": true }, "mode": "global", "schema_version": 9 } TinyWeaklyConn_Dir_N3_E2__VCONN__V9__FT2__global_weak.json000066400000000000000000000012151524402210300352400ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/vertex_connectivity{ "connectivity_type": "weak", "counts": { "links_sna": 2, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyWeaklyConn_Dir_N3_E2.paj", "path": "src/data/TinyWeaklyConn_Dir_N3_E2.paj" }, "global": { "value": 1 }, "graph": { "directed": true, "weighted": false }, "kernel": "vertex_connectivity", "load_report": { "fileType_signal": 2, "load_ms": 0, "load_msg": "", "net_name": "TinyWeaklyConn_Dir_N3_E2.paj", "ok": true }, "mode": "global", "schema_version": 9 } SocNetV-3.7/src/tools/baselines/walks/000077500000000000000000000000001524402210300177125ustar00rootroot00000000000000SocNetV-3.7/src/tools/baselines/walks/DunbarGelada_H22a__WALKS_K6__V3.json000066400000000000000000000113341524402210300260430ustar00rootroot00000000000000{ "counts": { "links_sna": 48, "nodes": 12, "ties_graph": 24 }, "dataset": { "filetype": 2, "name": "Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj", "path": "src/data/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj" }, "graph": { "directed": false, "weighted": true }, "kernel": "walks_matrix", "load_report": { "fileType_signal": 2, "load_ms": 2, "load_msg": "", "net_name": "Dunbar&Dunbar_Gelada_baboon_colony_H22a", "ok": true }, "run": { "considerWeights": true, "dropIsolates": false, "inverseWeights": true, "operation": "walks_matrix_exact_length", "walks_length": 6 }, "schema_version": 3, "walks": { "matrix": [ [ "1120", "164", "1751", "1718", "1173", "1456", "745", "763", "109", "1009", "453", "1064" ], [ "164", "59", "289", "347", "304", "264", "181", "109", "33", "245", "139", "271" ], [ "1751", "289", "2831", "2849", "1943", "2355", "1210", "1264", "179", "1654", "773", "1764" ], [ "1718", "347", "2849", "2989", "2147", "2418", "1319", "1253", "206", "1800", "894", "1941" ], [ "1173", "304", "1943", "2147", "1900", "1750", "1200", "717", "235", "1596", "792", "1665" ], [ "1456", "264", "2355", "2418", "1750", "2004", "1100", "1018", "175", "1486", "708", "1575" ], [ "745", "181", "1210", "1319", "1200", "1100", "774", "433", "157", "1019", "489", "1043" ], [ "763", "109", "1264", "1253", "717", "1018", "433", "633", "48", "608", "288", "669" ], [ "109", "33", "179", "206", "235", "175", "157", "48", "39", "202", "93", "196" ], [ "1009", "245", "1654", "1800", "1596", "1486", "1019", "608", "202", "1351", "653", "1394" ], [ "453", "139", "773", "894", "792", "708", "489", "288", "93", "653", "347", "699" ], [ "1064", "271", "1764", "1941", "1665", "1575", "1043", "669", "196", "1394", "699", "1469" ] ], "nodes": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ], "total_walks": "136644" } } SocNetV-3.7/src/tools/baselines/walks/StokmanZiegler_Netherlands__WALKS_K6__V3.json000066400000000000000000000173251524402210300302510ustar00rootroot00000000000000{ "counts": { "links_sna": 120, "nodes": 16, "ties_graph": 120 }, "dataset": { "filetype": 5, "name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "path": "src/data/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl" }, "graph": { "directed": true, "weighted": true }, "kernel": "walks_matrix", "load_report": { "fileType_signal": 5, "load_ms": 2, "load_msg": "", "net_name": "Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl", "ok": true }, "run": { "considerWeights": true, "dropIsolates": false, "inverseWeights": true, "operation": "walks_matrix_exact_length", "walks_length": 6 }, "schema_version": 3, "walks": { "matrix": [ [ "31590", "37646", "23348", "28886", "18937", "21897", "35845", "31709", "31241", "15805", "23165", "29708", "26821", "17258", "26992", "0" ], [ "37646", "45000", "27994", "34595", "22744", "26134", "42873", "37836", "37398", "18930", "27747", "35504", "32046", "20692", "32299", "0" ], [ "23348", "27994", "17533", "21573", "14266", "16264", "26709", "23528", "23283", "11846", "17296", "22055", "19985", "12920", "20189", "0" ], [ "28886", "34595", "21573", "26976", "17750", "20269", "33237", "29213", "29115", "14569", "21596", "27638", "24829", "16048", "24848", "0" ], [ "18937", "22744", "14266", "17750", "11811", "13325", "21860", "19196", "19139", "9669", "14238", "18166", "16338", "10618", "16428", "0" ], [ "21897", "26134", "16264", "20269", "13325", "15416", "25054", "22155", "21862", "11052", "16303", "20787", "18836", "12019", "18841", "0" ], [ "35845", "42873", "26709", "33237", "21860", "25054", "41109", "36124", "35877", "18044", "26629", "34074", "30675", "19864", "30780", "0" ], [ "31709", "37836", "23528", "29213", "19196", "22155", "36124", "32008", "31564", "15958", "23452", "29989", "27140", "17342", "27232", "0" ], [ "31241", "37398", "23283", "29115", "19139", "21862", "35877", "31564", "31463", "15725", "23322", "29878", "26789", "17320", "26811", "0" ], [ "15805", "18930", "11846", "14569", "9669", "11052", "18044", "15958", "15725", "8086", "11751", "14913", "13571", "8681", "13680", "0" ], [ "23165", "27747", "17296", "21596", "14238", "16303", "26629", "23452", "23322", "11751", "17402", "22161", "19946", "12826", "19940", "0" ], [ "29708", "35504", "22055", "27638", "18166", "20787", "34074", "29989", "29878", "14913", "22161", "28448", "25416", "16481", "25418", "0" ], [ "26821", "32046", "19985", "24829", "16338", "18836", "30675", "27140", "26789", "13571", "19946", "25416", "23085", "14694", "23119", "0" ], [ "17258", "20692", "12920", "16048", "10618", "12019", "19864", "17342", "17320", "8681", "12826", "16481", "14694", "9741", "14841", "0" ], [ "26992", "32299", "20189", "24848", "16428", "18841", "30780", "27232", "26811", "13680", "19940", "25418", "23119", "14841", "23356", "0" ], [ "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" ] ], "nodes": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ], "total_walks": "5129002" } } SocNetV-3.7/src/tools/baselines/walks/TinyPath_N3_E2__WALKS_K2__V3.json000066400000000000000000000021621524402210300253770ustar00rootroot00000000000000{ "counts": { "links_sna": 4, "nodes": 3, "ties_graph": 2 }, "dataset": { "filetype": 2, "name": "TinyPath_N3_E2.paj", "path": "src/data/TinyPath_N3_E2.paj" }, "graph": { "directed": false, "weighted": false }, "kernel": "walks_matrix", "load_report": { "fileType_signal": 2, "load_ms": 1, "load_msg": "", "net_name": "TinyPath_N3_E2.paj", "ok": true }, "run": { "considerWeights": true, "dropIsolates": false, "inverseWeights": true, "operation": "walks_matrix_exact_length", "walks_length": 2 }, "schema_version": 3, "walks": { "matrix": [ [ "1", "0", "1" ], [ "0", "2", "0" ], [ "1", "0", "1" ] ], "nodes": [ 1, 2, 3 ], "total_walks": "6" } } SocNetV-3.7/src/tools/cli/000077500000000000000000000000001524402210300153735ustar00rootroot00000000000000SocNetV-3.7/src/tools/cli/cli_common.cpp000066400000000000000000000127751524402210300202320ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // Common CLI utilities shared by socnetv-cli kernels. // Keep this file logic-free and deterministic. #include "cli_common.h" #include #include #include #include #include #include #include namespace cli { // ---------------- printing ---------------- void printKV(const QString &k, double v) { QTextStream(stdout) << k << "=" << QString::number(v, 'f', 3) << "\n"; } void printKV(const QString &k, const QString &v) { QTextStream(stdout) << k << "=" << v << "\n"; } void printKV(const QString &k, int v) { QTextStream(stdout) << k << "=" << v << "\n"; } void printKV(const QString &k, qint64 v) { QTextStream(stdout) << k << "=" << v << "\n"; } // ---------------- deterministic formatting ---------------- QString d2s(double v) { // Deterministic string for golden compare (avoid float parse/format differences). return QString::number(v, 'g', 17); } // ---------------- JSON I/O ---------------- bool writeJsonFile(const QString &path, const QJsonObject &obj, QString *err) { QFile f(path); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { if (err) *err = QString("Could not open for write: %1").arg(path); return false; } const QJsonDocument doc(obj); f.write(doc.toJson(QJsonDocument::Indented)); return true; } bool readJsonFile(const QString &path, QJsonObject *outObj, QString *err) { QFile f(path); if (!f.open(QIODevice::ReadOnly)) { if (err) *err = QString("Could not open for read: %1").arg(path); return false; } const QByteArray data = f.readAll(); QJsonParseError pe; const QJsonDocument doc = QJsonDocument::fromJson(data, &pe); if (doc.isNull() || !doc.isObject()) { if (err) *err = QString("Invalid JSON (%1) in %2").arg(pe.errorString(), path); return false; } *outObj = doc.object(); return true; } // ---------------- compare helpers (generic) ---------------- bool cmpStr(const QJsonObject &e, const QJsonObject &a, const QString &k, QTextStream &err) { const QString ev = e.value(k).toString(); const QString av = a.value(k).toString(); if (ev != av) { err << "MISMATCH " << k << " expected=" << ev << " got=" << av << "\n"; return false; } return true; } bool cmpInt(const QJsonObject &e, const QJsonObject &a, const QString &k, QTextStream &err) { const int ev = e.value(k).toInt(); const int av = a.value(k).toInt(); if (ev != av) { err << "MISMATCH " << k << " expected=" << ev << " got=" << av << "\n"; return false; } return true; } bool cmpBool(const QJsonObject &e, const QJsonObject &a, const QString &k, QTextStream &err) { const bool ev = e.value(k).toBool(); const bool av = a.value(k).toBool(); if (ev != av) { err << "MISMATCH " << k << " expected=" << (ev ? "true" : "false") << " got=" << (av ? "true" : "false") << "\n"; return false; } return true; } bool almostEqual(double a, double b, double rel, double abs) { // Treat NaN==NaN as equal for regression purposes if (std::isnan(a) && std::isnan(b)) return true; // Treat +Inf==+Inf and -Inf==-Inf as equal if (std::isinf(a) || std::isinf(b)) return a == b; const double diff = std::abs(a - b); if (diff <= abs) return true; const double scale = std::max(std::abs(a), std::abs(b)); return diff <= rel * scale; } bool cmpNumStrTol(const QJsonObject &e, const QJsonObject &a, const QString &k, QTextStream &err, double rel, double abs) { const QString es = e.value(k).toString(); const QString as = a.value(k).toString(); bool ok1 = false, ok2 = false; const double ev = es.toDouble(&ok1); const double av = as.toDouble(&ok2); if (!ok1 || !ok2) { err << "MISMATCH " << k << " non-numeric expected=" << es << " got=" << as << "\n"; return false; } if (!almostEqual(ev, av, rel, abs)) { err << "MISMATCH " << k << " expected=" << es << " got=" << as << " (diff=" << d2s(std::abs(ev - av)) << ")\n"; return false; } return true; } bool cmpIntArray(const QJsonArray &e, const QJsonArray &a, QTextStream &err, const QString &what) { if (e.size() != a.size()) { err << "MISMATCH " << what << ".size expected=" << e.size() << " got=" << a.size() << "\n"; return false; } for (int i = 0; i < e.size(); ++i) { const int ev = e.at(i).toInt(); const int av = a.at(i).toInt(); if (ev != av) { err << "MISMATCH " << what << "[" << i << "] expected=" << ev << " got=" << av << "\n"; return false; } } return true; } bool cmpStrArray(const QJsonArray &e, const QJsonArray &a, QTextStream &err, const QString &what) { if (e.size() != a.size()) { err << "MISMATCH " << what << ".size expected=" << e.size() << " got=" << a.size() << "\n"; return false; } for (int i = 0; i < e.size(); ++i) { const QString ev = e.at(i).toString(); const QString av = a.at(i).toString(); if (ev != av) { err << "MISMATCH " << what << "[" << i << "] expected=" << ev << " got=" << av << "\n"; return false; } } return true; } } // namespace cli SocNetV-3.7/src/tools/cli/cli_common.h000066400000000000000000000052261524402210300176700ustar00rootroot00000000000000#pragma once #include #include #include #include namespace cli { struct CliConfig { bool verbose = false; QString inputPath; int fileFormat = 0; QString delimiter; int twoMode = 0; bool hasLabels = false; bool computeCentralities = true; bool considerWeights = false; bool inverseWeights = true; bool dropIsolates = false; QString dumpJsonPath; QString compareJsonPath; int benchRuns = 0; // 0 = off QString kernel; // "distance", etc. bool strict = false; // if true, timing regressions fail (exit non-zero) QString connectivityType = "weak"; // "weak"|"strong", for --kernel connectivity on directed graphs QString connMode = "global"; // "local"|"global", for --kernel vertex_connectivity int connSource = -1; // required for connMode == "local" int connTarget = -1; // required for connMode == "local" qreal katzAlpha = -1; // >= 0 enables Katz Centrality for --kernel prominence qreal bonacichAlpha = -1; // >= 0 enables Bonacich Power Centrality for --kernel prominence qreal bonacichBeta = 0; }; // ---------------- printing ---------------- void printKV(const QString &k, double v); void printKV(const QString &k, const QString &v); void printKV(const QString &k, int v); void printKV(const QString &k, qint64 v); // ---------------- deterministic formatting ---------------- QString d2s(double v); // ---------------- JSON I/O ---------------- bool writeJsonFile(const QString &path, const QJsonObject &obj, QString *err); bool readJsonFile(const QString &path, QJsonObject *outObj, QString *err); // ---------------- compare helpers ---------------- bool cmpStr(const QJsonObject &e, const QJsonObject &a, const QString &k, QTextStream &err); bool cmpInt(const QJsonObject &e, const QJsonObject &a, const QString &k, QTextStream &err); bool cmpBool(const QJsonObject &e, const QJsonObject &a, const QString &k, QTextStream &err); bool almostEqual(double a, double b, double rel = 1e-15, double abs = 0.0); bool cmpNumStrTol(const QJsonObject &e, const QJsonObject &a, const QString &k, QTextStream &err, double rel = 1e-15, double abs = 0.0); bool cmpIntArray(const QJsonArray &e, const QJsonArray &a, QTextStream &err, const QString &what); bool cmpStrArray(const QJsonArray &e, const QJsonArray &a, QTextStream &err, const QString &what); } // namespace cli SocNetV-3.7/src/tools/cli/kernels/000077500000000000000000000000001524402210300170365ustar00rootroot00000000000000SocNetV-3.7/src/tools/cli/kernels/kernel_clustering_v6.cpp000066400000000000000000000367211524402210300237050ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // Clustering kernel (schema v6) for socnetv-cli. // Covers clustering coefficients + triad census + clique census. #include "kernel_clustering_v6.h" #include "graph.h" #include "graphvertex.h" #include "tools/cli/cli_common.h" #include "tools/headless_graph_loader.h" #include #include #include #include #include #include namespace cli { // ------------------------------ // Per-node builder // ------------------------------ static QJsonArray buildPerNodeArrayV6(Graph &g) { QJsonArray arr; const QList verts = g.verticesList(); // deterministic order for (int v : verts) { GraphVertex *gv = g.vertexPtr(v); if (!gv) continue; QJsonObject o; o["id"] = v; o["label"] = gv->label(); o["CLC"] = d2s(gv->CLC()); o["hasCLC"] = gv->hasCLC(); arr.append(o); } return arr; } // ------------------------------ // Metrics builder // ------------------------------ static QJsonObject buildMetricsV6(Graph &g, const qreal averageCLC) { QJsonObject metrics; int nodesWithCLC = 0; const QList verts = g.verticesList(); for (int v : verts) { GraphVertex *gv = g.vertexPtr(v); if (gv && gv->hasCLC()) ++nodesWithCLC; } metrics["averageCLC"] = d2s(averageCLC); metrics["nodesWithCLC"] = nodesWithCLC; return metrics; } // ------------------------------ // Triad census builder // ------------------------------ static QJsonObject buildTriadCensusV6(Graph &g) { // IMPORTANT: // graph_triad_census.cpp documents the canonical storage order: // 0:003 1:012 2:102 3:021D 4:021U 5:021C 6:111D 7:111U // 8:030T 9:030C 10:201 11:120D 12:120U 13:120C 14:210 15:300 const QList &f = g.graphTriadTypeFreqs(); QJsonObject classes; classes["003"] = (f.size() > 0) ? f[0] : 0; classes["012"] = (f.size() > 1) ? f[1] : 0; classes["102"] = (f.size() > 2) ? f[2] : 0; classes["021D"] = (f.size() > 3) ? f[3] : 0; classes["021U"] = (f.size() > 4) ? f[4] : 0; classes["021C"] = (f.size() > 5) ? f[5] : 0; classes["111D"] = (f.size() > 6) ? f[6] : 0; classes["111U"] = (f.size() > 7) ? f[7] : 0; classes["030T"] = (f.size() > 8) ? f[8] : 0; classes["030C"] = (f.size() > 9) ? f[9] : 0; classes["201"] = (f.size() > 10) ? f[10] : 0; classes["120D"] = (f.size() > 11) ? f[11] : 0; classes["120U"] = (f.size() > 12) ? f[12] : 0; classes["120C"] = (f.size() > 13) ? f[13] : 0; classes["210"] = (f.size() > 14) ? f[14] : 0; classes["300"] = (f.size() > 15) ? f[15] : 0; int totalTriads = 0; for (int x : f) totalTriads += x; QJsonObject root; root["classes"] = classes; root["total_triads"] = totalTriads; return root; } // ------------------------------ // Clique census builder // ------------------------------ static QJsonObject buildCliquesV6(Graph &g) { QJsonObject root; QJsonObject bySize; int totalCliques = 0; int maxCliqueSize = 0; // Maximal cliques are stored by size in m_cliques and exposed via // graphCliquesOfSize(size). We build a stable size->count map. const int maxPossibleSize = g.vertices(); for (int s = 1; s <= maxPossibleSize; ++s) { const int count = g.graphCliquesOfSize(s); if (count <= 0) continue; bySize[QString::number(s)] = count; totalCliques += count; maxCliqueSize = s; } root["by_size"] = bySize; root["max_clique_size"] = maxCliqueSize; root["total_cliques"] = totalCliques; return root; } // ------------------------------ // JSON builder // ------------------------------ static QJsonObject buildGoldenJsonV6( const QString &inputPath, int fileFormat, const HeadlessLoadResult &load, Graph &g, const CliConfig &cfg, const qreal averageCLC) { QJsonObject root; root["schema_version"] = 6; root["kernel"] = "clustering"; QJsonObject dataset; dataset["path"] = inputPath; dataset["name"] = QFileInfo(inputPath).fileName(); dataset["filetype"] = fileFormat; root["dataset"] = dataset; QJsonObject run; run["considerWeights"] = cfg.considerWeights; run["inverseWeights"] = cfg.inverseWeights; run["dropIsolates"] = cfg.dropIsolates; root["run"] = run; const int ties_graph = load.tiesGraph; // canonical const int links_sna = g.isDirected() ? ties_graph : (2 * ties_graph); QJsonObject counts; counts["nodes"] = load.totalNodes; counts["links_sna"] = links_sna; counts["ties_graph"] = ties_graph; root["counts"] = counts; QJsonObject graph; graph["directed"] = g.isDirected(); graph["weighted"] = g.isWeighted(); root["graph"] = graph; root["metrics"] = buildMetricsV6(g, averageCLC); root["per_node"] = buildPerNodeArrayV6(g); root["triad_census"] = buildTriadCensusV6(g); root["cliques"] = buildCliquesV6(g); QJsonObject loadReport; loadReport["ok"] = load.ok; loadReport["fileType_signal"] = load.fileType; loadReport["load_ms"] = static_cast(load.elapsedTime); loadReport["load_msg"] = load.message; loadReport["net_name"] = load.netName; root["load_report"] = loadReport; return root; } // ------------------------------ // Per-node comparator // ------------------------------ static bool cmpPerNodeArrayV6(const QJsonArray &eArr, const QJsonArray &aArr, QTextStream &err) { if (eArr.size() != aArr.size()) { err << "MISMATCH per_node.size expected=" << eArr.size() << " got=" << aArr.size() << "\n"; return false; } bool ok = true; auto cmpNodeFieldStrictStr = [&](const QJsonObject &e, const QJsonObject &a, const QString &k, int id) { const QString ev = e.value(k).toString(); const QString av = a.value(k).toString(); if (ev != av) { err << "MISMATCH per_node id=" << id << " field=" << k << " expected=" << ev << " got=" << av << "\n"; ok = false; } }; auto cmpNodeFieldNumStrTol = [&](const QJsonObject &e, const QJsonObject &a, const QString &k, int id, double tol) { const QString es = e.value(k).toString(); const QString as = a.value(k).toString(); bool ok1 = false, ok2 = false; const double ev = es.toDouble(&ok1); const double av = as.toDouble(&ok2); if (!ok1 || !ok2) { err << "MISMATCH per_node id=" << id << " field=" << k << " non-numeric expected=" << es << " got=" << as << "\n"; ok = false; return; } if (!almostEqual(ev, av, tol)) { err << "MISMATCH per_node id=" << id << " field=" << k << " expected=" << es << " got=" << as << " (diff=" << d2s(std::abs(ev - av)) << ")\n"; ok = false; } }; auto cmpNodeFieldInt = [&](const QJsonObject &e, const QJsonObject &a, const QString &k, int id) { const int ev = e.value(k).toInt(); const int av = a.value(k).toInt(); if (ev != av) { err << "MISMATCH per_node id=" << id << " field=" << k << " expected=" << ev << " got=" << av << "\n"; ok = false; } }; auto cmpNodeFieldBool = [&](const QJsonObject &e, const QJsonObject &a, const QString &k, int id) { const bool ev = e.value(k).toBool(); const bool av = a.value(k).toBool(); if (ev != av) { err << "MISMATCH per_node id=" << id << " field=" << k << " expected=" << (ev ? 1 : 0) << " got=" << (av ? 1 : 0) << "\n"; ok = false; } }; constexpr double TOL = 1e-15; for (int i = 0; i < eArr.size(); ++i) { const QJsonObject e = eArr.at(i).toObject(); const QJsonObject a = aArr.at(i).toObject(); const int eid = e.value("id").toInt(); const int aid = a.value("id").toInt(); if (eid != aid) { err << "MISMATCH per_node ordering at index=" << i << " expected_id=" << eid << " got_id=" << aid << "\n"; ok = false; continue; } cmpNodeFieldInt(e, a, "id", eid); cmpNodeFieldStrictStr(e, a, "label", eid); cmpNodeFieldNumStrTol(e, a, "CLC", eid, TOL); cmpNodeFieldBool(e, a, "hasCLC", eid); } return ok; } // ------------------------------ // Triad comparator // ------------------------------ static bool cmpTriadClassesV6(const QJsonObject &eObj, const QJsonObject &aObj, QTextStream &err) { bool ok = true; const QStringList fields = { "003", "012", "102", "021D", "021U", "021C", "111D", "111U", "030T", "030C", "201", "120D", "120U", "120C", "210", "300"}; for (const QString &f : fields) ok &= cmpInt(eObj, aObj, f, err); return ok; } static bool cmpCliquesBySizeV6(const QJsonObject &eObj, const QJsonObject &aObj, QTextStream &err) { bool ok = true; const QStringList eKeys = eObj.keys(); const QStringList aKeys = aObj.keys(); if (eKeys.size() != aKeys.size()) { err << "MISMATCH cliques.by_size.size expected=" << eKeys.size() << " got=" << aKeys.size() << "\n"; ok = false; } for (const QString &k : eKeys) { if (!aObj.contains(k)) { err << "MISMATCH cliques.by_size missing key=" << k << "\n"; ok = false; continue; } ok &= cmpInt(eObj, aObj, k, err); } return ok; } // ------------------------------ // schema v6 compare // ------------------------------ static int compareGoldenV6(const QJsonObject &expected, const QJsonObject &actual) { QTextStream err(stderr); if (expected.value("schema_version").toInt() != 6 || actual.value("schema_version").toInt() != 6) { err << "ERROR: schema_version mismatch or unsupported\n"; return 2; } bool ok = true; ok &= cmpStr(expected, actual, "kernel", err); // should be "clustering" const QJsonObject eDataset = expected.value("dataset").toObject(); const QJsonObject aDataset = actual.value("dataset").toObject(); ok &= cmpInt(eDataset, aDataset, "filetype", err); ok &= cmpStr(eDataset, aDataset, "name", err); const QJsonObject eRun = expected.value("run").toObject(); const QJsonObject aRun = actual.value("run").toObject(); ok &= cmpBool(eRun, aRun, "considerWeights", err); ok &= cmpBool(eRun, aRun, "inverseWeights", err); ok &= cmpBool(eRun, aRun, "dropIsolates", err); const QJsonObject eCounts = expected.value("counts").toObject(); const QJsonObject aCounts = actual.value("counts").toObject(); ok &= cmpInt(eCounts, aCounts, "nodes", err); ok &= cmpInt(eCounts, aCounts, "links_sna", err); ok &= cmpInt(eCounts, aCounts, "ties_graph", err); const QJsonObject eGraph = expected.value("graph").toObject(); const QJsonObject aGraph = actual.value("graph").toObject(); ok &= cmpBool(eGraph, aGraph, "directed", err); ok &= cmpBool(eGraph, aGraph, "weighted", err); const QJsonObject eMetrics = expected.value("metrics").toObject(); const QJsonObject aMetrics = actual.value("metrics").toObject(); ok &= cmpNumStrTol(eMetrics, aMetrics, "averageCLC", err); ok &= cmpInt(eMetrics, aMetrics, "nodesWithCLC", err); const QJsonArray ePN = expected.value("per_node").toArray(); const QJsonArray aPN = actual.value("per_node").toArray(); ok &= cmpPerNodeArrayV6(ePN, aPN, err); const QJsonObject eTriads = expected.value("triad_census").toObject(); const QJsonObject aTriads = actual.value("triad_census").toObject(); ok &= cmpTriadClassesV6(eTriads.value("classes").toObject(), aTriads.value("classes").toObject(), err); ok &= cmpInt(eTriads, aTriads, "total_triads", err); const QJsonObject eCliques = expected.value("cliques").toObject(); const QJsonObject aCliques = actual.value("cliques").toObject(); ok &= cmpCliquesBySizeV6(eCliques.value("by_size").toObject(), aCliques.value("by_size").toObject(), err); ok &= cmpInt(eCliques, aCliques, "max_clique_size", err); ok &= cmpInt(eCliques, aCliques, "total_cliques", err); if (!ok) return 1; err << "OK: baseline match\n"; return 0; } // ------------------------------ // Runner // ------------------------------ int runKernelClusteringV6(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g) { QElapsedTimer t; t.start(); const qreal averageCLC = g.clusteringCoefficient(); if (!g.graphTriadCensus()) { QTextStream(stderr) << "ERROR: graphTriadCensus failed\n"; return 2; } g.graphCliques(QSet(), QSet(), QSet()); const qint64 computeMs = t.elapsed(); printKV("COMPUTE_MS", computeMs); const QJsonObject actual = buildGoldenJsonV6(cfg.inputPath, cfg.fileFormat, load, g, cfg, averageCLC); if (!cfg.dumpJsonPath.isEmpty()) { QString err; if (!writeJsonFile(cfg.dumpJsonPath, actual, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } QTextStream(stderr) << "WROTE_JSON=" << cfg.dumpJsonPath << "\n"; } if (!cfg.compareJsonPath.isEmpty()) { QJsonObject expected; QString err; if (!readJsonFile(cfg.compareJsonPath, &expected, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } return compareGoldenV6(expected, actual); } return 0; } } // namespace cliSocNetV-3.7/src/tools/cli/kernels/kernel_clustering_v6.h000066400000000000000000000004151524402210300233410ustar00rootroot00000000000000#pragma once #include "tools/cli/cli_common.h" class Graph; struct HeadlessLoadResult; namespace cli { int runKernelClusteringV6(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g); } // namespace cliSocNetV-3.7/src/tools/cli/kernels/kernel_connectivity_v7.cpp000066400000000000000000000164251524402210300242440ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // Connectivity kernel (schema v7) for socnetv-cli. // Computes weakly or strongly connected components (--connectivity-type weak|strong, weak is the // default and the only option that means anything on an undirected graph) via // Graph::graphWeaklyConnectedComponents() / Graph::graphStronglyConnectedComponents(), and emits a // JSON report suitable for golden-baseline regression testing. #include "kernel_connectivity_v7.h" #include "graph.h" #include "graphvertex.h" #include "tools/headless_graph_loader.h" #include "tools/cli/cli_common.h" #include #include #include namespace cli { static QJsonObject buildGoldenJsonV7( const QString &inputPath, int fileFormat, const HeadlessLoadResult &load, Graph &g, int componentCount, const QString &connectivityTypeLabel) { QJsonObject root; root["schema_version"] = 7; root["kernel"] = "connectivity"; QJsonObject dataset; dataset["path"] = inputPath; dataset["name"] = QFileInfo(inputPath).fileName(); dataset["filetype"] = fileFormat; root["dataset"] = dataset; const int ties_graph = load.tiesGraph; const int links_sna = g.isDirected() ? ties_graph : (2 * ties_graph); QJsonObject counts; counts["nodes"] = load.totalNodes; counts["links_sna"] = links_sna; counts["ties_graph"] = ties_graph; root["counts"] = counts; QJsonObject graph; graph["directed"] = g.isDirected(); graph["weighted"] = g.isWeighted(); root["graph"] = graph; QJsonObject conn; conn["connected"] = (componentCount <= 1); conn["component_count"] = componentCount; conn["type"] = connectivityTypeLabel; root["connectivity"] = conn; // Per-node component IDs (deterministic: BFS visits vertices in list order). Only meaningful // for weak/undirected mode - Graph::vertexComponentId() is populated by // graphWeaklyConnectedComponents() only; graphStronglyConnectedComponents() reports just a // count, not per-vertex SCC membership (see its doc comment), so emitting it here for strong // mode would silently show weak IDs mislabeled as strong ones. const bool showComponentIds = (connectivityTypeLabel != "strong"); const QHash &compMap = g.vertexComponentId(); QJsonArray perNode; const QList verts = g.verticesList(); for (int v : verts) { GraphVertex *gv = g.vertexPtr(v); if (!gv) continue; QJsonObject o; o["id"] = v; o["label"] = gv->label(); if (showComponentIds) o["component_id"] = compMap.value(v, 0); perNode.append(o); } root["per_node"] = perNode; QJsonObject loadReport; loadReport["ok"] = load.ok; loadReport["fileType_signal"] = load.fileType; loadReport["load_ms"] = static_cast(load.elapsedTime); loadReport["load_msg"] = load.message; loadReport["net_name"] = load.netName; root["load_report"] = loadReport; return root; } static int compareGoldenV7(const QJsonObject &expected, const QJsonObject &actual) { QTextStream err(stderr); if (expected.value("schema_version").toInt() != 7 || actual.value("schema_version").toInt() != 7) { err << "ERROR: schema_version mismatch or unsupported\n"; return 2; } bool ok = true; ok &= cmpInt(expected.value("dataset").toObject(), actual.value("dataset").toObject(), "filetype", err); ok &= cmpStr(expected.value("dataset").toObject(), actual.value("dataset").toObject(), "name", err); ok &= cmpInt(expected.value("counts").toObject(), actual.value("counts").toObject(), "nodes", err); ok &= cmpInt(expected.value("counts").toObject(), actual.value("counts").toObject(), "ties_graph", err); ok &= cmpBool(expected.value("graph").toObject(), actual.value("graph").toObject(), "directed", err); const QJsonObject eConn = expected.value("connectivity").toObject(); const QJsonObject aConn = actual.value("connectivity").toObject(); ok &= cmpBool(eConn, aConn, "connected", err); ok &= cmpInt (eConn, aConn, "component_count", err); ok &= cmpStr (eConn, aConn, "type", err); // Per-node component IDs const QJsonArray ePN = expected.value("per_node").toArray(); const QJsonArray aPN = actual.value("per_node").toArray(); if (ePN.size() != aPN.size()) { err << "MISMATCH per_node.size expected=" << ePN.size() << " got=" << aPN.size() << "\n"; ok = false; } else { for (int i = 0; i < ePN.size(); ++i) { const QJsonObject e = ePN.at(i).toObject(); const QJsonObject a = aPN.at(i).toObject(); const int eid = e.value("id").toInt(); const int aid = a.value("id").toInt(); if (eid != aid) { err << "MISMATCH per_node ordering at index=" << i << " expected_id=" << eid << " got_id=" << aid << "\n"; ok = false; continue; } const int ec = e.value("component_id").toInt(); const int ac = a.value("component_id").toInt(); if (ec != ac) { err << "MISMATCH per_node id=" << eid << " component_id expected=" << ec << " got=" << ac << "\n"; ok = false; } } } if (!ok) return 1; err << "OK: baseline match\n"; return 0; } int runKernelConnectivityV7(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g) { // Strong connectivity only means something distinct from weak on a directed graph - on an // undirected graph the two notions coincide, so --connectivity-type is ignored there (same // as the GUI's Connectedness action, which only asks weak-vs-strong for directed networks). const bool useStrong = g.isDirected() && (cfg.connectivityType == "strong"); const int components = useStrong ? g.graphStronglyConnectedComponents() : g.graphWeaklyConnectedComponents(); const bool connected = (components <= 1); const QString typeLabel = useStrong ? "strong" : (g.isDirected() ? "weak" : "connected"); printKV("COMPONENTS", components); printKV("CONNECTED", connected ? 1 : 0); printKV("TYPE", typeLabel); const QJsonObject actual = buildGoldenJsonV7( cfg.inputPath, cfg.fileFormat, load, g, components, typeLabel); if (!cfg.dumpJsonPath.isEmpty()) { QString err; if (!writeJsonFile(cfg.dumpJsonPath, actual, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } QTextStream(stderr) << "WROTE_JSON=" << cfg.dumpJsonPath << "\n"; } if (!cfg.compareJsonPath.isEmpty()) { QJsonObject expected; QString err; if (!readJsonFile(cfg.compareJsonPath, &expected, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } return compareGoldenV7(expected, actual); } return 0; } } // namespace cli SocNetV-3.7/src/tools/cli/kernels/kernel_connectivity_v7.h000066400000000000000000000007561524402210300237110ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // Connectivity kernel (schema v7) for socnetv-cli. // Reports connected/disconnected status and weakly connected component count. #pragma once #include "tools/cli/cli_common.h" class Graph; struct HeadlessLoadResult; namespace cli { int runKernelConnectivityV7(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g); } // namespace cli SocNetV-3.7/src/tools/cli/kernels/kernel_distance_v1.cpp000066400000000000000000000300451524402210300233040ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // Distance/Centrality kernel (schema v1) for socnetv-cli. // Extracted mechanically from former monolithic socnetv_cli.cpp. // No behavior changes intended. #include "kernel_distance_v1.h" #include "graph.h" #include "graphvertex.h" #include "tools/headless_graph_loader.h" #include "tools/cli/cli_common.h" #include #include #include #include #include #include #include #include namespace cli { // ---- local helpers (schema v1 only) ---- static QJsonArray buildPerNodeArray(Graph &g) { QJsonArray arr; // Deterministic order: vertex numbers ascending const QList verts = g.verticesList(); for (int v : verts) { GraphVertex *gv = g.vertexPtr(v); if (!gv) continue; QJsonObject o; o["id"] = v; o["label"] = gv->label(); // Core outputs requested: CC/BC/SC/EC/PC + standardized variants o["CC"] = d2s(gv->CC()); o["SCC"] = d2s(gv->SCC()); o["BC"] = d2s(gv->BC()); o["SBC"] = d2s(gv->SBC()); o["SC"] = d2s(gv->SC()); o["SSC"] = d2s(gv->SSC()); o["EC"] = d2s(gv->EC()); o["SEC"] = d2s(gv->SEC()); o["PC"] = d2s(gv->PC()); o["SPC"] = d2s(gv->SPC()); // Useful distance-kernel side values (helps catch subtle regressions) o["distance_sum"] = d2s(gv->distanceSum()); const qreal ecc = gv->eccentricity(); o["eccentricity"] = d2s(ecc); o["eccentricity_inf"] = (ecc >= 2147483647.0); arr.append(o); } return arr; } static QJsonObject buildGoldenJsonV1( const QString &inputPath, int fileFormat, const HeadlessLoadResult &load, Graph &g, bool computeCentralities, bool considerWeights, bool inverseWeights, bool dropIsolates, double avgDist, int diameter) { QJsonObject root; root["schema_version"] = 1; // ---------------- Dataset ---------------- QJsonObject dataset; dataset["path"] = inputPath; dataset["name"] = QFileInfo(inputPath).fileName(); dataset["filetype"] = fileFormat; root["dataset"] = dataset; // ---------------- Run config ---------------- QJsonObject run; run["computeCentralities"] = computeCentralities; run["considerWeights"] = considerWeights; run["inverseWeights"] = inverseWeights; run["dropIsolates"] = dropIsolates; root["run"] = run; // ---------------- Counts ---------------- const int ties_graph = load.tiesGraph; // canonical, already correct const int links_sna = g.isDirected() ? ties_graph : (2 * ties_graph); QJsonObject counts; counts["nodes"] = load.totalNodes; counts["links_sna"] = links_sna; counts["ties_graph"] = ties_graph; root["counts"] = counts; // ---------------- Graph flags ---------------- QJsonObject graph; graph["directed"] = g.isDirected(); graph["weighted"] = g.isWeighted(); root["graph"] = graph; // ---------------- Metrics ---------------- QJsonObject metrics; metrics["avg_distance"] = d2s(avgDist); metrics["diameter"] = diameter; metrics["disconnected_pairs"] = g.notConnectedPairsSize(); metrics["connected"] = g.isConnectedCached(); // NEW: Density (relation-aware, cached, canonical) metrics["density"] = d2s(g.graphDensity()); root["metrics"] = metrics; // ---------------- Per-node vectors ---------------- if (computeCentralities) root["per_node"] = buildPerNodeArray(g); // ---------------- Load report ---------------- QJsonObject loadReport; loadReport["ok"] = load.ok; loadReport["fileType_signal"] = load.fileType; loadReport["load_ms"] = static_cast(load.elapsedTime); loadReport["load_msg"] = load.message; loadReport["net_name"] = load.netName; root["load_report"] = loadReport; return root; } static bool cmpPerNodeArray(const QJsonArray &eArr, const QJsonArray &aArr, QTextStream &err) { if (eArr.size() != aArr.size()) { err << "MISMATCH per_node.size expected=" << eArr.size() << " got=" << aArr.size() << "\n"; return false; } bool ok = true; auto cmpNodeFieldStrictStr = [&](const QJsonObject &e, const QJsonObject &a, const QString &k, int id) { const QString ev = e.value(k).toString(); const QString av = a.value(k).toString(); if (ev != av) { err << "MISMATCH per_node id=" << id << " field=" << k << " expected=" << ev << " got=" << av << "\n"; ok = false; } }; auto cmpNodeFieldNumStrTol = [&](const QJsonObject &e, const QJsonObject &a, const QString &k, int id) { const QString es = e.value(k).toString(); const QString as = a.value(k).toString(); bool ok1 = false, ok2 = false; const double ev = es.toDouble(&ok1); const double av = as.toDouble(&ok2); if (!ok1 || !ok2) { err << "MISMATCH per_node id=" << id << " field=" << k << " non-numeric expected=" << es << " got=" << as << "\n"; ok = false; return; } if (!almostEqual(ev, av, 1e-15)) { err << "MISMATCH per_node id=" << id << " field=" << k << " expected=" << es << " got=" << as << " (diff=" << d2s(std::abs(ev - av)) << ")\n"; ok = false; } }; auto cmpNodeFieldInt = [&](const QJsonObject &e, const QJsonObject &a, const QString &k, int id) { const int ev = e.value(k).toInt(); const int av = a.value(k).toInt(); if (ev != av) { err << "MISMATCH per_node id=" << id << " field=" << k << " expected=" << ev << " got=" << av << "\n"; ok = false; } }; for (int i = 0; i < eArr.size(); ++i) { const QJsonObject e = eArr.at(i).toObject(); const QJsonObject a = aArr.at(i).toObject(); const int eid = e.value("id").toInt(); const int aid = a.value("id").toInt(); if (eid != aid) { err << "MISMATCH per_node ordering at index=" << i << " expected_id=" << eid << " got_id=" << aid << "\n"; ok = false; continue; } cmpNodeFieldInt(e, a, "id", eid); cmpNodeFieldStrictStr(e, a, "label", eid); const QStringList fields = { "CC", "SCC", "BC", "SBC", "SC", "SSC", "EC", "SEC", "PC", "SPC", "distance_sum", "eccentricity"}; for (const QString &f : fields) cmpNodeFieldNumStrTol(e, a, f, eid); } return ok; } static int compareGoldenV1(const QJsonObject &expected, const QJsonObject &actual) { QTextStream err(stderr); if (expected.value("schema_version").toInt() != 1 || actual.value("schema_version").toInt() != 1) { err << "ERROR: schema_version mismatch or unsupported\n"; return 2; } bool ok = true; const QJsonObject eDataset = expected.value("dataset").toObject(); const QJsonObject aDataset = actual.value("dataset").toObject(); ok &= cmpInt(eDataset, aDataset, "filetype", err); ok &= cmpStr(eDataset, aDataset, "name", err); const QJsonObject eRun = expected.value("run").toObject(); const QJsonObject aRun = actual.value("run").toObject(); ok &= cmpBool(eRun, aRun, "computeCentralities", err); ok &= cmpBool(eRun, aRun, "considerWeights", err); ok &= cmpBool(eRun, aRun, "inverseWeights", err); ok &= cmpBool(eRun, aRun, "dropIsolates", err); const QJsonObject eCounts = expected.value("counts").toObject(); const QJsonObject aCounts = actual.value("counts").toObject(); ok &= cmpInt(eCounts, aCounts, "nodes", err); ok &= cmpInt(eCounts, aCounts, "links_sna", err); ok &= cmpInt(eCounts, aCounts, "ties_graph", err); const QJsonObject eGraph = expected.value("graph").toObject(); const QJsonObject aGraph = actual.value("graph").toObject(); ok &= cmpBool(eGraph, aGraph, "directed", err); ok &= cmpBool(eGraph, aGraph, "weighted", err); const QJsonObject eMetrics = expected.value("metrics").toObject(); const QJsonObject aMetrics = actual.value("metrics").toObject(); ok &= cmpNumStrTol(eMetrics, aMetrics, "avg_distance", err, 1e-15); ok &= cmpInt(eMetrics, aMetrics, "diameter", err); ok &= cmpInt(eMetrics, aMetrics, "disconnected_pairs", err); ok &= cmpBool(eMetrics, aMetrics, "connected", err); const bool wantPerNode = expected.value("run").toObject().value("computeCentralities").toBool(); if (wantPerNode) { const QJsonArray ePN = expected.value("per_node").toArray(); const QJsonArray aPN = actual.value("per_node").toArray(); ok &= cmpPerNodeArray(ePN, aPN, err); } if (!ok) return 1; err << "OK: baseline match\n"; return 0; } // ---- exported runner ---- int runKernelDistanceV1(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g) { // Ensure centralities are actually computed before we read per-node values. auto run_compute_once_ms = [&]() -> qint64 { // Prevent early-return in DistanceEngine::compute() g.resetDistanceCentralityCacheFlags(); QElapsedTimer t; t.start(); // Canonical entry point (refactor target) g.graphDistancesGeodesic(cfg.computeCentralities, cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates); return t.elapsed(); }; if (cfg.benchRuns > 0) { // warmup (not measured) (void)run_compute_once_ms(); std::vector ms; ms.reserve(static_cast(cfg.benchRuns)); for (int r = 0; r < cfg.benchRuns; ++r) ms.push_back(run_compute_once_ms()); std::sort(ms.begin(), ms.end()); const qint64 minMs = ms.front(); const qint64 maxMs = ms.back(); const double meanMs = std::accumulate(ms.begin(), ms.end(), 0.0) / static_cast(ms.size()); const qint64 medianMs = (ms.size() % 2 == 1) ? ms[ms.size() / 2] : (ms[ms.size() / 2 - 1] + ms[ms.size() / 2]) / 2; printKV("COMPUTE_RUNS", cfg.benchRuns); printKV("COMPUTE_MS_MIN", minMs); printKV("COMPUTE_MS_MEDIAN", medianMs); printKV("COMPUTE_MS_MEAN", meanMs); printKV("COMPUTE_MS_MAX", maxMs); return 0; } const qint64 computeMs = run_compute_once_ms(); printKV("COMPUTE_MS", computeMs); const qreal avgDist = g.graphDistanceGeodesicAverageCached(); const int diameter = g.graphDiameterCached(); const int discPairs = g.notConnectedPairsSize(); const bool connected = g.isConnectedCached(); printKV("AVG_DIST", QString::number(avgDist, 'g', 12)); printKV("DIAMETER", diameter); printKV("DISC_PAIRS", discPairs); printKV("CONNECTED", connected ? 1 : 0); if (cfg.computeCentralities) printKV("PER_NODE", g.verticesList().size()); const QJsonObject actual = buildGoldenJsonV1( cfg.inputPath, cfg.fileFormat, load, g, cfg.computeCentralities, cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates, static_cast(avgDist), diameter); if (!cfg.dumpJsonPath.isEmpty()) { QString err; if (!writeJsonFile(cfg.dumpJsonPath, actual, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } QTextStream(stderr) << "WROTE_JSON=" << cfg.dumpJsonPath << "\n"; } if (!cfg.compareJsonPath.isEmpty()) { QJsonObject expected; QString err; if (!readJsonFile(cfg.compareJsonPath, &expected, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } return compareGoldenV1(expected, actual); } return 0; } } // namespace cli SocNetV-3.7/src/tools/cli/kernels/kernel_distance_v1.h000066400000000000000000000010661524402210300227520ustar00rootroot00000000000000#pragma once #include #include "tools/cli/cli_common.h" // for cli::CliConfig class Graph; struct HeadlessLoadResult; namespace cli { // Runs schema v1 distance kernel. // - If cfg.benchRuns > 0: prints COMPUTE_MS_* stats and returns 0. // - Otherwise: runs once, prints metrics KVs, optionally dump/compare JSON. // Return codes match existing behavior (0 ok, 1 mismatch, 2 usage/error). int runKernelDistanceV1(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g); } // namespace cli SocNetV-3.7/src/tools/cli/kernels/kernel_io_roundtrip_v5.cpp000066400000000000000000000576751524402210300242550ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // // IO Roundtrip kernel (schema v5) for socnetv-cli. // Multirelational support: // - Build canonical signature PER RELATION // - Roundtrip equivalence requires identical relations bundle // - Reports relation count in JSON + harness prints RELATIONS in facade // // NOTE: For Pajek multirel matrices, roundtrip currently fails because exporter // likely writes only the current relation. This is a real IO regression signal. #include "kernel_io_roundtrip_v5.h" #include "graph.h" #include "graphvertex.h" #include "tools/headless_graph_loader.h" #include #include #include #include #include #include #include #include #include #include #include namespace cli { namespace { // ------------------------------- // Helpers // ------------------------------- static QByteArray sha256(const QByteArray &bytes) { return QCryptographicHash::hash(bytes, QCryptographicHash::Sha256); } static QJsonObject hashToJsonObjectSorted(const QHash &h) { QMap sorted; for (auto it = h.begin(); it != h.end(); ++it) sorted.insert(it.key(), it.value()); QJsonObject o; for (auto it = sorted.begin(); it != sorted.end(); ++it) o.insert(it.key(), it.value()); return o; } static QString suffixForFileType(int ft) { // Matches global.h enum switch (ft) { case FileType::GRAPHML: return ".graphml"; case FileType::PAJEK: return ".paj"; case FileType::ADJACENCY: return ".adj"; case FileType::GRAPHVIZ: return ".dot"; case FileType::UCINET: return ".dl"; case FileType::GML: return ".gml"; default: return ".tmp"; } } struct EdgeRow { int u = 0; int v = 0; QString w; // deterministic string QString label; // may be empty }; static bool edgeLess(const EdgeRow &a, const EdgeRow &b) { if (a.u != b.u) return a.u < b.u; if (a.v != b.v) return a.v < b.v; if (a.w != b.w) return a.w < b.w; return a.label < b.label; } static void printSigMismatchHint(const QJsonObject &sig1, const QJsonObject &sig2) { QTextStream err(stderr); err << "SIG1_SHA256=" << sig1.value("hash_sha256").toString() << "\n"; err << "SIG2_SHA256=" << sig2.value("hash_sha256").toString() << "\n"; const QJsonArray e1 = sig1.value("edge_list").toArray(); const QJsonArray e2 = sig2.value("edge_list").toArray(); if (e1 != e2) { err << "HINT=edge_list differ\n"; const int n = qMin(e1.size(), e2.size()); for (int i = 0; i < n; ++i) { const QJsonObject o1 = e1.at(i).toObject(); const QJsonObject o2 = e2.at(i).toObject(); if (o1 != o2) { err << "FIRST_DIFF edge_list[" << i << "]\n"; err << "EXPECTED=" << QJsonDocument(o1).toJson(QJsonDocument::Compact) << "\n"; err << "GOT=" << QJsonDocument(o2).toJson(QJsonDocument::Compact) << "\n"; return; } } err << "FIRST_DIFF edge_list size expected=" << e1.size() << " got=" << e2.size() << "\n"; return; } const QJsonArray l1 = sig1.value("node_labels").toArray(); const QJsonArray l2 = sig2.value("node_labels").toArray(); if (l1 != l2) { err << "HINT=node_labels differ\n"; const int n = qMin(l1.size(), l2.size()); for (int i = 0; i < n; ++i) { const QString a = l1.at(i).toString(); const QString b = l2.at(i).toString(); if (a != b) { err << "FIRST_DIFF node_labels[" << i << "] expected=" << a << " got=" << b << "\n"; return; } } err << "FIRST_DIFF node_labels size expected=" << l1.size() << " got=" << l2.size() << "\n"; return; } const QJsonArray a1 = sig1.value("node_custom_attributes").toArray(); const QJsonArray a2 = sig2.value("node_custom_attributes").toArray(); if (a1 != a2) { err << "HINT=node_custom_attributes differ\n"; err << "FIRST_DIFF node_custom_attributes differs (array mismatch)\n"; return; } err << "HINT=signature differs but no localized diff found (unexpected)\n"; } // ------------------------------- // Signature for CURRENT relation // ------------------------------- static QJsonObject buildSignatureForCurrentRelation(Graph &g) { QList order = g.verticesList(); std::sort(order.begin(), order.end()); QJsonArray nodeLabels; for (int id : order) nodeLabels.append(g.vertexLabel(id)); QJsonArray nodeAttrs; for (int id : order) nodeAttrs.append(hashToJsonObjectSorted(g.vertexCustomAttributes(id))); QList edges; edges.reserve(g.edgesEnabled()); QSet seenUndir; for (int u : order) { GraphVertex *gv = g.vertexPtr(u); if (!gv) continue; // current relation only (as per GraphVertex state after relationSet) const QHash out = gv->outEdgesEnabledHash(false); for (auto it = out.begin(); it != out.end(); ++it) { const int v = it.key(); const qreal w = it.value(); int uu = u, vv = v; if (!g.isDirected()) { uu = qMin(u, v); vv = qMax(u, v); const qulonglong key = (static_cast(static_cast(uu)) << 32) | static_cast(static_cast(vv)); if (seenUndir.contains(key)) continue; seenUndir.insert(key); } EdgeRow row; row.u = uu; row.v = vv; row.w = d2s(static_cast(w)); row.label = g.edgeLabel(uu, vv); edges.push_back(row); } } std::sort(edges.begin(), edges.end(), edgeLess); QJsonArray edgeList; for (const auto &e : edges) { QJsonObject o; o["u"] = e.u; o["v"] = e.v; o["w"] = e.w; if (!e.label.isEmpty()) o["label"] = e.label; edgeList.append(o); } QJsonObject sig; sig["node_labels"] = nodeLabels; sig["node_custom_attributes"] = nodeAttrs; sig["edge_list"] = edgeList; const QByteArray canon = QJsonDocument(sig).toJson(QJsonDocument::Compact); sig["hash_sha256"] = QString::fromLatin1(sha256(canon).toHex()); return sig; } // ------------------------------- // Per-relation bundle // ------------------------------- static QJsonArray buildRelationsBundle(Graph &g) { QJsonArray arr; const int relCount = g.relations(); const int originalRel = g.relationCurrent(); for (int r = 0; r < relCount; ++r) { g.relationSet(r, false /* updateUI */); QJsonObject relObj; relObj["index"] = r; relObj["name"] = g.relationCurrentName(); relObj["ties_graph"] = g.edgesEnabled(); // canonical ties for this relation relObj["signature"] = buildSignatureForCurrentRelation(g); arr.append(relObj); } // restore if (originalRel >= 0 && originalRel < relCount) g.relationSet(originalRel, false); return arr; } static bool compareRelationsBundle(const QJsonArray &expected, const QJsonArray &actual, QTextStream &err) { if (expected.size() != actual.size()) { err << "MISMATCH roundtrip.relations.size expected=" << expected.size() << " got=" << actual.size() << "\n"; return false; } for (int i = 0; i < expected.size(); ++i) { const QJsonObject e = expected.at(i).toObject(); const QJsonObject a = actual.at(i).toObject(); if (e.value("index").toInt() != a.value("index").toInt()) { err << "MISMATCH relations[" << i << "].index expected=" << e.value("index").toInt() << " got=" << a.value("index").toInt() << "\n"; return false; } if (e.value("name").toString() != a.value("name").toString()) { err << "MISMATCH relations[" << i << "].name expected=" << e.value("name").toString() << " got=" << a.value("name").toString() << "\n"; return false; } if (e.value("ties_graph").toInt() != a.value("ties_graph").toInt()) { err << "MISMATCH relations[" << i << "].ties_graph expected=" << e.value("ties_graph").toInt() << " got=" << a.value("ties_graph").toInt() << "\n"; return false; } const QJsonObject es = e.value("signature").toObject(); const QJsonObject as = a.value("signature").toObject(); if (es != as) { err << "MISMATCH relations[" << i << "].signature differs\n"; printSigMismatchHint(es, as); return false; } } return true; } // ------------------------------- // Golden JSON builder + compare (schema v5) // ------------------------------- static QJsonObject buildGoldenJsonV5Io(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g, const QJsonObject &sig_current_relation, const QJsonArray &relations_bundle, bool performed, bool equivalent, qint64 saveMs, qint64 reloadMs, qint64 totalMs) { QJsonObject root; root["schema_version"] = 5; root["kernel"] = "io_roundtrip"; QJsonObject dataset; dataset["path"] = cfg.inputPath; dataset["name"] = QFileInfo(cfg.inputPath).fileName(); dataset["filetype"] = cfg.fileFormat; root["dataset"] = dataset; QJsonObject run; run["considerWeights"] = cfg.considerWeights; run["inverseWeights"] = cfg.inverseWeights; run["dropIsolates"] = cfg.dropIsolates; run["operation"] = "io_roundtrip_same_format"; root["run"] = run; QJsonObject graph; graph["directed"] = g.isDirected(); // Use isAnyRelationWeighted() rather than isWeighted() so a multi-relation // graph is reported as weighted if at least one relation has non-unit weights, // regardless of which relation is currently active. graph["weighted"] = g.isAnyRelationWeighted(); graph["relations"] = g.relations(); root["graph"] = graph; // For continuity with existing CLI semantics: // counts are derived from the currently selected relation (usually relation 0 after load). const int ties_graph = load.tiesGraph; const int links_sna = g.isDirected() ? ties_graph : (2 * ties_graph); QJsonObject counts; counts["nodes"] = load.totalNodes; counts["links_sna"] = links_sna; counts["ties_graph"] = ties_graph; root["counts"] = counts; QJsonObject metrics; metrics["density"] = d2s(g.graphDensity()); root["metrics"] = metrics; QJsonObject timings; timings["load_ms"] = static_cast(load.elapsedTime); timings["save_ms"] = static_cast(saveMs); timings["reload_ms"] = static_cast(reloadMs); timings["total_ms"] = static_cast(totalMs); root["timings"] = timings; // v5 existing key (current relation signature) root["signature"] = sig_current_relation; // NEW in v5 (additive, no schema bump): multirel bundle root["relations_bundle"] = relations_bundle; QJsonObject rt; rt["performed"] = performed; rt["equivalent"] = equivalent; root["roundtrip_result"] = rt; QJsonObject loadReport; loadReport["ok"] = load.ok; loadReport["fileType_signal"] = load.fileType; loadReport["load_ms"] = static_cast(load.elapsedTime); loadReport["load_msg"] = load.message; loadReport["net_name"] = load.netName; root["load_report"] = loadReport; return root; } static int checkIoTimings(const QJsonObject &expected, const QJsonObject &actual, bool strict, QTextStream &err) { // Timing check policy: // - N < 50: skip entirely (measurement noise dominates) // - baseline load_ms == 0: skip (can't compute threshold) // - N >= 50: warn if actual > baseline * 110%; fail only if --strict const int n = expected.value("counts").toObject().value("nodes").toInt(0); if (n < 50) { err << "TIMING_SKIP: N=" << n << " < 50, timing not enforced\n"; return 0; } const qint64 baselineMs = static_cast( expected.value("timings").toObject().value("load_ms").toInteger(0)); if (baselineMs == 0) { err << "TIMING_SKIP: baseline load_ms=0, timing not enforced\n"; return 0; } const qint64 actualMs = static_cast( actual.value("timings").toObject().value("load_ms").toInteger(0)); const qint64 allowed = baselineMs + (baselineMs / 10); // +10% if (actualMs <= allowed) { err << "TIMING_OK: load_ms=" << actualMs << " <= " << allowed << " (baseline=" << baselineMs << " +10%)\n"; return 0; } err << "TIMING_WARN: load_ms=" << actualMs << " > " << allowed << " (baseline=" << baselineMs << " +10%)\n"; if (strict) { err << "TIMING_FAIL: --strict mode, timing regression is a hard failure\n"; return 1; } return 0; } static int compareGoldenV5Io(const QJsonObject &expected, const QJsonObject &actual, const CliConfig &cfg) { QTextStream err(stderr); if (expected.value("schema_version").toInt() != 5 || actual.value("schema_version").toInt() != 5) { err << "ERROR: schema_version mismatch or unsupported\n"; return 2; } if (expected.value("kernel").toString() != "io_roundtrip" || actual.value("kernel").toString() != "io_roundtrip") { err << "ERROR: kernel mismatch or unsupported\n"; return 2; } bool ok = true; const QJsonObject eDs = expected.value("dataset").toObject(); const QJsonObject aDs = actual.value("dataset").toObject(); ok &= cmpInt(eDs, aDs, "filetype", err); ok &= cmpStr(eDs, aDs, "name", err); const QJsonObject eRun = expected.value("run").toObject(); const QJsonObject aRun = actual.value("run").toObject(); ok &= cmpBool(eRun, aRun, "considerWeights", err); ok &= cmpBool(eRun, aRun, "inverseWeights", err); ok &= cmpBool(eRun, aRun, "dropIsolates", err); ok &= cmpStr(eRun, aRun, "operation", err); const QJsonObject eG = expected.value("graph").toObject(); const QJsonObject aG = actual.value("graph").toObject(); ok &= cmpBool(eG, aG, "directed", err); ok &= cmpBool(eG, aG, "weighted", err); ok &= cmpInt(eG, aG, "relations", err); const QJsonObject eC = expected.value("counts").toObject(); const QJsonObject aC = actual.value("counts").toObject(); ok &= cmpInt(eC, aC, "nodes", err); ok &= cmpInt(eC, aC, "links_sna", err); ok &= cmpInt(eC, aC, "ties_graph", err); const QJsonObject eM = expected.value("metrics").toObject(); const QJsonObject aM = actual.value("metrics").toObject(); ok &= cmpNumStrTol(eM, aM, "density", err, 0.0, 0.0); const QJsonObject eRt = expected.value("roundtrip_result").toObject(); const QJsonObject aRt = actual.value("roundtrip_result").toObject(); ok &= cmpBool(eRt, aRt, "performed", err); ok &= cmpBool(eRt, aRt, "equivalent", err); if (!ok) return 1; // Compare multirel bundle strictly (always present in v5 output now). const QJsonArray eRel = expected.value("relations_bundle").toArray(); const QJsonArray aRel = actual.value("relations_bundle").toArray(); if (!compareRelationsBundle(eRel, aRel, err)) return 1; // Timing check (advisory unless --strict) const int timingRc = checkIoTimings(expected, actual, cfg.strict, err); if (timingRc != 0) return timingRc; err << "OK: baseline match\n"; return 0; } } // namespace // ------------------------------- // exported runner // ------------------------------- int runKernelIoRoundtripV5(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g) { // IO kernel must not mutate graph (drop isolates is a mutation). if (cfg.dropIsolates) { QTextStream(stderr) << "ERROR: --drop-isolates is not allowed for --kernel io_roundtrip\n"; return 2; } if (cfg.benchRuns > 0) { QTextStream(stderr) << "ERROR: --bench is only supported with --kernel distance\n"; return 2; } const bool exportSupported = g.isFileFormatExportSupported(cfg.fileFormat); if (!exportSupported) { QTextStream(stderr) << "ROUNDTRIP_SKIPPED: export not supported for file type " << cfg.fileFormat << "\n"; } QElapsedTimer total; total.start(); // Build multirel bundle from loaded model const QJsonArray rel1 = buildRelationsBundle(g); // Keep legacy single signature: current relation snapshot const QJsonObject sig1_current = buildSignatureForCurrentRelation(g); bool performed = false; bool equivalent = true; qint64 saveMs = 0; qint64 reloadMs = 0; if (exportSupported) { performed = true; QTemporaryDir tmpDir; if (!tmpDir.isValid()) { QTextStream(stderr) << "ERROR: could not create temporary directory for roundtrip\n"; return 2; } const QString outPath = tmpDir.path() + "/socnetv_cli_roundtrip" + suffixForFileType(cfg.fileFormat); // Save (timed) QElapsedTimer tSave; tSave.start(); g.saveToFile(outPath, cfg.fileFormat, true /*saveEdgeWeights*/, false /*saveZeroWeightEdges*/); saveMs = tSave.elapsed(); // Reload into fresh graph (timed) Graph g2; QElapsedTimer tReload; tReload.start(); const HeadlessLoadResult load2 = loadGraphHeadless( g2, outPath, "UTF-8", cfg.fileFormat, cfg.delimiter, cfg.twoMode, cfg.hasLabels); reloadMs = tReload.elapsed(); if (!load2.ok) { QTextStream(stderr) << "ERROR: roundtrip reload failed: " << load2.message << "\n"; return 1; } const QJsonArray rel2 = buildRelationsBundle(g2); equivalent = (rel1 == rel2); if (!equivalent) { QTextStream(stdout) << "ROUNDTRIP_EQUIV=0\n"; QTextStream(stderr) << "MISMATCH_DETAIL: relations bundle differs\n"; QTextStream err(stderr); (void)compareRelationsBundle(rel1, rel2, err); } else { QTextStream(stdout) << "ROUNDTRIP_EQUIV=1\n"; } } const QJsonObject root = buildGoldenJsonV5Io(cfg, load, g, sig1_current, rel1, performed, equivalent, saveMs, reloadMs, total.elapsed()); if (!cfg.dumpJsonPath.isEmpty()) { QString err; if (!writeJsonFile(cfg.dumpJsonPath, root, &err)) { QTextStream(stderr) << "ERROR: JSON dump failed: " << err << "\n"; return 2; } } if (!cfg.compareJsonPath.isEmpty()) { QJsonObject expected; QString err; if (!readJsonFile(cfg.compareJsonPath, &expected, &err)) { QTextStream(stderr) << "ERROR: JSON compare read failed: " << err << "\n"; return 2; } const int rc = compareGoldenV5Io(expected, root, cfg); if (rc != 0) return rc; } if (performed && !equivalent) { QTextStream(stderr) << "MISMATCH: roundtrip differs (schema v5, multirel)\n"; return 1; } return 0; } } // namespace cliSocNetV-3.7/src/tools/cli/kernels/kernel_io_roundtrip_v5.h000066400000000000000000000011051524402210300236730ustar00rootroot00000000000000#pragma once #include "tools/cli/cli_common.h" // cli::CliConfig class Graph; struct HeadlessLoadResult; namespace cli { // Schema v5 β€” IO roundtrip regression kernel (multirelational-aware). // - Always reports relations count + per-relation signatures. // - SAME-FORMAT roundtrip if exporter exists; otherwise performed=false. // - If roundtrip performed: strict equality across relations_bundle required. int runKernelIoRoundtripV5(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g); } // namespace cliSocNetV-3.7/src/tools/cli/kernels/kernel_matrix_v8.cpp000066400000000000000000000364231524402210300230330ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // Matrix golden coverage kernel (schema v8) for socnetv-cli. // See kernel_matrix_v8.h and roadmap_ws6_testing_ci_regression.md's WS6.7 section for // the design rationale: existing kernels only ever check downstream results (centrality // scores, distance values, clique counts), never a Matrix's actual contents, so a subtle // Matrix::item()/setItem() indexing bug could slip through untested. This kernel dumps raw // matrix contents for every Matrix-producing Graph operation instead. #include "kernel_matrix_v8.h" #include "graph.h" #include "tools/cli/cli_common.h" #include "tools/headless_graph_loader.h" #include #include #include #include #include #include namespace cli { // Networks above this size skip the total-walks (XSM) category: computing it (summing // matrix powers up to N-1) measured ~9.2 min (553,940 ms) at N=500 on // Benchmark_BA_Directed_N500_m3 (MacBook Pro M5, 24GB RAM, unoptimized/default build // type - not a Release number, and not tied to this specific machine either; the // point is minutes, not the exact figure). Clique co-membership (CLQM) needs no such // gate - measured 6 ms on the same run - so it's always computed. static const int kTotalWalksSkipThreshold = 50; // Networks above this size are dumped as a compact summary (row/col sums, trace, a // handful of sampled cells) instead of the full N*N grid - a full-grid JSON dump is // impractical past a few dozen nodes (WS5 A2.0 measured ~461 MB for one Matrix at // N=7343). static const int kFullGridSizeLimit = 20; // ---- per-matrix dump/compare ---- // Serializes one Matrix's contents: the full N*N grid if fullGrid is true, otherwise a // compact summary (row/col sums, trace, five sampled cells). static QJsonObject dumpMatrixJson(Matrix &m, bool fullGrid) { QJsonObject o; const int rows = m.rows(); const int cols = m.cols(); o["dump_mode"] = fullGrid ? "full" : "summary"; o["rows"] = rows; o["cols"] = cols; if (fullGrid) { QJsonArray data; for (int r = 0; r < rows; ++r) { QJsonArray row; for (int c = 0; c < cols; ++c) row.append(d2s(m.item(r, c))); data.append(row); } o["data"] = data; return o; } QJsonArray rowSums, colSums; for (int r = 0; r < rows; ++r) { qreal sum = 0; for (int c = 0; c < cols; ++c) sum += m.item(r, c); rowSums.append(d2s(sum)); } for (int c = 0; c < cols; ++c) { qreal sum = 0; for (int r = 0; r < rows; ++r) sum += m.item(r, c); colSums.append(d2s(sum)); } o["row_sums"] = rowSums; o["col_sums"] = colSums; qreal trace = 0; const int diag = std::min(rows, cols); for (int i = 0; i < diag; ++i) trace += m.item(i, i); o["trace"] = d2s(trace); QJsonObject samples; auto sample = [&](int r, int c) { if (r < 0 || c < 0 || r >= rows || c >= cols) return; samples[QString("%1,%2").arg(r).arg(c)] = d2s(m.item(r, c)); }; sample(0, 0); sample(0, cols - 1); sample(rows - 1, 0); sample(rows - 1, cols - 1); sample(rows / 2, cols / 2); o["sample_cells"] = samples; return o; } // Compares two JSON arrays of d2s()-formatted numeric strings element-by-element, // with the same tolerance almostEqual() uses elsewhere in the CLI harness. static bool cmpNumStrArray(const QJsonArray &e, const QJsonArray &a, QTextStream &err, const QString &what) { if (e.size() != a.size()) { err << "MISMATCH " << what << ".size expected=" << e.size() << " got=" << a.size() << "\n"; return false; } bool ok = true; for (int i = 0; i < e.size(); ++i) { bool ok1 = false, ok2 = false; const double ev = e.at(i).toString().toDouble(&ok1); const double av = a.at(i).toString().toDouble(&ok2); if (!ok1 || !ok2 || !almostEqual(ev, av)) { err << "MISMATCH " << what << "[" << i << "] expected=" << e.at(i).toString() << " got=" << a.at(i).toString() << "\n"; ok = false; } } return ok; } // Compares one category's dumpMatrixJson() output against its baseline, dispatching to // either a full-grid or a summary-tier comparison depending on the baseline's dump_mode. static bool compareMatrixJson(const QJsonObject &e, const QJsonObject &a, QTextStream &err, const QString &what) { bool ok = true; ok &= cmpStr(e, a, "dump_mode", err); ok &= cmpInt(e, a, "rows", err); ok &= cmpInt(e, a, "cols", err); if (e.value("dump_mode").toString() == "full") { const QJsonArray eData = e.value("data").toArray(); const QJsonArray aData = a.value("data").toArray(); if (eData.size() != aData.size()) { err << "MISMATCH " << what << ".data.rows expected=" << eData.size() << " got=" << aData.size() << "\n"; return false; } for (int r = 0; r < eData.size(); ++r) ok &= cmpNumStrArray(eData.at(r).toArray(), aData.at(r).toArray(), err, QString("%1.data[%2]").arg(what).arg(r)); return ok; } ok &= cmpNumStrArray(e.value("row_sums").toArray(), a.value("row_sums").toArray(), err, what + ".row_sums"); ok &= cmpNumStrArray(e.value("col_sums").toArray(), a.value("col_sums").toArray(), err, what + ".col_sums"); ok &= cmpNumStrTol(e, a, "trace", err); const QJsonObject eSamples = e.value("sample_cells").toObject(); const QJsonObject aSamples = a.value("sample_cells").toObject(); const QStringList keys = eSamples.keys(); if (keys.size() != aSamples.keys().size()) { err << "MISMATCH " << what << ".sample_cells.size expected=" << keys.size() << " got=" << aSamples.keys().size() << "\n"; ok = false; } for (const QString &k : keys) { if (!aSamples.contains(k)) { err << "MISMATCH " << what << ".sample_cells missing key=" << k << "\n"; ok = false; continue; } ok &= cmpNumStrTol(eSamples, aSamples, k, err); } return ok; } // ---- schema v8 builder ---- // Assembles the full schema-v8 JSON document: dataset/counts/graph/run metadata plus the // already-built "matrices" object (one entry per category, see runKernelMatrixV8()). static QJsonObject buildGoldenJsonV8( const QString &inputPath, int fileFormat, const HeadlessLoadResult &load, Graph &g, bool considerWeights, bool inverseWeights, bool dropIsolates, const QJsonObject &matrices) { QJsonObject root; root["schema_version"] = 8; root["kernel"] = "matrix"; QJsonObject dataset; dataset["path"] = inputPath; dataset["name"] = QFileInfo(inputPath).fileName(); dataset["filetype"] = fileFormat; root["dataset"] = dataset; const int ties_graph = load.tiesGraph; const int links_sna = g.isDirected() ? ties_graph : (2 * ties_graph); QJsonObject counts; counts["nodes"] = load.totalNodes; counts["links_sna"] = links_sna; counts["ties_graph"] = ties_graph; root["counts"] = counts; QJsonObject graph; graph["directed"] = g.isDirected(); graph["weighted"] = g.isWeighted(); root["graph"] = graph; QJsonObject run; run["considerWeights"] = considerWeights; run["inverseWeights"] = inverseWeights; run["dropIsolates"] = dropIsolates; root["run"] = run; root["matrices"] = matrices; QJsonObject loadReport; loadReport["ok"] = load.ok; loadReport["fileType_signal"] = load.fileType; loadReport["load_ms"] = static_cast(load.elapsedTime); loadReport["load_msg"] = load.message; loadReport["net_name"] = load.netName; root["load_report"] = loadReport; return root; } // ---- schema v8 comparator ---- // Compares an actual run's schema-v8 JSON against a committed baseline. Returns 0 on // match, 1 on a data mismatch, 2 on a structural error (unsupported schema, baseline // missing a category it must have). total_walks is the only optional category, gated // by kTotalWalksSkipThreshold. static int compareGoldenV8(const QJsonObject &expected, const QJsonObject &actual) { QTextStream err(stderr); if (expected.value("schema_version").toInt() != 8 || actual.value("schema_version").toInt() != 8) { err << "ERROR: schema_version mismatch or unsupported\n"; return 2; } bool ok = true; ok &= cmpInt(expected.value("dataset").toObject(), actual.value("dataset").toObject(), "filetype", err); ok &= cmpStr(expected.value("dataset").toObject(), actual.value("dataset").toObject(), "name", err); ok &= cmpInt(expected.value("counts").toObject(), actual.value("counts").toObject(), "nodes", err); ok &= cmpBool(expected.value("graph").toObject(), actual.value("graph").toObject(), "directed", err); ok &= cmpBool(expected.value("graph").toObject(), actual.value("graph").toObject(), "weighted", err); const QJsonObject eM = expected.value("matrices").toObject(); const QJsonObject aM = actual.value("matrices").toObject(); static const QStringList kAlwaysPresent = { "adjacency", "adjacency_inverse", "distances", "similarity", "reachability", "walks", "clique_comembership" }; for (const QString &cat : kAlwaysPresent) { if (!eM.contains(cat)) { err << "ERROR: expected baseline missing category=" << cat << "\n"; return 2; } if (!aM.contains(cat)) { err << "MISMATCH missing actual category=" << cat << "\n"; ok = false; continue; } ok &= compareMatrixJson(eM.value(cat).toObject(), aM.value(cat).toObject(), err, cat); } ok &= cmpBool(eM.value("adjacency_inverse").toObject(), aM.value("adjacency_inverse").toObject(), "invertible", err); ok &= cmpInt(eM.value("walks").toObject(), aM.value("walks").toObject(), "length", err); ok &= cmpStr(eM.value("similarity").toObject(), aM.value("similarity").toObject(), "metric", err); // total_walks only exists for small fixtures - see kTotalWalksSkipThreshold. for (const QString &cat : {QStringLiteral("total_walks")}) { const bool eHas = eM.contains(cat); const bool aHas = aM.contains(cat); if (eHas != aHas) { err << "MISMATCH category presence for " << cat << " expected_present=" << eHas << " actual_present=" << aHas << "\n"; ok = false; continue; } if (eHas) ok &= compareMatrixJson(eM.value(cat).toObject(), aM.value(cat).toObject(), err, cat); } if (!ok) return 1; err << "OK: baseline match\n"; return 0; } // ---- exported runner ---- // Builds every Matrix-producing category (adjacency, inverse, distances, similarity, // reachability, walks, total walks, clique co-membership) in turn, dumping each one's // contents right after it's constructed, then writes/compares the resulting JSON per // --dump-json/--compare-json. int runKernelMatrixV8(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g) { if (cfg.computeCentralities) { QTextStream(stderr) << "ERROR: --centralities is not applicable to --kernel matrix\n"; return 2; } if (cfg.benchRuns > 0) { QTextStream(stderr) << "ERROR: --bench is only supported with --kernel distance\n"; return 2; } g.resetDistanceCentralityCacheFlags(); const int n = g.vertices(); const bool fullGrid = (n <= kFullGridSizeLimit); const bool includeTotalWalks = (n <= kTotalWalksSkipThreshold); QElapsedTimer t; t.start(); QJsonObject matrices; // Adjacency - dumped immediately, before createMatrixAdjacencyInverse() runs, since // that call internally rebuilds AM with dropIsolates forced true (see // graph_matrix_adjacency.cpp) - capturing the JSON right after each construction // call, rather than re-reading Graph state at the very end, avoids depending on // later steps' internal side effects on shared fields like AM. g.createMatrixAdjacency(); matrices["adjacency"] = dumpMatrixJson(g.matrixAdjacency(), fullGrid); const bool invertible = g.createMatrixAdjacencyInverse("lu"); QJsonObject inv = dumpMatrixJson(g.matrixAdjacencyInverse(), fullGrid); inv["invertible"] = invertible; matrices["adjacency_inverse"] = inv; g.graphMatrixDistanceGeodesicCreate(cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates); matrices["distances"] = dumpMatrixJson(g.matrixDistances(), fullGrid); // Similarity needs a fresh adjacency input - createMatrixAdjacencyInverse() already // overwrote AM with its own dropIsolates=true policy, so rebuild once more here. g.createMatrixAdjacency(); Matrix similarity; g.createMatrixSimilarityMatching(g.matrixAdjacency(), similarity, METRIC_SIMPLE_MATCHING, "Rows", false, false); QJsonObject sim = dumpMatrixJson(similarity, fullGrid); sim["metric"] = "simple_matching"; matrices["similarity"] = sim; g.createMatrixReachability(); matrices["reachability"] = dumpMatrixJson(g.matrixReachability(), fullGrid); const int walksN = g.vertices(); g.graphWalksMatrixCreate(walksN, /*length=*/2, /*dropIsolates=*/false, /*considerWeights=*/false, /*inverseWeights=*/false, /*symmetrize=*/false); QJsonObject walks = dumpMatrixJson(g.matrixWalks(), fullGrid); walks["length"] = 2; matrices["walks"] = walks; if (includeTotalWalks) { g.graphWalksMatrixCreate(walksN, /*length=*/0, false, false, false, false); matrices["total_walks"] = dumpMatrixJson(g.matrixTotalWalks(), fullGrid); } // Unlike total walks, clique co-membership stays cheap at scale (Bron-Kerbosch on a // sparse graph) - see the kTotalWalksSkipThreshold comment above for the measurement // that justified computing this one unconditionally. g.graphCliques(); matrices["clique_comembership"] = dumpMatrixJson(g.matrixCliqueCoMembership(), fullGrid); const qint64 computeMs = t.elapsed(); printKV("COMPUTE_MS", computeMs); printKV("MATRIX_N", n); printKV("MATRIX_FULL_GRID", fullGrid ? 1 : 0); printKV("MATRIX_TOTAL_WALKS_INCLUDED", includeTotalWalks ? 1 : 0); const QJsonObject actual = buildGoldenJsonV8( cfg.inputPath, cfg.fileFormat, load, g, cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates, matrices); if (!cfg.dumpJsonPath.isEmpty()) { QString err; if (!writeJsonFile(cfg.dumpJsonPath, actual, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } QTextStream(stderr) << "WROTE_JSON=" << cfg.dumpJsonPath << "\n"; } if (!cfg.compareJsonPath.isEmpty()) { QJsonObject expected; QString err; if (!readJsonFile(cfg.compareJsonPath, &expected, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } return compareGoldenV8(expected, actual); } return 0; } } // namespace cli SocNetV-3.7/src/tools/cli/kernels/kernel_matrix_v8.h000066400000000000000000000012331524402210300224670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // Matrix golden coverage kernel (schema v8) for socnetv-cli. // Dumps raw contents of every Matrix-producing Graph operation (adjacency, inverse, // distances, similarity, reachability, walks, clique co-membership) for direct // regression coverage of Matrix::item()/setItem() indexing - see WS6.7. #pragma once #include "tools/cli/cli_common.h" // for cli::CliConfig class Graph; struct HeadlessLoadResult; namespace cli { int runKernelMatrixV8(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g); } // namespace cli SocNetV-3.7/src/tools/cli/kernels/kernel_prominence_v4.cpp000066400000000000000000000375351524402210300236670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // Prominence kernel (schema v4) for socnetv-cli. // Covers ALL centrality + prestige indices stored on GraphVertex. #include "kernel_prominence_v4.h" #include "graph.h" #include "graphvertex.h" #include "tools/cli/cli_common.h" #include "tools/headless_graph_loader.h" #include #include #include #include #include #include #include namespace cli { // ------------------------------ // Per-node builder // ------------------------------ static QJsonArray buildPerNodeArrayV4(Graph &g, bool katzEnabled, bool bonacichEnabled) { QJsonArray arr; const QList verts = g.verticesList(); // deterministic order for (int v : verts) { GraphVertex *gv = g.vertexPtr(v); if (!gv) continue; QJsonObject o; o["id"] = v; o["label"] = gv->label(); // ---- Katz (optional - only computed/valid when katzEnabled) ---- if (katzEnabled) { o["KC"] = d2s(gv->KC()); o["SKC"] = d2s(gv->SKC()); } // ---- Bonacich (optional - only computed/valid when bonacichEnabled) ---- if (bonacichEnabled) { o["BPC"] = d2s(gv->BPC()); o["SBPC"] = d2s(gv->SBPC()); } // ---- Centrality ---- o["DC"] = d2s(gv->DC()); o["SDC"] = d2s(gv->SDC()); o["CC"] = d2s(gv->CC()); o["SCC"] = d2s(gv->SCC()); o["BC"] = d2s(gv->BC()); o["SBC"] = d2s(gv->SBC()); o["SC"] = d2s(gv->SC()); o["SSC"] = d2s(gv->SSC()); o["PC"] = d2s(gv->PC()); o["SPC"] = d2s(gv->SPC()); o["IC"] = d2s(gv->IC()); o["SIC"] = d2s(gv->SIC()); o["EVC"] = d2s(gv->EVC()); o["SEVC"] = d2s(gv->SEVC()); o["IRCC"] = d2s(gv->IRCC()); o["SIRCC"] = d2s(gv->SIRCC()); const qreal ecc = gv->eccentricity(); o["eccentricity"] = d2s(ecc); o["eccentricity_inf"] = (ecc >= 2147483647.0); // ---- Prestige ---- o["DP"] = d2s(gv->DP()); o["SDP"] = d2s(gv->SDP()); o["PP"] = d2s(gv->PP()); o["SPP"] = d2s(gv->SPP()); o["PRP"] = d2s(gv->PRP()); o["SPRP"] = d2s(gv->SPRP()); arr.append(o); } return arr; } // ------------------------------ // JSON builder // ------------------------------ static QJsonObject buildGoldenJsonV4( const QString &inputPath, int fileFormat, const HeadlessLoadResult &load, Graph &g, const CliConfig &cfg) { QJsonObject root; root["schema_version"] = 4; root["kernel"] = "prominence"; QJsonObject dataset; dataset["path"] = inputPath; dataset["name"] = QFileInfo(inputPath).fileName(); dataset["filetype"] = fileFormat; root["dataset"] = dataset; const bool katzEnabled = (cfg.katzAlpha >= 0); const bool bonacichEnabled = (cfg.bonacichAlpha >= 0); QJsonObject run; run["considerWeights"] = cfg.considerWeights; run["inverseWeights"] = cfg.inverseWeights; run["dropIsolates"] = cfg.dropIsolates; run["katzEnabled"] = katzEnabled; if (katzEnabled) run["katzAlpha"] = d2s(cfg.katzAlpha); run["bonacichEnabled"] = bonacichEnabled; if (bonacichEnabled) { run["bonacichAlpha"] = d2s(cfg.bonacichAlpha); run["bonacichBeta"] = d2s(cfg.bonacichBeta); } root["run"] = run; const int ties_graph = load.tiesGraph; // canonical, already correct // links_sna is the SNA convention: undirected ties are counted as 2 arcs. const int links_sna = g.isDirected() ? ties_graph : (2 * ties_graph); QJsonObject counts; counts["nodes"] = load.totalNodes; counts["links_sna"] = links_sna; counts["ties_graph"] = ties_graph; root["counts"] = counts; QJsonObject graph; graph["directed"] = g.isDirected(); graph["weighted"] = g.isWeighted(); root["graph"] = graph; // Metrics (add density so golden JSONs carry it) QJsonObject metrics; metrics["density"] = d2s(g.graphDensity()); root["metrics"] = metrics; root["per_node"] = buildPerNodeArrayV4(g, katzEnabled, bonacichEnabled); QJsonObject loadReport; loadReport["ok"] = load.ok; loadReport["fileType_signal"] = load.fileType; loadReport["load_ms"] = static_cast(load.elapsedTime); loadReport["load_msg"] = load.message; loadReport["net_name"] = load.netName; root["load_report"] = loadReport; return root; } // ------------------------------ // Per-node comparator // ------------------------------ // ---- schema v4 compare ---- static bool cmpPerNodeArrayV4(const QJsonArray &eArr, const QJsonArray &aArr, bool katzEnabled, bool bonacichEnabled, QTextStream &err) { if (eArr.size() != aArr.size()) { err << "MISMATCH per_node.size expected=" << eArr.size() << " got=" << aArr.size() << "\n"; return false; } bool ok = true; auto cmpNodeFieldStrictStr = [&](const QJsonObject &e, const QJsonObject &a, const QString &k, int id) { const QString ev = e.value(k).toString(); const QString av = a.value(k).toString(); if (ev != av) { err << "MISMATCH per_node id=" << id << " field=" << k << " expected=" << ev << " got=" << av << "\n"; ok = false; } }; auto cmpNodeFieldNumStrTol = [&](const QJsonObject &e, const QJsonObject &a, const QString &k, int id, double tol) { const QString es = e.value(k).toString(); const QString as = a.value(k).toString(); bool ok1 = false, ok2 = false; const double ev = es.toDouble(&ok1); const double av = as.toDouble(&ok2); if (!ok1 || !ok2) { err << "MISMATCH per_node id=" << id << " field=" << k << " non-numeric expected=" << es << " got=" << as << "\n"; ok = false; return; } if (!almostEqual(ev, av, tol)) { err << "MISMATCH per_node id=" << id << " field=" << k << " expected=" << es << " got=" << as << " (diff=" << d2s(std::abs(ev - av)) << ")\n"; ok = false; } }; auto cmpNodeFieldInt = [&](const QJsonObject &e, const QJsonObject &a, const QString &k, int id) { const int ev = e.value(k).toInt(); const int av = a.value(k).toInt(); if (ev != av) { err << "MISMATCH per_node id=" << id << " field=" << k << " expected=" << ev << " got=" << av << "\n"; ok = false; } }; auto cmpNodeFieldBool = [&](const QJsonObject &e, const QJsonObject &a, const QString &k, int id) { const bool ev = e.value(k).toBool(); const bool av = a.value(k).toBool(); if (ev != av) { err << "MISMATCH per_node id=" << id << " field=" << k << " expected=" << (ev ? 1 : 0) << " got=" << (av ? 1 : 0) << "\n"; ok = false; } }; // Same tolerance policy as v1 unless you want tighter/looser: constexpr double TOL = 1e-15; for (int i = 0; i < eArr.size(); ++i) { const QJsonObject e = eArr.at(i).toObject(); const QJsonObject a = aArr.at(i).toObject(); const int eid = e.value("id").toInt(); const int aid = a.value("id").toInt(); if (eid != aid) { err << "MISMATCH per_node ordering at index=" << i << " expected_id=" << eid << " got_id=" << aid << "\n"; ok = false; continue; } cmpNodeFieldInt(e, a, "id", eid); cmpNodeFieldStrictStr(e, a, "label", eid); // Prominence v4 fields (centralities + prestige + eigenvector + pagerank + IRCC) const QStringList numFields = { "DC", "SDC", "CC", "SCC", "IRCC", "SIRCC", "BC", "SBC", "SC", "SSC", "PC", "SPC", "IC", "SIC", "EVC", "SEVC", "DP", "SDP", "PP", "SPP", "PRP", "SPRP", "eccentricity"}; // KC/SKC and BPC/SBPC are only populated (on both sides) when this run enabled the // corresponding measure - see the "katzEnabled"/"bonacichEnabled" run-config checks // in compareGoldenV4() below, which catch an enabled/disabled mismatch between // baseline and current run before we get here. QStringList allFields = numFields; if (katzEnabled) allFields << "KC" << "SKC"; if (bonacichEnabled) allFields << "BPC" << "SBPC"; for (const QString &f : allFields) cmpNodeFieldNumStrTol(e, a, f, eid, TOL); // Sentinel flag must be exact cmpNodeFieldBool(e, a, "eccentricity_inf", eid); } return ok; } static int compareGoldenV4(const QJsonObject &expected, const QJsonObject &actual) { QTextStream err(stderr); if (expected.value("schema_version").toInt() != 4 || actual.value("schema_version").toInt() != 4) { err << "ERROR: schema_version mismatch or unsupported\n"; return 2; } bool ok = true; ok &= cmpStr(expected, actual, "kernel", err); // should be "prominence" const QJsonObject eDataset = expected.value("dataset").toObject(); const QJsonObject aDataset = actual.value("dataset").toObject(); ok &= cmpInt(eDataset, aDataset, "filetype", err); ok &= cmpStr(eDataset, aDataset, "name", err); const QJsonObject eRun = expected.value("run").toObject(); const QJsonObject aRun = actual.value("run").toObject(); ok &= cmpBool(eRun, aRun, "considerWeights", err); ok &= cmpBool(eRun, aRun, "inverseWeights", err); ok &= cmpBool(eRun, aRun, "dropIsolates", err); ok &= cmpBool(eRun, aRun, "katzEnabled", err); const bool katzEnabled = aRun.value("katzEnabled").toBool(); ok &= cmpBool(eRun, aRun, "bonacichEnabled", err); const bool bonacichEnabled = aRun.value("bonacichEnabled").toBool(); const QJsonObject eCounts = expected.value("counts").toObject(); const QJsonObject aCounts = actual.value("counts").toObject(); ok &= cmpInt(eCounts, aCounts, "nodes", err); ok &= cmpInt(eCounts, aCounts, "links_sna", err); ok &= cmpInt(eCounts, aCounts, "ties_graph", err); const QJsonObject eGraph = expected.value("graph").toObject(); const QJsonObject aGraph = actual.value("graph").toObject(); ok &= cmpBool(eGraph, aGraph, "directed", err); ok &= cmpBool(eGraph, aGraph, "weighted", err); // Per-node (always present in v4) const QJsonArray ePN = expected.value("per_node").toArray(); const QJsonArray aPN = actual.value("per_node").toArray(); ok &= cmpPerNodeArrayV4(ePN, aPN, katzEnabled, bonacichEnabled, err); if (!ok) return 1; err << "OK: baseline match\n"; return 0; } // ------------------------------ // Runner // ------------------------------ int runKernelProminenceV4(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g) { auto run_compute_once_ms = [&]() -> qint64 { // Force recomputation g.resetDistanceCentralityCacheFlags(); QElapsedTimer t; t.start(); // 1. Geodesic-based centralities g.graphDistancesGeodesic(true, cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates); // 2. Standalone centralities g.centralityDegree(cfg.considerWeights, cfg.dropIsolates); g.centralityInformation(cfg.considerWeights, cfg.inverseWeights); g.centralityEigenvector(cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates); g.centralityClosenessIR(cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates); if (cfg.katzAlpha >= 0) g.centralityKatz(cfg.katzAlpha, cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates); if (cfg.bonacichAlpha >= 0) g.centralityBonacich(cfg.bonacichAlpha, cfg.bonacichBeta, cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates); // 3. Prestige g.prestigeDegree(cfg.considerWeights, cfg.dropIsolates); g.prestigeProximity(cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates); g.prestigePageRank(cfg.dropIsolates); return t.elapsed(); }; if (cfg.benchRuns > 0) { // warmup (not measured) (void)run_compute_once_ms(); std::vector ms; ms.reserve(static_cast(cfg.benchRuns)); for (int r = 0; r < cfg.benchRuns; ++r) ms.push_back(run_compute_once_ms()); std::sort(ms.begin(), ms.end()); const qint64 minMs = ms.front(); const qint64 maxMs = ms.back(); const double meanMs = std::accumulate(ms.begin(), ms.end(), 0.0) / static_cast(ms.size()); const qint64 medianMs = (ms.size() % 2 == 1) ? ms[ms.size() / 2] : (ms[ms.size() / 2 - 1] + ms[ms.size() / 2]) / 2; printKV("COMPUTE_RUNS", cfg.benchRuns); printKV("COMPUTE_MS_MIN", minMs); printKV("COMPUTE_MS_MEDIAN", medianMs); printKV("COMPUTE_MS_MEAN", meanMs); printKV("COMPUTE_MS_MAX", maxMs); return 0; } const qint64 computeMs = run_compute_once_ms(); printKV("COMPUTE_MS", computeMs); const QJsonObject actual = buildGoldenJsonV4(cfg.inputPath, cfg.fileFormat, load, g, cfg); if (!cfg.dumpJsonPath.isEmpty()) { QString err; if (!writeJsonFile(cfg.dumpJsonPath, actual, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } QTextStream(stderr) << "WROTE_JSON=" << cfg.dumpJsonPath << "\n"; } if (!cfg.compareJsonPath.isEmpty()) { QJsonObject expected; QString err; if (!readJsonFile(cfg.compareJsonPath, &expected, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } return compareGoldenV4(expected, actual); } return 0; } } // namespace cliSocNetV-3.7/src/tools/cli/kernels/kernel_prominence_v4.h000066400000000000000000000010221524402210300233120ustar00rootroot00000000000000#pragma once #include #include "tools/cli/cli_common.h" // for cli::CliConfig class Graph; struct HeadlessLoadResult; namespace cli { // Runs schema v4 prominence kernel. // Always computes prominence indices (ignores cfg.computeCentralities toggle). // Optional dump/compare JSON. // Return codes: // 0 = ok // 1 = mismatch // 2 = usage/error int runKernelProminenceV4(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g); } // namespace cliSocNetV-3.7/src/tools/cli/kernels/kernel_reachability_v2.cpp000066400000000000000000000202751524402210300241570ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // Reachability kernel (schema v2) for socnetv-cli. // Extracted mechanically from former monolithic socnetv_cli.cpp. // No behavior changes intended. #include "kernel_reachability_v2.h" #include "graph.h" #include "graphvertex.h" #include "tools/cli/cli_common.h" #include "tools/headless_graph_loader.h" #include #include #include #include #include namespace cli { // ---- schema v2 builder ---- static QJsonObject buildGoldenJsonV2Reachability( const QString &inputPath, int fileFormat, const HeadlessLoadResult &load, Graph &g, bool considerWeights, bool inverseWeights, bool dropIsolates, const QList &order, const QJsonArray &matrix, int onesCount) { QJsonObject root; root["schema_version"] = 2; root["kernel"] = "reachability"; QJsonObject dataset; dataset["path"] = inputPath; dataset["name"] = QFileInfo(inputPath).fileName(); dataset["filetype"] = fileFormat; root["dataset"] = dataset; QJsonObject run; run["considerWeights"] = considerWeights; run["inverseWeights"] = inverseWeights; run["dropIsolates"] = dropIsolates; run["operation"] = "reachability_matrix"; root["run"] = run; const int ties_graph = load.tiesGraph; // canonical, already correct const int links_sna = g.isDirected() ? ties_graph : (2 * ties_graph); QJsonObject counts; counts["nodes"] = load.totalNodes; counts["links_sna"] = links_sna; counts["ties_graph"] = ties_graph; root["counts"] = counts; QJsonObject graph; graph["directed"] = g.isDirected(); graph["weighted"] = g.isWeighted(); root["graph"] = graph; // Metrics (add density so golden JSONs carry it) QJsonObject metrics; metrics["density"] = d2s(g.graphDensity()); root["metrics"] = metrics; QJsonObject reach; QJsonArray ord; for (int id : order) ord.append(id); reach["nodes"] = ord; reach["reachable_pairs"] = onesCount; reach["matrix"] = matrix; const int n = order.size(); const double density = (n > 0) ? double(onesCount) / double(n * n) : 0.0; // Reachability density is over n*n pairs (includes diagonal), distinct from graph density. reach["reachable_density"] = d2s(density); // as string root["reachability"] = reach; QJsonObject loadReport; loadReport["ok"] = load.ok; loadReport["fileType_signal"] = load.fileType; loadReport["load_ms"] = static_cast(load.elapsedTime); loadReport["load_msg"] = load.message; loadReport["net_name"] = load.netName; root["load_report"] = loadReport; return root; } // ---- schema v2 comparator ---- static int compareGoldenV2Reachability(const QJsonObject &expected, const QJsonObject &actual) { QTextStream err(stderr); if (expected.value("schema_version").toInt() != 2 || actual.value("schema_version").toInt() != 2) { err << "ERROR: schema_version mismatch or unsupported\n"; return 2; } if (expected.value("kernel").toString() != "reachability" || actual.value("kernel").toString() != "reachability") { err << "ERROR: kernel mismatch or unsupported\n"; return 2; } bool ok = true; ok &= cmpInt(expected.value("dataset").toObject(), actual.value("dataset").toObject(), "filetype", err); ok &= cmpStr(expected.value("dataset").toObject(), actual.value("dataset").toObject(), "name", err); const QJsonObject eRun = expected.value("run").toObject(); const QJsonObject aRun = actual.value("run").toObject(); ok &= cmpBool(eRun, aRun, "considerWeights", err); ok &= cmpBool(eRun, aRun, "inverseWeights", err); ok &= cmpBool(eRun, aRun, "dropIsolates", err); ok &= cmpStr(eRun, aRun, "operation", err); ok &= cmpInt(expected.value("counts").toObject(), actual.value("counts").toObject(), "nodes", err); ok &= cmpInt(expected.value("counts").toObject(), actual.value("counts").toObject(), "links_sna", err); ok &= cmpInt(expected.value("counts").toObject(), actual.value("counts").toObject(), "ties_graph", err); ok &= cmpBool(expected.value("graph").toObject(), actual.value("graph").toObject(), "directed", err); ok &= cmpBool(expected.value("graph").toObject(), actual.value("graph").toObject(), "weighted", err); const QJsonObject eR = expected.value("reachability").toObject(); const QJsonObject aR = actual.value("reachability").toObject(); ok &= cmpInt(eR, aR, "reachable_pairs", err); const QJsonArray eOrder = eR.value("nodes").toArray(); const QJsonArray aOrder = aR.value("nodes").toArray(); ok &= cmpIntArray(eOrder, aOrder, err, "reachability.nodes"); // NOTE: this preserves your current behavior: exact compare via cmpNumStrTol with rel=0 abs=0 ok &= cmpNumStrTol(eR, aR, "reachable_density", err, 0.0, 0.0); const QJsonArray eM = eR.value("matrix").toArray(); const QJsonArray aM = aR.value("matrix").toArray(); if (eM.size() != aM.size()) { err << "MISMATCH reachability.matrix.rows expected=" << eM.size() << " got=" << aM.size() << "\n"; ok = false; } else { for (int r = 0; r < eM.size(); ++r) { const QJsonArray eRow = eM.at(r).toArray(); const QJsonArray aRow = aM.at(r).toArray(); if (!cmpIntArray(eRow, aRow, err, QString("reachability.matrix[%1]").arg(r))) ok = false; } } if (!ok) return 1; err << "OK: baseline match\n"; return 0; } // ---- exported runner ---- int runKernelReachabilityV2(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g) { if (cfg.computeCentralities) { QTextStream(stderr) << "ERROR: --centralities is not applicable to --kernel reachability\n"; return 2; } if (cfg.benchRuns > 0) { QTextStream(stderr) << "ERROR: --bench is only supported with --kernel distance\n"; return 2; } // Compute geodesic distances once (centralities must be false here) g.resetDistanceCentralityCacheFlags(); QElapsedTimer t; t.start(); g.graphDistancesGeodesic(false, cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates); const qint64 computeMs = t.elapsed(); // Deterministic vertex order QList order = g.verticesList(); std::sort(order.begin(), order.end()); // Build reachability matrix (0/1) using the same semantics as graphReachable() QJsonArray matrix; int ones = 0; for (int src : order) { GraphVertex *gv = g.vertexPtr(src); if (!gv) { QTextStream(stderr) << "ERROR: null vertex for id=" << src << "\n"; return 2; } QJsonArray row; for (int dst : order) { const bool reachable = (g.apspDistance(src, dst) != RAND_MAX); row.append(reachable ? 1 : 0); if (reachable) ++ones; } matrix.append(row); } printKV("COMPUTE_MS", computeMs); printKV("REACH_NODES", order.size()); printKV("REACHABLE_PAIRS", ones); const int n = order.size(); const double density = (n > 0) ? (double)ones / (double)(n * n) : 0.0; printKV("REACHABLE_DENSITY", QString::number(density, 'g', 17)); const QJsonObject actual = buildGoldenJsonV2Reachability( cfg.inputPath, cfg.fileFormat, load, g, cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates, order, matrix, ones); if (!cfg.dumpJsonPath.isEmpty()) { QString err; if (!writeJsonFile(cfg.dumpJsonPath, actual, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } QTextStream(stderr) << "WROTE_JSON=" << cfg.dumpJsonPath << "\n"; } if (!cfg.compareJsonPath.isEmpty()) { QJsonObject expected; QString err; if (!readJsonFile(cfg.compareJsonPath, &expected, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } return compareGoldenV2Reachability(expected, actual); } return 0; } } // namespace cli SocNetV-3.7/src/tools/cli/kernels/kernel_reachability_v2.h000066400000000000000000000010531524402210300236150ustar00rootroot00000000000000#pragma once #include "tools/cli/cli_common.h" // for cli::CliConfig class Graph; struct HeadlessLoadResult; namespace cli { // Runs schema v2 reachability kernel. // - Requires cfg.computeCentralities == false // - Forbids cfg.benchRuns > 0 (bench is distance-only) // - Uses g.graphDistancesGeodesic(false, ...) then builds 0/1 reachability matrix // - Optionally dump/compare JSON int runKernelReachabilityV2(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g); } // namespace cli SocNetV-3.7/src/tools/cli/kernels/kernel_vertex_connectivity_v9.cpp000066400000000000000000000147221524402210300256410ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // Vertex connectivity kernel (schema v9) for socnetv-cli. // Computes local (Menger's-theorem/max-flow) or global (pairwise-minimum) vertex connectivity via // Graph::graphNodeConnectivity() / Graph::graphConnectivity(), and emits a JSON report suitable // for golden-baseline regression testing. #include "kernel_vertex_connectivity_v9.h" #include "graph.h" #include "tools/headless_graph_loader.h" #include "tools/cli/cli_common.h" #include #include namespace cli { static QString nodeConnectivityStatusStr(Graph::NodeConnectivityStatus s) { switch (s) { case Graph::NodeConnectivityStatus::Ok: return "ok"; case Graph::NodeConnectivityStatus::Adjacent: return "adjacent"; case Graph::NodeConnectivityStatus::Invalid: return "invalid"; } return "invalid"; } static QJsonObject buildGoldenJsonV9( const QString &inputPath, int fileFormat, const HeadlessLoadResult &load, Graph &g, const QString &mode, const QString &connectivityTypeLabel, int connSource, int connTarget, const Graph::NodeConnectivityResult &localResult, int globalValue) { QJsonObject root; root["schema_version"] = 9; root["kernel"] = "vertex_connectivity"; QJsonObject dataset; dataset["path"] = inputPath; dataset["name"] = QFileInfo(inputPath).fileName(); dataset["filetype"] = fileFormat; root["dataset"] = dataset; const int ties_graph = load.tiesGraph; const int links_sna = g.isDirected() ? ties_graph : (2 * ties_graph); QJsonObject counts; counts["nodes"] = load.totalNodes; counts["links_sna"] = links_sna; counts["ties_graph"] = ties_graph; root["counts"] = counts; QJsonObject graph; graph["directed"] = g.isDirected(); graph["weighted"] = g.isWeighted(); root["graph"] = graph; root["mode"] = mode; root["connectivity_type"] = connectivityTypeLabel; if (mode == "local") { QJsonObject local; local["source"] = connSource; local["target"] = connTarget; local["status"] = nodeConnectivityStatusStr(localResult.status); if (localResult.status == Graph::NodeConnectivityStatus::Ok) local["value"] = localResult.value; root["local"] = local; } else { QJsonObject global; global["value"] = globalValue; root["global"] = global; } QJsonObject loadReport; loadReport["ok"] = load.ok; loadReport["fileType_signal"] = load.fileType; loadReport["load_ms"] = static_cast(load.elapsedTime); loadReport["load_msg"] = load.message; loadReport["net_name"] = load.netName; root["load_report"] = loadReport; return root; } static int compareGoldenV9(const QJsonObject &expected, const QJsonObject &actual) { QTextStream err(stderr); if (expected.value("schema_version").toInt() != 9 || actual.value("schema_version").toInt() != 9) { err << "ERROR: schema_version mismatch or unsupported\n"; return 2; } bool ok = true; ok &= cmpInt(expected.value("dataset").toObject(), actual.value("dataset").toObject(), "filetype", err); ok &= cmpStr(expected.value("dataset").toObject(), actual.value("dataset").toObject(), "name", err); ok &= cmpInt(expected.value("counts").toObject(), actual.value("counts").toObject(), "nodes", err); ok &= cmpInt(expected.value("counts").toObject(), actual.value("counts").toObject(), "ties_graph", err); ok &= cmpBool(expected.value("graph").toObject(), actual.value("graph").toObject(), "directed", err); ok &= cmpStr(expected, actual, "mode", err); ok &= cmpStr(expected, actual, "connectivity_type", err); const QString mode = actual.value("mode").toString(); if (mode == "local") { const QJsonObject eLocal = expected.value("local").toObject(); const QJsonObject aLocal = actual.value("local").toObject(); ok &= cmpInt(eLocal, aLocal, "source", err); ok &= cmpInt(eLocal, aLocal, "target", err); ok &= cmpStr(eLocal, aLocal, "status", err); if (aLocal.value("status").toString() == "ok") ok &= cmpInt(eLocal, aLocal, "value", err); } else { ok &= cmpInt(expected.value("global").toObject(), actual.value("global").toObject(), "value", err); } if (!ok) return 1; err << "OK: baseline match\n"; return 0; } int runKernelVertexConnectivityV9(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g) { const bool useStrong = g.isDirected() && (cfg.connectivityType == "strong"); const QString typeLabel = useStrong ? "strong" : (g.isDirected() ? "weak" : "undirected"); const bool respectDirection = useStrong; const QString mode = (cfg.connMode == "local") ? "local" : "global"; Graph::NodeConnectivityResult localResult; int globalValue = 0; if (mode == "local") { localResult = g.graphNodeConnectivity(cfg.connSource, cfg.connTarget, respectDirection); printKV("STATUS", nodeConnectivityStatusStr(localResult.status)); if (localResult.status == Graph::NodeConnectivityStatus::Ok) printKV("VALUE", localResult.value); } else { globalValue = g.graphConnectivity(respectDirection); printKV("VALUE", globalValue); } printKV("TYPE", typeLabel); const QJsonObject actual = buildGoldenJsonV9( cfg.inputPath, cfg.fileFormat, load, g, mode, typeLabel, cfg.connSource, cfg.connTarget, localResult, globalValue); if (!cfg.dumpJsonPath.isEmpty()) { QString err; if (!writeJsonFile(cfg.dumpJsonPath, actual, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } QTextStream(stderr) << "WROTE_JSON=" << cfg.dumpJsonPath << "\n"; } if (!cfg.compareJsonPath.isEmpty()) { QJsonObject expected; QString err; if (!readJsonFile(cfg.compareJsonPath, &expected, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } return compareGoldenV9(expected, actual); } return 0; } } // namespace cli SocNetV-3.7/src/tools/cli/kernels/kernel_vertex_connectivity_v9.h000066400000000000000000000011541524402210300253010ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // Vertex connectivity kernel (schema v9) for socnetv-cli. // Local (--conn-mode local --conn-source S --conn-target T) or global (--conn-mode global) // vertex connectivity via Graph::graphNodeConnectivity() / Graph::graphConnectivity(). #pragma once #include "tools/cli/cli_common.h" class Graph; struct HeadlessLoadResult; namespace cli { int runKernelVertexConnectivityV9(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g); } // namespace cli SocNetV-3.7/src/tools/cli/kernels/kernel_walks_v3.cpp000066400000000000000000000176451524402210300226500ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // Walks matrix kernel (schema v3) for socnetv-cli. // Exact-length K walks using Graph::walksBetween(). // No behavior changes intended elsewhere. #include "kernel_walks_v3.h" #include "tools/cli/cli_common.h" #include "graph.h" #include "tools/headless_graph_loader.h" #include #include #include #include #include namespace cli { // ---- schema v3 builder ---- static QJsonObject buildGoldenJsonV3WalksMatrix( const QString &inputPath, int fileFormat, const HeadlessLoadResult &load, Graph &g, bool considerWeights, bool inverseWeights, bool dropIsolates, int walksLength, const QList &order, const QJsonArray &matrix, const QString &totalWalksStr) { QJsonObject root; root["schema_version"] = 3; root["kernel"] = "walks_matrix"; QJsonObject dataset; dataset["path"] = inputPath; dataset["name"] = QFileInfo(inputPath).fileName(); dataset["filetype"] = fileFormat; root["dataset"] = dataset; QJsonObject run; run["considerWeights"] = considerWeights; run["inverseWeights"] = inverseWeights; run["dropIsolates"] = dropIsolates; run["operation"] = "walks_matrix_exact_length"; run["walks_length"] = walksLength; root["run"] = run; const int ties_graph = load.tiesGraph; // canonical, already correct const int links_sna = g.isDirected() ? ties_graph : (2 * ties_graph); QJsonObject counts; counts["nodes"] = load.totalNodes; counts["links_sna"] = links_sna; counts["ties_graph"] = ties_graph; root["counts"] = counts; QJsonObject graph; graph["directed"] = g.isDirected(); graph["weighted"] = g.isWeighted(); root["graph"] = graph; // Metrics (add density so golden JSONs carry it) QJsonObject metrics; // Graph density (relation-aware). Not to be confused with any kernel-specific densities. metrics["density"] = d2s(g.graphDensity()); root["metrics"] = metrics; QJsonObject walks; QJsonArray ord; for (int id : order) ord.append(id); walks["nodes"] = ord; walks["matrix"] = matrix; walks["total_walks"] = totalWalksStr; // integer as string root["walks"] = walks; QJsonObject loadReport; loadReport["ok"] = load.ok; loadReport["fileType_signal"] = load.fileType; loadReport["load_ms"] = static_cast(load.elapsedTime); loadReport["load_msg"] = load.message; loadReport["net_name"] = load.netName; root["load_report"] = loadReport; return root; } // ---- schema v3 comparator ---- static int compareGoldenV3WalksMatrix(const QJsonObject &expected, const QJsonObject &actual) { QTextStream err(stderr); if (expected.value("schema_version").toInt() != 3 || actual.value("schema_version").toInt() != 3) { err << "ERROR: schema_version mismatch or unsupported\n"; return 2; } if (expected.value("kernel").toString() != "walks_matrix" || actual.value("kernel").toString() != "walks_matrix") { err << "ERROR: kernel mismatch or unsupported\n"; return 2; } bool ok = true; ok &= cmpInt(expected.value("dataset").toObject(), actual.value("dataset").toObject(), "filetype", err); ok &= cmpStr(expected.value("dataset").toObject(), actual.value("dataset").toObject(), "name", err); const QJsonObject eRun = expected.value("run").toObject(); const QJsonObject aRun = actual.value("run").toObject(); ok &= cmpBool(eRun, aRun, "considerWeights", err); ok &= cmpBool(eRun, aRun, "inverseWeights", err); ok &= cmpBool(eRun, aRun, "dropIsolates", err); ok &= cmpStr(eRun, aRun, "operation", err); ok &= cmpInt(eRun, aRun, "walks_length", err); ok &= cmpInt(expected.value("counts").toObject(), actual.value("counts").toObject(), "nodes", err); ok &= cmpInt(expected.value("counts").toObject(), actual.value("counts").toObject(), "links_sna", err); ok &= cmpInt(expected.value("counts").toObject(), actual.value("counts").toObject(), "ties_graph", err); ok &= cmpBool(expected.value("graph").toObject(), actual.value("graph").toObject(), "directed", err); ok &= cmpBool(expected.value("graph").toObject(), actual.value("graph").toObject(), "weighted", err); const QJsonObject eW = expected.value("walks").toObject(); const QJsonObject aW = actual.value("walks").toObject(); ok &= cmpIntArray(eW.value("nodes").toArray(), aW.value("nodes").toArray(), err, "walks.nodes"); ok &= cmpStr(eW, aW, "total_walks", err); const QJsonArray eM = eW.value("matrix").toArray(); const QJsonArray aM = aW.value("matrix").toArray(); if (eM.size() != aM.size()) { err << "MISMATCH walks.matrix.rows expected=" << eM.size() << " got=" << aM.size() << "\n"; ok = false; } else { for (int r = 0; r < eM.size(); ++r) { const QJsonArray eRow = eM.at(r).toArray(); const QJsonArray aRow = aM.at(r).toArray(); if (!cmpStrArray(eRow, aRow, err, QString("walks.matrix[%1]").arg(r))) ok = false; } } if (!ok) return 1; err << "OK: baseline match\n"; return 0; } // ---- exported runner ---- int runKernelWalksV3(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g, int walksLength) { if (cfg.computeCentralities) { QTextStream(stderr) << "ERROR: --centralities is not applicable to --kernel walks_matrix\n"; return 2; } if (cfg.benchRuns > 0) { QTextStream(stderr) << "ERROR: --bench is only supported with --kernel distance\n"; return 2; } if (walksLength < 1) { QTextStream(stderr) << "ERROR: --kernel walks_matrix requires --walks-length K (K>=1)\n"; return 2; } // Deterministic vertex order QList order = g.verticesList(); std::sort(order.begin(), order.end()); // QTextStream(stderr) << "WALKS_ORDER="; // for (int id : order) // QTextStream(stderr) << id << ","; // QTextStream(stderr) << "\n"; QElapsedTimer t; t.start(); QJsonArray matrix; quint64 totalWalks = 0; for (int src : order) { QJsonArray row; for (int dst : order) { const int w = g.walksBetween(src, dst, walksLength); if (w < 0) { QTextStream(stderr) << "ERROR: walksBetween(" << src << "," << dst << "," << walksLength << ") returned " << w << "\n"; return 2; } totalWalks += static_cast(w); row.append(QString::number(w)); // integers as strings (deterministic) } matrix.append(row); } const qint64 computeMs = t.elapsed(); printKV("COMPUTE_MS", computeMs); printKV("WALKS_NODES", order.size()); printKV("WALKS_LENGTH", walksLength); printKV("WALKS_TOTAL", QString::number(totalWalks)); const QJsonObject actual = buildGoldenJsonV3WalksMatrix( cfg.inputPath, cfg.fileFormat, load, g, cfg.considerWeights, cfg.inverseWeights, cfg.dropIsolates, walksLength, order, matrix, QString::number(totalWalks)); if (!cfg.dumpJsonPath.isEmpty()) { QString err; if (!writeJsonFile(cfg.dumpJsonPath, actual, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } QTextStream(stderr) << "WROTE_JSON=" << cfg.dumpJsonPath << "\n"; } if (!cfg.compareJsonPath.isEmpty()) { QJsonObject expected; QString err; if (!readJsonFile(cfg.compareJsonPath, &expected, &err)) { QTextStream(stderr) << "ERROR: " << err << "\n"; return 2; } return compareGoldenV3WalksMatrix(expected, actual); } return 0; } } // namespace cli SocNetV-3.7/src/tools/cli/kernels/kernel_walks_v3.h000066400000000000000000000011531524402210300223000ustar00rootroot00000000000000#pragma once #include "tools/cli/cli_common.h" // for cli::CliConfig class Graph; struct HeadlessLoadResult; namespace cli { // Runs schema v3 walks_matrix kernel (exact length K). // - Requires cfg.computeCentralities == false // - Forbids cfg.benchRuns > 0 (bench is distance-only) // - Requires walksLength >= 1 // - Uses Graph::walksBetween(src,dst,K) to build NxN matrix of integer strings // - Optionally dump/compare JSON int runKernelWalksV3(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g, int walksLength); } // namespace cli SocNetV-3.7/src/tools/headless_graph_loader.cpp000066400000000000000000000127671524402210300216440ustar00rootroot00000000000000#include "headless_graph_loader.h" #include #include #include #include #include "../graph/io/graph_parse_sink_graph.h" #include "../graph.h" #include "../parser.h" /** * @brief Loads a graph from a given file, without any UI. * @returns a struct with load results (success, file type, node/link counts, etc.) */ HeadlessLoadResult loadGraphHeadless( Graph &graph, const QString &fileName, const QString &codecName, int fileFormat, const QString &delimiter, int sm_two_mode, bool sm_has_labels) { HeadlessLoadResult out; out.ok = false; out.fileType = -1; out.totalNodes = 0; out.tiesGraph = 0; out.elapsedTime = 0; qCDebug(lcGraphIO) << "[CLI] loadGraphHeadless() begin:" << fileName; // Same semantics as Graph::loadFile() graph.relationsClear(); // Local parser + local thread (no Graph::file_parser involvement) auto *parser = new Parser(); QThread parserThread; parser->moveToThread(&parserThread); SocNetV::IO::GraphParseSinkGraph sink(graph); parser->setParseSink(&sink); // We'll block until Graph emits signalGraphLoaded QEventLoop loop; bool done = false; // either one fires first (loaded OR finished), avoid double quit QObject::connect(&graph, &Graph::signalGraphLoaded, &loop, [&](const int &loadedFileType, const QString &loadedFileName, const QString &netName, const int &totalNodes, const int &tiesGraph, const int & /*density*/, const qint64 &elapsedTime, const QString &message) { if (done) return; done = true; out.fileType = loadedFileType; out.fileName = loadedFileName; out.netName = netName; out.totalNodes = totalNodes; // tiesGraph is emitted by Graph::graphFileLoaded() as edgesEnabled(): // logical ties count (E for undirected, A for directed). out.tiesGraph = tiesGraph; out.elapsedTime = elapsedTime; out.message = message; out.ok = (loadedFileType != FileType::UNRECOGNIZED); qCDebug(lcGraphIO) << "[CLI] signalGraphLoaded:" << "ok" << out.ok << "nodes" << out.totalNodes << "tiesGraph" << out.tiesGraph << "msg" << out.message; loop.quit(); }); // Also quit when parser finishes (belt-and-suspenders) QObject::connect(parser, &Parser::finished, &loop, [&](const QString &reason) { if (done) return; done = true; qCDebug(lcGraphIO) << "[CLI] Parser finished:" << reason; // If graph didn't emit signalGraphLoaded for some reason, still quit. if (out.fileType == -1) { out.ok = false; out.message = reason; } loop.quit(); }); parserThread.start(); // --- Headless defaults (visual-only, do not affect distances/centralities) --- const int initVertexSize = 10; const QString initVertexColor = "#ffffff"; const QString initVertexShape = "circle"; const QString initVertexNumberColor = "#000000"; const int initVertexNumberSize = 10; const QString initVertexLabelColor = "#000000"; const int initVertexLabelSize = 10; const QString initEdgeColor = "#000000"; const int canvasWidth = 700; const int canvasHeight = 600; // Queue Parser::load to run in parserThread *after* the event loop spins. QMetaObject::invokeMethod( parser, [parser, fileName, codecName, fileFormat, delimiter, sm_two_mode, sm_has_labels, initVertexSize, initVertexColor, initVertexShape, initVertexNumberColor, initVertexNumberSize, initVertexLabelColor, initVertexLabelSize, initEdgeColor, canvasWidth, canvasHeight]() { parser->load(fileName, codecName, initVertexSize, initVertexColor, initVertexShape, initVertexNumberColor, initVertexNumberSize, initVertexLabelColor, initVertexLabelSize, initEdgeColor, canvasWidth, canvasHeight, fileFormat, delimiter, sm_two_mode, sm_has_labels); }, Qt::QueuedConnection); qCDebug(lcGraphIO) << "[CLI] entering loop.exec()"; loop.exec(); qCDebug(lcGraphIO) << "[CLI] left loop.exec()"; parserThread.quit(); parserThread.wait(); delete parser; parser = nullptr; qCDebug(lcGraphIO) << "[CLI] loadGraphHeadless() end:" << out.ok; return out; } SocNetV-3.7/src/tools/headless_graph_loader.h000066400000000000000000000025561524402210300213040ustar00rootroot00000000000000#pragma once #include #include class Graph; /** * HeadlessLoadResult mirrors what Graph reports via signalGraphLoaded * after loading a dataset headlessly. * * Terminology (SocNetV canonical model, current relation only): * - tiesGraph: canonical tie count from the graph model, as returned by * Graph::edgesEnabled(). * For undirected graphs this is |E| (unique edges). * For directed graphs this is |A| (arcs). * * Important: * - This is NOT the Parser's raw totalLinks accumulator. * - Graph::graphFileLoaded() currently emits Graph::edgesEnabled() as the * "totalLinks" argument in signalGraphLoaded (see issue #183 rationale). * * Derived reporting (CLI / golden JSON): * - SNA-style "links" where undirected edges are counted as 2 arcs must be * derived as: * links_sna = directed ? tiesGraph : 2 * tiesGraph */ struct HeadlessLoadResult { bool ok = false; int fileType = -1; QString fileName; QString netName; int totalNodes = 0; int tiesGraph = 0; // canonical ties == Graph::edgesEnabled() qint64 elapsedTime = 0; // ms QString message; }; HeadlessLoadResult loadGraphHeadless( Graph &graph, const QString &fileName, const QString &codecName, int fileFormat, const QString &delimiter, int sm_two_mode, bool sm_has_labels); SocNetV-3.7/src/tools/matrix_storage_bench.cpp000066400000000000000000000164401524402210300215240ustar00rootroot00000000000000/** * @file matrix_storage_bench.cpp * @brief Standalone WS5 A2.0 benchmark: QHash> (current APSP storage, * one per GraphVertex) vs. a flat Matrix (proposed APSP storage), measuring construction * and lookup speed for a single (N, topology, structure) configuration per run. Peak RSS * is measured externally by the driver script (see scripts/run_matrix_storage_bench.sh), * not by this process itself β€” see the note above buildComponents() for why. * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "../matrix.h" #include #include #include #include #include #include #include #include namespace { // Deliberately no in-process RSS measurement here. It was tried (both mach_task_basic_info and // getrusage's ru_maxrss) and both under-report for this tool's shape: a tight, single-threaded, // syscall-free compute loop doesn't generate enough kernel bookkeeping events (page faults, // scheduling activity) for ru_maxrss to be updated in real time, so a read taken mid-run lags // well behind the true peak β€” confirmed by comparing against macOS's own `/usr/bin/time -l` // (which reads the accurate parent-side wait4() rusage after the process exits) at N=20000: the // in-process reading undercounted by 6x-14x depending on the exact measurement used. Memory is // measured externally instead β€” see scripts/run_matrix_storage_bench.sh, which wraps each // invocation in the platform's equivalent of `time -l`/`time -v` and reports the peak RSS that // way. This tool only reports timing and a checksum. struct Component { int start; int size; // 0-size "components" are isolates }; // Builds the component structure for one of three topologies: // "connected" β€” a single component covering all N vertices. // "disconnected" β€” ~8 roughly-equal-sized fully-connected components plus ~5% isolates. // "giant" β€” one giant component (90% of N) plus a long tail of halving-sized // components down to isolates, approximating the component-size // distribution of real-world networks (one dominant component, a long // tail of small ones) rather than "disconnected"'s equal-sized split. std::vector buildComponents(int n, const QString &topology) { std::vector components; if (topology == "connected" || n < 16) { components.push_back({0, n}); return components; } if (topology == "giant") { const int giantSize = (n * 9) / 10; // 90% components.push_back({0, giantSize}); int pos = giantSize; int remaining = n - giantSize; while (remaining > 8) { const int half = remaining / 2; components.push_back({pos, half}); pos += half; remaining -= half; } for (int i = 0; i < remaining; ++i) { components.push_back({pos, 1}); ++pos; } return components; } // "disconnected" const int isolateCount = std::max(1, n / 20); // ~5% const int remaining = n - isolateCount; const int numGroups = 8; const int groupSize = remaining / numGroups; int pos = 0; for (int g = 0; g < numGroups; ++g) { const int size = (g == numGroups - 1) ? (remaining - pos) : groupSize; components.push_back({pos, size}); pos += size; } for (int i = 0; i < isolateCount; ++i) { components.push_back({pos, 1}); ++pos; } return components; } qreal sampleValue(int i, int j) { return 1.0 + static_cast((i + j) % 7); } } // namespace int main(int argc, char *argv[]) { int n = -1; QString topology; QString structure; for (int i = 1; i < argc; ++i) { const QString arg = QString::fromLocal8Bit(argv[i]); if (arg == "--n" && i + 1 < argc) { n = QString::fromLocal8Bit(argv[++i]).toInt(); } else if (arg == "--topology" && i + 1 < argc) { topology = QString::fromLocal8Bit(argv[++i]); } else if (arg == "--structure" && i + 1 < argc) { structure = QString::fromLocal8Bit(argv[++i]); } } if (n <= 0 || (topology != "connected" && topology != "disconnected" && topology != "giant") || (structure != "qhash" && structure != "matrix")) { fprintf(stderr, "usage: matrix-storage-bench --n --topology " "--structure \n"); return 1; } const std::vector components = buildComponents(n, topology); // Map each vertex to its component's [start, start+size) range for O(1) "same component?" // checks during construction and lookup. std::vector compStart(n), compEnd(n); for (const Component &c : components) { for (int v = c.start; v < c.start + c.size; ++v) { compStart[v] = c.start; compEnd[v] = c.start + c.size; } } QElapsedTimer timer; qreal checksum = 0.0; qint64 constructMs = 0; qint64 lookupMs = 0; if (structure == "qhash") { std::vector>> perVertex(n); timer.start(); for (int i = 0; i < n; ++i) { perVertex[i].reserve(compEnd[i] - compStart[i]); for (int j = compStart[i]; j < compEnd[i]; ++j) { if (j == i) continue; perVertex[i].insert(j, qMakePair(0, sampleValue(i, j))); } } constructMs = timer.elapsed(); timer.restart(); const int curRelation = 0; for (int i = 0; i < n; ++i) { const auto &hash = perVertex[i]; for (int j = 0; j < n; ++j) { if (j == i) continue; auto it = hash.constFind(j); while (it != hash.constEnd() && it.key() == j) { if (it.value().first == curRelation) { checksum += it.value().second; break; } ++it; } } } lookupMs = timer.elapsed(); } else { // matrix timer.start(); Matrix m(n, n); for (int i = 0; i < n; ++i) { for (int j = compStart[i]; j < compEnd[i]; ++j) { if (j == i) continue; m.setItem(i, j, sampleValue(i, j)); } } constructMs = timer.elapsed(); timer.restart(); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (j == i) continue; checksum += m.item(i, j); } } lookupMs = timer.elapsed(); } printf("%d,%s,%s,%lld,%lld,%.3f\n", n, qPrintable(topology), qPrintable(structure), static_cast(constructMs), static_cast(lookupMs), checksum); return 0; } SocNetV-3.7/src/tools/socnetv_cli.cpp000066400000000000000000000256531524402210300176530ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later // SocNetV - Social Network Visualizer // // socnetv-cli faΓ§ade: argument parsing + dispatch only. // All kernel logic lives in src/tools/cli/kernels/. #include #include #include #include #include #include #include "graph.h" #include "tools/headless_graph_loader.h" #include "tools/cli/cli_common.h" #include "tools/cli/kernels/kernel_distance_v1.h" #include "tools/cli/kernels/kernel_reachability_v2.h" #include "tools/cli/kernels/kernel_walks_v3.h" #include "tools/cli/kernels/kernel_prominence_v4.h" #include "tools/cli/kernels/kernel_io_roundtrip_v5.h" #include "tools/cli/kernels/kernel_clustering_v6.h" #include "tools/cli/kernels/kernel_connectivity_v7.h" #include "tools/cli/kernels/kernel_vertex_connectivity_v9.h" #include "tools/cli/kernels/kernel_matrix_v8.h" int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QCommandLineParser cli; cli.addHelpOption(); cli.addVersionOption(); QCommandLineOption verboseOpt(QStringList() << "b" << "verbose", "Verbose debug output."); QCommandLineOption strictOpt(QStringList() << "strict", "Fail on timing regressions (io_roundtrip kernel)."); QCommandLineOption fileOpt(QStringList() << "i" << "input", "Input network file path.", "path"); QCommandLineOption typeOpt(QStringList() << "f" << "format", "File type enum int.", "int", "0"); QCommandLineOption delimOpt(QStringList() << "d" << "delim", "Delimiter.", "str", " "); QCommandLineOption twoModeOpt(QStringList() << "m" << "two-mode", "Two-mode int.", "int", "0"); QCommandLineOption labelsOpt(QStringList() << "l" << "labels", "Adjacency has labels (0/1).", "int", "0"); QCommandLineOption centralitiesOpt(QStringList() << "c" << "centralities", "Compute centralities (0/1).", "int", "1"); QCommandLineOption weightsOpt(QStringList() << "w" << "weights", "Consider weights (0/1).", "int", "0"); QCommandLineOption invWeightsOpt(QStringList() << "x" << "inverse-weights", "Inverse weights (0/1).", "int", "1"); QCommandLineOption dropIsoOpt(QStringList() << "k" << "drop-isolates", "Drop isolates (0/1).", "int", "0"); QCommandLineOption dumpJsonOpt(QStringList() << "j" << "dump-json", "Write JSON.", "path"); QCommandLineOption compareJsonOpt(QStringList() << "p" << "compare-json", "Compare JSON.", "path"); QCommandLineOption benchOpt(QStringList() << "bench", "Benchmark compute kernel (distance only).", "N", "0"); QCommandLineOption kernelOpt(QStringList() << "kernel", "Kernel: distance|reachability|walks_matrix|prominence|io_roundtrip|clustering|connectivity|matrix|vertex_connectivity", "name", "distance"); QCommandLineOption walksLenOpt(QStringList() << "walks-length", "Walks length K for walks_matrix (K>=1).", "K", "0"); QCommandLineOption connTypeOpt(QStringList() << "connectivity-type", "Connectivity type for --kernel connectivity on directed graphs: weak|strong.", "type", "weak"); QCommandLineOption connModeOpt(QStringList() << "conn-mode", "Mode for --kernel vertex_connectivity: local|global.", "mode", "global"); QCommandLineOption connSourceOpt(QStringList() << "conn-source", "Source node number for --kernel vertex_connectivity --conn-mode local.", "int", "-1"); QCommandLineOption connTargetOpt(QStringList() << "conn-target", "Target node number for --kernel vertex_connectivity --conn-mode local.", "int", "-1"); QCommandLineOption katzAlphaOpt(QStringList() << "katz-alpha", "Attenuation factor alpha for --kernel prominence's Katz Centrality " "(must satisfy |alpha| < 1/lambda_max or the report will be all-zero). " "Omit to skip Katz Centrality entirely.", "alpha", "-1"); QCommandLineOption bonacichAlphaOpt(QStringList() << "bonacich-alpha", "Overall scale factor alpha for --kernel prominence's Bonacich " "Power Centrality. Requires --bonacich-beta too. Omit to skip " "Bonacich Power Centrality entirely.", "alpha", "-1"); QCommandLineOption bonacichBetaOpt(QStringList() << "bonacich-beta", "Attenuation factor beta for --kernel prominence's Bonacich Power " "Centrality (must satisfy |beta| < 1/lambda_max or the report will " "be all-zero). May be negative.", "beta", "0"); cli.addOption(verboseOpt); cli.addOption(strictOpt); cli.addOption(fileOpt); cli.addOption(typeOpt); cli.addOption(delimOpt); cli.addOption(twoModeOpt); cli.addOption(labelsOpt); cli.addOption(centralitiesOpt); cli.addOption(weightsOpt); cli.addOption(invWeightsOpt); cli.addOption(dropIsoOpt); cli.addOption(dumpJsonOpt); cli.addOption(compareJsonOpt); cli.addOption(benchOpt); cli.addOption(kernelOpt); cli.addOption(walksLenOpt); cli.addOption(connTypeOpt); cli.addOption(connModeOpt); cli.addOption(connSourceOpt); cli.addOption(connTargetOpt); cli.addOption(katzAlphaOpt); cli.addOption(bonacichAlphaOpt); cli.addOption(bonacichBetaOpt); cli.process(app); cli::CliConfig cfg; cfg.verbose = cli.isSet(verboseOpt); cfg.strict = cli.isSet(strictOpt); if (!cfg.verbose) { // Kill qDebug/qInfo output from Qt + your code (keeps warnings/criticals) qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &, const QString &msg) { if (type == QtWarningMsg || type == QtCriticalMsg || type == QtFatalMsg) { QTextStream(stderr) << msg << "\n"; } }); // WS14: the message handler above only discards output AFTER a message has already // been formatted -- it does nothing for qCDebug(category)'s actual cost-saving // mechanism, which is skipping argument evaluation BEFORE formatting, gated by the // category's own enabled/disabled state. Without this, a converted category (e.g. // socnetv.engine) defaults to enabled here and every qCDebug() call still pays full // formatting cost, just to have the result thrown away by the handler above -- the // exact "format-then-discard" tax WS14 exists to eliminate. Explicitly disabling the // categories is what actually lets the category-gate short-circuit take effect. QLoggingCategory::setFilterRules("default.debug=false\n" "socnetv.*.debug=false"); } cfg.inputPath = cli.value(fileOpt); cfg.fileFormat = cli.value(typeOpt).toInt(); cfg.delimiter = cli.value(delimOpt); cfg.twoMode = cli.value(twoModeOpt).toInt(); cfg.hasLabels = (cli.value(labelsOpt).toInt() != 0); cfg.computeCentralities = (cli.value(centralitiesOpt).toInt() != 0); cfg.considerWeights = (cli.value(weightsOpt).toInt() != 0); cfg.inverseWeights = (cli.value(invWeightsOpt).toInt() != 0); cfg.dropIsolates = (cli.value(dropIsoOpt).toInt() != 0); cfg.dumpJsonPath = cli.value(dumpJsonOpt); cfg.compareJsonPath = cli.value(compareJsonOpt); const int benchRunsRaw = cli.value(benchOpt).toInt(); cfg.benchRuns = (benchRunsRaw > 0) ? benchRunsRaw : 0; cfg.kernel = cli.value(kernelOpt).trimmed().toLower(); const int walksLength = cli.value(walksLenOpt).toInt(); cfg.connectivityType = cli.value(connTypeOpt).trimmed().toLower(); cfg.connMode = cli.value(connModeOpt).trimmed().toLower(); cfg.connSource = cli.value(connSourceOpt).toInt(); cfg.connTarget = cli.value(connTargetOpt).toInt(); cfg.katzAlpha = cli.value(katzAlphaOpt).toDouble(); cfg.bonacichAlpha = cli.value(bonacichAlphaOpt).toDouble(); cfg.bonacichBeta = cli.value(bonacichBetaOpt).toDouble(); if (cfg.inputPath.isEmpty()) { QTextStream(stderr) << "ERROR: --input is required\n"; return 2; } if (cfg.benchRuns > 0 && cfg.kernel != "distance" && cfg.kernel != "prominence") { QTextStream(stderr) << "ERROR: --bench is only supported with --kernel distance or prominence\n"; return 2; } Graph g; const auto load = loadGraphHeadless( g, cfg.inputPath, "UTF-8", cfg.fileFormat, cfg.delimiter, cfg.twoMode, cfg.hasLabels); cli::printKV("LOAD_OK", load.ok ? "1" : "0"); cli::printKV("FILE", QFileInfo(cfg.inputPath).fileName()); cli::printKV("FILETYPE", load.fileType); cli::printKV("N", load.totalNodes); cli::printKV("LOAD_MS", load.elapsedTime); cli::printKV("LOAD_MSG", load.message); if (!load.ok) return 1; // now safe to derive from graph state const int ties_graph = load.tiesGraph; const int links_sna = g.isDirected() ? ties_graph : (2 * ties_graph); cli::printKV("DIRECTED", g.isDirected() ? 1 : 0); cli::printKV("SYMMETRIC", g.isSymmetric() ? 1 : 0); cli::printKV("WEIGHTED", g.isWeighted() ? 1 : 0); cli::printKV("RELATIONS", g.relations()); cli::printKV("TIES_GRAPH", ties_graph); cli::printKV("LINKS_SNA", links_sna); if (cfg.kernel == "io_roundtrip") { cli::printKV("KERNEL_DESC", "io_roundtrip: load -> save(same-format) -> reload; compares per-relation signatures from the reloaded file"); } if (cfg.kernel == "distance") return cli::runKernelDistanceV1(cfg, load, g); if (cfg.kernel == "reachability") return cli::runKernelReachabilityV2(cfg, load, g); if (cfg.kernel == "walks_matrix") return cli::runKernelWalksV3(cfg, load, g, walksLength); if (cfg.kernel == "prominence") return cli::runKernelProminenceV4(cfg, load, g); if (cfg.kernel == "io_roundtrip") return cli::runKernelIoRoundtripV5(cfg, load, g); if (cfg.kernel == "clustering") return runKernelClusteringV6(cfg, load, g); if (cfg.kernel == "connectivity") return cli::runKernelConnectivityV7(cfg, load, g); if (cfg.kernel == "matrix") return cli::runKernelMatrixV8(cfg, load, g); if (cfg.kernel == "vertex_connectivity") return cli::runKernelVertexConnectivityV9(cfg, load, g); QTextStream(stderr) << "ERROR: unsupported --kernel: " << cfg.kernel << "\n"; return 2; } SocNetV-3.7/src/webcrawler.cpp000077500000000000000000000543201524402210300163340ustar00rootroot00000000000000/** * @file WebCrawler.cpp * @brief Implements the WebCrawler class for extracting and processing network data from web pages and online resources. * @details This file contains the logic for crawling web pages, extracting links, and generating network structures from online content. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #include "webcrawler.h" #include #include #include #include #include #include Q_LOGGING_CATEGORY(lcWebCrawler, "socnetv.webcrawler") /** * @brief Constructor from parent Graph thread. Inits variables. * @param url * @param maxNc * @param maxLinksPerPage * @param extLinks * @param intLinks */ WebCrawler::WebCrawler(QQueue *urlQueue, const QUrl &startUrl, const QStringList &urlPatternsIncluded, const QStringList &urlPatternsExcluded, const QStringList &linkClasses, const int &maxN, const int &maxLinksPerPage, const bool &intLinks, const bool &childLinks, const bool &parentLinks, const bool &selfLinks, const bool &extLinksIncluded, const bool &extLinksCrawl, const bool &socialLinks, const int &delayBetween) { qCDebug(lcWebCrawler) << "WebCrawler constructed on thread:" << thread() << "Initializing variables..."; m_urlQueue = urlQueue; m_initialUrl = startUrl; // Initialize user-defined control variables and limits m_urlPatternsIncluded = urlPatternsIncluded; // list of url patterns to include m_urlPatternsExcluded = urlPatternsExcluded; // list of url patterns to include m_linkClasses = linkClasses; // list of link classes to include m_maxUrls=maxN; // max urls we'll check == max nodes in the social network m_maxLinksPerPage = maxLinksPerPage; // max links per page to search for m_intLinks = intLinks; m_selfLinks = selfLinks; m_childLinks = childLinks; m_parentLinks = parentLinks; m_extLinksIncluded = extLinksIncluded; m_extLinksCrawl = extLinksCrawl; m_socialLinks = socialLinks; m_socialLinksExcluded << "facebook.com" << "twitter.com" << "linkedin.com" << "instagram.com" << "pinterest.com" << "telegram.org" << "telegram.me" << "youtube.com" << "reddit.com" << "tumblr.com" << "flickr.com" << "plus.google.com"; m_delayBetween = delayBetween; knownUrls.clear(); // a map of all known urls to their node number m_discoveredNodes=1; // Counts discovered nodes -- Set the counter to 1, as we already know the initial url knownUrls[m_initialUrl]=m_discoveredNodes; // Add the initial url to the map of known urls as node numbered 1. qCDebug(lcWebCrawler) << "initialUrl:" << m_initialUrl.toString() << " m_maxUrls " << m_maxUrls << " m_maxLinksPerPage " << m_maxLinksPerPage << " m_intLinks " << m_intLinks << " m_extLinksIncluded " << m_extLinksIncluded << " m_extLinksCrawl"<thread(); // Find the node the response HTML belongs to // Get this from the reply object request method QUrl currentUrl = reply->request().url(); QString currentUrlStr = currentUrl.toString(); QString locationHeader = reply->header(QNetworkRequest::LocationHeader).toString(); int sourceNode = knownUrls [ currentUrl ]; QString scheme = currentUrl.scheme(); QString host = currentUrl.host(); QUrl baseUrl = QUrl( scheme + "://" + host); QString path = currentUrl.path(); qCDebug(lcWebCrawler) << "Reply HTML belongs to url:" << currentUrlStr << " sourceNode:" << sourceNode << "host:" << host << "path:" << path; // Check for redirects if ( locationHeader != "" && locationHeader != currentUrlStr ) { qCDebug(lcWebCrawler) << "REDIRECT - Location response header:" << locationHeader << "differs from currentUrl:" << currentUrlStr << "Calling newLink() to create node redirect, and RETURNing..."; newLink( sourceNode, locationHeader , true ); return; } QUrl newUrl; QString newUrlStr; int start=-1, end=-1, equal=-1 , invalidUrlsInPage =0; // index=-1; int validUrlsInPage = 0; QByteArray ba = reply->readAll(); // read all data from the reply into a bytearray reply->deleteLater(); // schedule reply to be deleted QString page(ba); // construct a QString from the bytearray // Create a md5 hash of the page code QString md5(QCryptographicHash::hash(ba,QCryptographicHash::Md5).toHex()); qCDebug(lcWebCrawler) << "Reply MD5 sum:" << md5.toLocal8Bit(); // If there are no links inside the HTML source, return if (!page.contains ("href")) { //FIXME: Frameset pages are not parsed! See docs/manual.html for example. qCDebug(lcWebCrawler) << "Empty or not useful html from:" << currentUrl << "page size:" << page.size() << "\npage contents: " << page << "\nRETURN ##"; return; } // We only search inside ... tags qCDebug(lcWebCrawler) << "Finding tags"; start=page.indexOf (""); if ( start != -1 && end != -1 ) { page = page.remove(0, start); // remove everything until end=page.indexOf (""); // find new index pos of page = page.left(end); // keep everything until } else if ( start == -1 ) { qCDebug(lcWebCrawler) << "ERROR IN opening tag"; } else if ( end == -1 ) { qCDebug(lcWebCrawler) << "ERROR IN locating closing tag"; } // Main Loop: While there are more links in the page, parse them qCDebug(lcWebCrawler) << "Searching for href links inside the page code..."; while (page.contains(" href")) { if ( QThread::currentThread()->isInterruptionRequested() ) { qCDebug(lcWebCrawler) <<"STOP because currentThread()->isInterruptionRequested() == true."; emit finished("message from parse() - interruptionRequested"); return; } if (m_maxUrls>0) { if (m_discoveredNodes >= m_maxUrls ) { qCDebug(lcWebCrawler) <<"STOP because I have reached m_maxUrls."; emit finished("message from parse() - discoveredNodes > maxNodes"); return; } } // remove whitespace from the start and the end // all whitespace sequence becomes single space page=page.simplified(); start=page.indexOf (" href"); //Find its pos // Why " href" instead of "href"? // Because href might be inside random strings/tokens or // even in share links (ie. whatsapp://sadadasd &href=url) // By searching for " href" we avoid some of these cases, without // introducing other serious problems. page = page.remove(0, start); //erase everything up to href equal=page.indexOf ("="); // Find next equal sign (=) page=page.remove(0, equal+1); //Erase everything up to = if (page.startsWith("\"") ) { page.remove(0,1); end=page.indexOf ("\""); } else if (page.startsWith("\'") ){ page.remove(0,1); end=page.indexOf ("\'"); } else { //end=page.indexOf ("\'"); } newUrlStr=page.left(end); //Save new url to newUrl :) newUrlStr=newUrlStr.simplified(); qCDebug(lcWebCrawler) << "Found new URL:"<< newUrlStr << "Examining it..."; newUrl = QUrl(newUrlStr); if (newUrl.isRelative()) { qCDebug(lcWebCrawler) << "newUrl is RELATIVE. Merging baseUrl with it."; newUrl=baseUrl.resolved(newUrl); } if (!newUrl.isValid()) { invalidUrlsInPage ++; qCDebug(lcWebCrawler) << "newUrl is INVALID: " << newUrl.toString() << "in page " << currentUrlStr << "SKIPPING IT. Will continue in this page, only if invalidUrlsInPage < 200"; if (invalidUrlsInPage > 200) { qCDebug(lcWebCrawler) << "RETURN because invalid newUrls > 200"; emit finished("invalidUrlsInPage > 200"); return; } continue; } // TODO - REMOVE LAST / FROM EVERY PATH NOT ONLY ROOT PATH if (newUrl.path() == "/") { newUrl.setPath(""); } newUrlStr = newUrl.toString(); qCDebug(lcWebCrawler) << "newUrl is VALID:" << newUrlStr << "Checking if it is resource and if is allowed..."; // Skip css, favicon, rss, ping, etc if ( newUrlStr.startsWith("#", Qt::CaseInsensitive) || newUrlStr.endsWith("feed/", Qt::CaseInsensitive) || newUrlStr.endsWith("rss/", Qt::CaseInsensitive) || newUrlStr.endsWith("atom/", Qt::CaseInsensitive) || newUrl.fileName().endsWith("xmlrpc.php", Qt::CaseInsensitive) || newUrl.fileName().endsWith(".xml", Qt::CaseInsensitive) || newUrl.fileName().endsWith(".ico", Qt::CaseInsensitive) || newUrl.fileName().endsWith(".gif", Qt::CaseInsensitive) || newUrl.fileName().endsWith(".png", Qt::CaseInsensitive) || newUrl.fileName().endsWith(".jpg", Qt::CaseInsensitive) || newUrl.fileName().endsWith(".js", Qt::CaseInsensitive) || newUrl.fileName().endsWith(".css", Qt::CaseInsensitive) || newUrl.fileName().endsWith(".rsd", Qt::CaseInsensitive) ) { qCDebug(lcWebCrawler)<< "newUrl is a page resource or anchor (rss, favicon, etc) " << "Skipping..."; continue; } // Check if newUrl is compatible with the url patterns the user asked for m_urlPatternAllowed = true; for (constIterator = m_urlPatternsIncluded.constBegin(); constIterator != m_urlPatternsIncluded.constEnd(); ++constIterator) { urlPattern = (*constIterator).toLocal8Bit().constData(); if (urlPattern.isEmpty()) { continue; } if ( newUrl.toString().contains( urlPattern ) ) { qCDebug(lcWebCrawler) << "newUrl in allowed url patterns:" << urlPattern << "OK."; break; } else { qCDebug(lcWebCrawler) << "newUrl not in allowed url patterns. I WILL SKIP IT. "; m_urlPatternAllowed = false; } } m_urlPatternNotAllowed = false; for (constIterator = m_urlPatternsExcluded.constBegin(); constIterator != m_urlPatternsExcluded.constEnd(); ++constIterator) { urlPattern = (*constIterator).toLocal8Bit().constData(); if (urlPattern.isEmpty()) continue; if ( newUrl.toString().contains( urlPattern ) ) { qCDebug(lcWebCrawler) << "newUrl in excluded url patterns:" << urlPattern << "I WILL SKIP IT."; m_urlPatternNotAllowed = true; break; } else { qCDebug(lcWebCrawler) << "newUrl not in excluded url patterns. OK."; } } if (m_urlPatternAllowed && !m_urlPatternNotAllowed) { if ( newUrl.isRelative() ) { newUrl = currentUrl.resolved(newUrl); newUrlStr = newUrl.toString(); qCDebug(lcWebCrawler) << "newUrl is RELATIVE." << "host: " << host << "resolved url:" << newUrl.toString(); if (!m_intLinks ){ qCDebug(lcWebCrawler)<< "SKIPPING node/edge creation because internal URLs are forbidden."; continue; } if (currentUrl.path() == newUrl.path()) { if (m_selfLinks) { qCDebug(lcWebCrawler)<< "Self-link found. Creating it!"; newLink(sourceNode, newUrl, false); } else { qCDebug(lcWebCrawler)<< "Self-link found but self-links are not allowed. I will not create it."; } } else { qCDebug(lcWebCrawler)<< "Internal URLs allowed. Calling newLink() "; this->newLink(sourceNode, newUrl, true); } } else { qCDebug(lcWebCrawler) << "newUrl is ABSOLUTE."; if ( newUrl.scheme() != "http" && newUrl.scheme() != "https" && newUrl.scheme() != "ftp" && newUrl.scheme() != "ftps") { qCDebug(lcWebCrawler) << "INVALID newUrl SCHEME" << newUrl.toString() << "CONTINUE."; continue; } if ( newUrl.host() != host ) { qCDebug(lcWebCrawler)<< "newUrl ABSOLUTE & EXTERNAL."; if ( !m_extLinksIncluded ) { qCDebug(lcWebCrawler)<< "External URLs forbidden. CONTINUE"; continue; } else { m_urlIsSocial = false; if ( !m_socialLinks ) { for (constIterator = m_socialLinksExcluded.constBegin(); constIterator != m_socialLinksExcluded.constEnd(); ++constIterator) { urlPattern = (*constIterator).toLocal8Bit().constData(); if ( newUrl.host().contains ( urlPattern) ) { m_urlIsSocial = true; break; } } if ( m_urlIsSocial) { qCDebug(lcWebCrawler) << "newUrl in excluded social links:" << urlPattern << "CONTINUE "; continue; } } if ( m_extLinksCrawl ) { qCDebug(lcWebCrawler)<< "External URLs are included and to be crawled. Calling newLink..."; newLink(sourceNode, newUrl, true); } else { qCDebug(lcWebCrawler)<< "External URLs included but not to be crawled. Calling newLink() but the url will not be added to the queue"; newLink(sourceNode, newUrl, false); } } } else { qCDebug(lcWebCrawler)<< "newUrl ABSOLUTE & INTERNAL."; if (!m_intLinks){ qCDebug(lcWebCrawler)<< "Internal URLs forbidden." << "SKIPPING node creation"; continue; } if ( newUrl.path () == path && !m_selfLinks) { qCDebug(lcWebCrawler)<< "Self links forbidden. CONTINUE."; continue; } if ( newUrl.isParentOf(currentUrl) && !m_parentLinks ) { qCDebug(lcWebCrawler)<< "Parent URLs forbidden. CONTINUE"; continue; } if ( currentUrl.isParentOf(newUrl) && !m_childLinks ) { qCDebug(lcWebCrawler)<< "Child URLs forbidden. CONTINUE"; continue; } qCDebug(lcWebCrawler)<< "Internal, absolute newURL allowed. Calling newLink..."; newLink(sourceNode, newUrl, true); } } } // end if in allowed patterns validUrlsInPage ++; qCDebug(lcWebCrawler) << "validUrlsInPage:" << validUrlsInPage << "in page:" << currentUrlStr; // If the user has specified a maxLinksPerPage limit then, // if we have reached it, stop parsing this page if ( m_maxLinksPerPage != 0 ) { if ( validUrlsInPage > m_maxLinksPerPage ) { qCDebug(lcWebCrawler) <<"STOP because I reached m_maxLinksPerPage " <0 && m_discoveredNodes >= m_maxUrls ) { qCDebug(lcWebCrawler) <<"STOP because we have reached m_maxUrls!"; emit finished("maxpages from newLink"); return; } // check if the new url has been discovered previously QMap::const_iterator index = knownUrls.constFind(target); if ( index!= knownUrls.constEnd() ) { qCDebug(lcWebCrawler)<< "Target already discovered (in knownUrls) as node:" << index.value(); if (s !=index.value()) { if ( QThread::currentThread()->isInterruptionRequested() ) { qCDebug(lcWebCrawler) <<"STOP because currentThread()->isInterruptionRequested() == true."; emit finished("message from parse() - interruptionRequested"); return; } qCDebug(lcWebCrawler)<< "Emitting signalCreateEdge" << s << "->" << index.value() << "and RETURN."; emit signalCreateEdge (s, index.value() ); } else { qCDebug(lcWebCrawler)<< "Self links not allowed. RETURN."; } return; } m_discoveredNodes++; knownUrls[target]=m_discoveredNodes; if ( QThread::currentThread()->isInterruptionRequested() ) { qCDebug(lcWebCrawler) <<"STOP because currentThread()->isInterruptionRequested() == true."; emit finished("message from parse() - interruptionRequested"); return; } qCDebug(lcWebCrawler)<< "Target just discovered. Emitting signalCreateNode:" << m_discoveredNodes << "for url:"<< target.toString(); emit signalCreateNode( m_discoveredNodes, target.toString(), false); if (enqueue_to_frontier) { m_urlQueue->enqueue(target); qCDebug(lcWebCrawler)<< "Enqueued new node:" << m_discoveredNodes << "to urlQueue." << "queue size: "<< m_urlQueue->size() << " - Emitting signalStartSpider..."; // Check if we need to add some delay between requests if (m_delayBetween) { int m_wait_msecs = rand() % m_delayBetween; qCDebug(lcWebCrawler) << "delay requested between signalStartSpider() calls. Setting a deadline for:" << m_wait_msecs << "msecs"; QDeadlineTimer deadline(m_wait_msecs); do { } while (deadline.remainingTime() > 0 && this->thread()->isRunning()); if ( QThread::currentThread()->isInterruptionRequested() ) { qCDebug(lcWebCrawler) <<"STOP because currentThread()->isInterruptionRequested() == true."; emit finished("message from parse() - interruptionRequested"); return; } emit signalStartSpider(); } else { if ( QThread::currentThread()->isInterruptionRequested() ) { qCDebug(lcWebCrawler) <<"STOP because currentThread()->isInterruptionRequested() == true."; emit finished("message from parse() - interruptionRequested"); return; } emit signalStartSpider(); } } else { qCDebug(lcWebCrawler)<< "NOT adding new node to queue"; } if ( QThread::currentThread()->isInterruptionRequested() ) { qCDebug(lcWebCrawler) <<"STOP because currentThread()->isInterruptionRequested() == true."; emit finished("message from parse() - interruptionRequested"); return; } qCDebug(lcWebCrawler)<< "Emitting signalCreateEdge" << s << "->" << m_discoveredNodes; emit signalCreateEdge (s, m_discoveredNodes); } WebCrawler::~WebCrawler() { qCDebug(lcWebCrawler) << "Destructor running. Clearing vars..."; knownUrls.clear(); m_urlPatternsIncluded.clear(); urlPattern=""; m_urlPatternsExcluded.clear(); m_linkClasses.clear(); m_discoveredNodes = 0; } SocNetV-3.7/src/webcrawler.h000077500000000000000000000055371524402210300160070ustar00rootroot00000000000000/** * @file WebCrawler.h * @brief Declares the WebCrawler class for extracting and processing network data from web pages and online resources. * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 or later. * For more details, see . * @see https://socnetv.org */ #ifndef WEBCRAWLER_H #define WEBCRAWLER_H #include #include QT_BEGIN_NAMESPACE class QUrl; QT_END_NAMESPACE using namespace std; /** * @brief The WebCrawler class * Parses HTML code it receives, locates urls inside it and puts them into a url queue (passed from the parent) * while emitting signals to the parent to create new nodes and edges between them. */ class WebCrawler : public QObject { Q_OBJECT // QThread wc_spiderThread; public: WebCrawler ( QQueue *urlQueue, const QUrl &startUrl, const QStringList &urlPatternsIncluded, const QStringList &urlPatternsExcluded, const QStringList &linkClasses, const int &maxNodes, const int &maxLinksPerPage, const bool &intLinks = true, const bool &childLinks = true, const bool &parentLinks = false, const bool &selfLinks = false, const bool &extLinksIncluded = false, const bool &extLinksCrawl = false, const bool &socialLinks = false, const int &delayBetween = 0 ); ~WebCrawler(); public slots: void parse(QNetworkReply *reply); void newLink(int s, QUrl target, bool enqueue_to_frontier); signals: void signalCreateNode(const int &no, const QString &url, const bool &signalMW=false); void signalCreateEdge (const int &source, const int &target); void signalStartSpider(); void finished (QString); private: QQueue *m_urlQueue; QMap knownUrls; QUrl m_initialUrl; int m_maxUrls; int m_discoveredNodes; int m_maxLinksPerPage; bool m_intLinks; bool m_childLinks; bool m_parentLinks; bool m_selfLinks ; bool m_extLinksIncluded; bool m_extLinksCrawl; bool m_socialLinks; bool m_urlIsSocial; int m_delayBetween; QStringList m_urlPatternsIncluded; QString urlPattern; QStringList m_urlPatternsExcluded; QStringList m_linkClasses; QStringList m_socialLinksExcluded; QStringList::const_iterator constIterator; bool m_urlPatternAllowed; bool m_urlPatternNotAllowed; bool m_linkClassAllowed; }; #endif SocNetV-3.7/src/widgets/000077500000000000000000000000001524402210300151325ustar00rootroot00000000000000SocNetV-3.7/src/widgets/edgetablemodel.cpp000066400000000000000000000165531524402210300206050ustar00rootroot00000000000000/** * @file edgetablemodel.cpp * @brief Implements EdgeTableModel β€” local cache of edge data for the data table * panel (#225). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #include "edgetablemodel.h" #include "../graph.h" #include "../graphvertex.h" #include EdgeTableModel::EdgeTableModel(QObject *parent) : QAbstractTableModel(parent) { } /** * @brief Rebuilds the internal row cache from the current relation of @p graph. * * Only enabled edges belonging to the current relation are included. * Custom attribute keys are taken from Graph::graphHasEdgeCustomAttributes(). */ void EdgeTableModel::populate(Graph *graph) { beginResetModel(); m_graph = graph; m_rows.clear(); m_attrKeys.clear(); if (!m_graph) { endResetModel(); return; } m_attrKeys = m_graph->graphHasEdgeCustomAttributes(); const int curRel = m_graph->relationCurrent(); const QString relName = m_graph->relationCurrentName(); for (auto vIt = m_graph->verticesBegin(); vIt != m_graph->verticesEnd(); ++vIt) { GraphVertex *v = *vIt; // Iterate over the out-edges of this vertex. // H_edges: QMultiHash>> // key = target vertex number // value = (relation index, (weight, enabled)) const H_edges &edges = v->outEdges(); for (auto eIt = edges.cbegin(); eIt != edges.cend(); ++eIt) { const int relation = eIt.value().first; const qreal weight = eIt.value().second.first; const bool enabled = eIt.value().second.second; // Skip edges from other relations or disabled edges if (relation != curRel || !enabled) continue; const int source = v->number(); const int target = eIt.key(); EdgeRow row; row.source = source; row.target = target; row.relation = relName; row.weight = weight; row.label = m_graph->edgeLabel(source, target); row.color = QColor(m_graph->edgeColor(source, target)).name(); const QHash attrs = m_graph->edgeCustomAttributes(source, target); for (const QString &key : m_attrKeys) { row.attrValues.append(attrs.value(key, QString())); } m_rows.append(row); } } endResetModel(); } int EdgeTableModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return m_rows.size(); } int EdgeTableModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return FIXED_COLS + m_attrKeys.size(); } QVariant EdgeTableModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); const int row = index.row(); const int col = index.column(); if (row < 0 || row >= m_rows.size() || col < 0 || col >= columnCount()) return QVariant(); const EdgeRow &r = m_rows.at(row); switch (role) { case Qt::DisplayRole: switch (col) { case COL_SOURCE: return QString::number(r.source); case COL_TARGET: return QString::number(r.target); case COL_RELATION: return r.relation; case COL_WEIGHT: return QString::number(r.weight, 'f', 2); case COL_LABEL: return r.label; case COL_COLOR: return r.color; default: return r.attrValues.value(col - FIXED_COLS); } case Qt::DecorationRole: if (col == COL_COLOR) return QColor(r.color); return QVariant(); case Qt::TextAlignmentRole: if (col == COL_SOURCE || col == COL_TARGET || col == COL_WEIGHT) return static_cast(Qt::AlignCenter); return QVariant(); case Qt::BackgroundRole: if (col == COL_SOURCE || col == COL_TARGET || col == COL_RELATION) return QBrush(QColor("#f0f0f0")); return QVariant(); default: break; } return QVariant(); } QVariant EdgeTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { switch (section) { case COL_SOURCE: return tr("Source"); case COL_TARGET: return tr("Target"); case COL_RELATION: return tr("Relation"); case COL_WEIGHT: return tr("Weight"); case COL_LABEL: return tr("Label"); case COL_COLOR: return tr("Color"); default: if (section - FIXED_COLS < m_attrKeys.size()) return m_attrKeys.at(section - FIXED_COLS); break; } } else { return section + 1; } return QVariant(); } Qt::ItemFlags EdgeTableModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; const int col = index.column(); // Read-only columns: Source, Target, Relation if (col == COL_SOURCE || col == COL_TARGET || col == COL_RELATION) return Qt::ItemIsEnabled | Qt::ItemIsSelectable; // Editable columns: Weight, Label, Color, custom attrs return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } bool EdgeTableModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || role != Qt::EditRole || !m_graph) return false; const int row = index.row(); const int col = index.column(); if (row < 0 || row >= m_rows.size() || col < 0 || col >= columnCount()) return false; EdgeRow &r = m_rows[row]; switch (col) { case COL_WEIGHT: { const qreal newVal = value.toDouble(); if (qFuzzyCompare(r.weight, newVal)) return false; r.weight = newVal; m_graph->edgeWeightSet(r.source, r.target, newVal); break; } case COL_LABEL: { const QString newVal = value.toString(); if (r.label == newVal) return false; r.label = newVal; m_graph->edgeLabelSet(r.source, r.target, newVal); break; } case COL_COLOR: { const QString newVal = QColor(value.toString()).name(); if (r.color == newVal) return false; r.color = newVal; m_graph->edgeColorSet(r.source, r.target, newVal); break; } default: if (col >= FIXED_COLS) { const int attrIdx = col - FIXED_COLS; if (attrIdx >= m_attrKeys.size()) return false; const QString newVal = value.toString(); if (r.attrValues.value(attrIdx) == newVal) return false; r.attrValues[attrIdx] = newVal; QHash attrs = m_graph->edgeCustomAttributes(r.source, r.target); attrs[m_attrKeys.at(attrIdx)] = newVal; m_graph->edgeCustomAttributesSet(r.source, r.target, attrs); } else { return false; } break; } emit dataChanged(index, index, {role}); return true; } SocNetV-3.7/src/widgets/edgetablemodel.h000066400000000000000000000047051524402210300202460ustar00rootroot00000000000000/** * @file edgetablemodel.h * @brief Declares EdgeTableModel β€” a QAbstractTableModel for edge data (#225). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #pragma once #include #include #include #include class Graph; /** * @brief Table model that caches edge data for the current relation from * Graph and writes back via the Graph API. * * Fixed columns: Source, Target, Relation, Weight, Label, Color. * Dynamic columns: one per custom attribute key reported by the graph. */ class EdgeTableModel : public QAbstractTableModel { Q_OBJECT public: explicit EdgeTableModel(QObject *parent = nullptr); /** * @brief Rebuilds the internal cache from @p graph (current relation only). * * Calls beginResetModel() / endResetModel() so connected views repaint. */ void populate(Graph *graph); /** Custom attribute keys present in the current graph (dynamic columns). */ QStringList attrKeys() const { return m_attrKeys; } int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; private: struct EdgeRow { int source; int target; QString relation; qreal weight; QString label; QString color; QList attrValues; ///< parallel to m_attrKeys }; Graph *m_graph = nullptr; QList m_rows; QStringList m_attrKeys; static constexpr int FIXED_COLS = 6; // Column index constants static constexpr int COL_SOURCE = 0; static constexpr int COL_TARGET = 1; static constexpr int COL_RELATION = 2; static constexpr int COL_WEIGHT = 3; static constexpr int COL_LABEL = 4; static constexpr int COL_COLOR = 5; }; SocNetV-3.7/src/widgets/filterbarwidget.cpp000066400000000000000000000111711524402210300210150ustar00rootroot00000000000000/** * @file filterbarwidget.cpp * @brief Implements FilterBarWidget β€” the persistent filter chip bar (#219). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #include "filterbarwidget.h" #include #include #include #include #include #include #include FilterBarWidget::FilterBarWidget(QWidget *parent) : QWidget(parent) { setObjectName("FilterBarWidget"); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); QHBoxLayout *outer = new QHBoxLayout(this); outer->setContentsMargins(6, 3, 6, 3); outer->setSpacing(4); m_chipsLayout = new QHBoxLayout; m_chipsLayout->setContentsMargins(0, 0, 0, 0); m_chipsLayout->setSpacing(4); outer->addLayout(m_chipsLayout); outer->addStretch(1); m_clearAllBtn = new QPushButton(tr("Clear all"), this); m_clearAllBtn->setObjectName("ClearAllButton"); m_clearAllBtn->setFlat(true); m_clearAllBtn->setCursor(Qt::PointingHandCursor); connect(m_clearAllBtn, &QPushButton::clicked, this, &FilterBarWidget::clearAllRequested); outer->addWidget(m_clearAllBtn); hide(); } void FilterBarWidget::addChip(const QString &label, FilterCondition::Scope scope) { QFrame *chip = new QFrame(this); chip->setObjectName("FilterChip"); chip->setFrameShape(QFrame::StyledPanel); QHBoxLayout *lay = new QHBoxLayout(chip); lay->setContentsMargins(8, 2, 2, 2); lay->setSpacing(2); QLabel *lbl = new QLabel(label, chip); lbl->setObjectName("ChipLabel"); QToolButton *closeBtn = new QToolButton(chip); closeBtn->setObjectName("ChipCloseButton"); closeBtn->setIcon(QIcon(":/images/close_24px.svg")); closeBtn->setIconSize(QSize(12, 12)); closeBtn->setAutoRaise(true); closeBtn->setCursor(Qt::PointingHandCursor); closeBtn->setToolTip(tr("Remove this filter")); lay->addWidget(lbl); lay->addWidget(closeBtn); m_chipsLayout->addWidget(chip); m_chips.append({scope, chip, closeBtn}); connect(closeBtn, &QToolButton::clicked, this, [this, chip, scope]() { int idx = -1; for (int i = 0; i < m_chips.size(); ++i) { if (m_chips[i].frame == chip) { idx = i; break; } } if (idx >= 0) emit chipCloseRequested(idx, scope); // Chip removal is handled by the receiver (MainWindow) via // removeChipAt() or a full bar rebuild. }); updateVisibility(); } void FilterBarWidget::removeChip(QFrame *chip) { for (int i = 0; i < m_chips.size(); ++i) { if (m_chips[i].frame == chip) { m_chipsLayout->removeWidget(chip); chip->deleteLater(); m_chips.removeAt(i); break; } } updateVisibility(); } void FilterBarWidget::removeLatestChipForScope(FilterCondition::Scope scope) { for (int i = m_chips.size() - 1; i >= 0; --i) { if (m_chips[i].scope == scope) { m_chipsLayout->removeWidget(m_chips[i].frame); m_chips[i].frame->deleteLater(); m_chips.removeAt(i); break; } } updateVisibility(); } void FilterBarWidget::removeAllChipsForScope(FilterCondition::Scope scope) { for (int i = m_chips.size() - 1; i >= 0; --i) { if (m_chips[i].scope == scope) { m_chipsLayout->removeWidget(m_chips[i].frame); m_chips[i].frame->deleteLater(); m_chips.removeAt(i); } } updateVisibility(); } void FilterBarWidget::clearAllChips() { for (auto &cd : m_chips) { m_chipsLayout->removeWidget(cd.frame); cd.frame->deleteLater(); } m_chips.clear(); updateVisibility(); } void FilterBarWidget::updateVisibility() { setVisible(!m_chips.isEmpty()); updateCloseButtons(); } void FilterBarWidget::updateCloseButtons() { for (auto &cd : m_chips) { cd.closeBtn->setEnabled(true); cd.closeBtn->setToolTip(tr("Remove this filter")); } } void FilterBarWidget::removeChipAt(int barIndex) { if (barIndex < 0 || barIndex >= m_chips.size()) return; m_chipsLayout->removeWidget(m_chips[barIndex].frame); m_chips[barIndex].frame->deleteLater(); m_chips.removeAt(barIndex); updateVisibility(); } FilterCondition::Scope FilterBarWidget::chipScopeAt(int barIndex) const { if (barIndex < 0 || barIndex >= m_chips.size()) return FilterCondition::Scope::Nodes; return m_chips[barIndex].scope; } SocNetV-3.7/src/widgets/filterbarwidget.h000066400000000000000000000047151524402210300204700ustar00rootroot00000000000000/** * @file filterbarwidget.h * @brief Persistent filter bar showing active filter chips (#219). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #pragma once #include #include #include "../graph/filters/filter_condition.h" class QHBoxLayout; class QPushButton; class QFrame; class QToolButton; /** * @brief Thin strip between toolbar and canvas showing one chip per active filter. * * Hidden when no filters are active. Each chip carries a label and a Γ— button. * Clicking Γ— emits chipCloseRequested(barIndex, scope) BEFORE any chip is removed. * The receiver (MainWindow) orchestrates graph state restoration, then calls * removeChipAt() or rebuilds the bar via clearAllChips() + addChip(). * "Clear all" emits clearAllRequested(); MainWindow drains the stack, then calls * clearAllChips(). * * Menu-driven restores (Restore All Nodes / Restore All Edges) must call * removeLatestChipForScope() / removeAllChipsForScope() so the bar stays in sync. */ class FilterBarWidget : public QWidget { Q_OBJECT public: explicit FilterBarWidget(QWidget *parent = nullptr); void addChip(const QString &label, FilterCondition::Scope scope); void removeLatestChipForScope(FilterCondition::Scope scope); void removeAllChipsForScope(FilterCondition::Scope scope); void removeChipAt(int barIndex); FilterCondition::Scope chipScopeAt(int barIndex) const; public slots: void clearAllChips(); signals: /** * Emitted when the user clicks Γ— on a chip, BEFORE the chip is removed. * @p barIndex is the chip's position (0 = leftmost) at the time of the click. * The bar does NOT remove the chip automatically; the receiver must call * removeChipAt(), clearAllChips(), or rebuild the bar as appropriate. */ void chipCloseRequested(int barIndex, FilterCondition::Scope scope); /** Emitted when the user clicks "Clear all". */ void clearAllRequested(); private: struct ChipData { FilterCondition::Scope scope; QFrame *frame = nullptr; QToolButton *closeBtn = nullptr; }; QHBoxLayout *m_chipsLayout; QPushButton *m_clearAllBtn; QList m_chips; void removeChip(QFrame *chip); void updateVisibility(); void updateCloseButtons(); }; SocNetV-3.7/src/widgets/graphtablewidget.cpp000066400000000000000000000746341524402210300211710ustar00rootroot00000000000000/** * @file graphtablewidget.cpp * @brief Implements GraphTableWidget β€” the tabbed node/edge data table panel * (#225). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #include "graphtablewidget.h" #include "nodetablemodel.h" #include "edgetablemodel.h" #include "../graph/io/table_export.h" #include "../graph/io/table_import.h" #include "../forms/dialogimportattributes.h" #include "../forms/dialogbulkedit.h" #include "../graph.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * @brief Constructs the GraphTableWidget. * * Builds both tabs (Nodes, Edges) with search bar, Refresh button, and a * sortable/searchable QTableView backed by a QSortFilterProxyModel. */ GraphTableWidget::GraphTableWidget(QWidget *parent) : QWidget(parent) { setObjectName("GraphTableWidget"); // ----------------------------------------------------------------------- // Create models // ----------------------------------------------------------------------- m_nodeModel = new NodeTableModel(this); m_edgeModel = new EdgeTableModel(this); // ----------------------------------------------------------------------- // Proxy models // ----------------------------------------------------------------------- m_nodeProxy = new QSortFilterProxyModel(this); m_nodeProxy->setSourceModel(m_nodeModel); m_nodeProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); m_nodeProxy->setFilterKeyColumn(-1); // search all columns m_edgeProxy = new QSortFilterProxyModel(this); m_edgeProxy->setSourceModel(m_edgeModel); m_edgeProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); m_edgeProxy->setFilterKeyColumn(-1); // ----------------------------------------------------------------------- // Helper lambda to build a configured QTableView // ----------------------------------------------------------------------- auto makeView = [this]() -> QTableView * { QTableView *view = new QTableView(this); view->setSortingEnabled(true); view->setSelectionBehavior(QAbstractItemView::SelectRows); view->setSelectionMode(QAbstractItemView::ExtendedSelection); view->setAlternatingRowColors(true); view->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); view->horizontalHeader()->setStretchLastSection(true); view->verticalHeader()->hide(); return view; }; // ----------------------------------------------------------------------- // Nodes tab // ----------------------------------------------------------------------- m_nodeView = makeView(); m_nodeView->setModel(m_nodeProxy); m_nodeSearch = new QLineEdit(this); m_nodeSearch->setPlaceholderText(tr("Search...")); m_nodeSearch->setClearButtonEnabled(true); m_nodeSearch->setMaximumWidth(140); QPushButton *nodeRefreshBtn = new QPushButton(tr("Refresh"), this); nodeRefreshBtn->setToolTip(tr("Reload node data from the current graph")); QPushButton *nodeExportCSVBtn = new QPushButton(tr("Export CSV"), this); nodeExportCSVBtn->setToolTip(tr("Export currently visible rows as CSV")); QPushButton *nodeExportJSONBtn = new QPushButton(tr("Export JSON"), this); nodeExportJSONBtn->setToolTip(tr("Export currently visible rows as JSON")); QPushButton *nodeImportCSVBtn = new QPushButton(tr("Import CSV"), this); nodeImportCSVBtn->setToolTip(tr("Import node attributes from a CSV file")); QPushButton *nodeImportJSONBtn = new QPushButton(tr("Import JSON"), this); nodeImportJSONBtn->setToolTip(tr("Import node attributes from a JSON file")); QPushButton *nodeSetPropBtn = new QPushButton(tr("Set property..."), this); nodeSetPropBtn->setToolTip( tr("Set a single property on the selected rows (or all visible rows if none are selected). " "Applies to built-in fields (Label, Size, Color, Shape) and custom attributes.")); QPushButton *nodeRemoveAttrBtn = new QPushButton(tr("Remove attribute..."), this); nodeRemoveAttrBtn->setToolTip( tr("Remove a custom attribute key from the selected rows (or all visible rows if none are selected).")); QPushButton *nodeAddAttrBtn = new QPushButton(tr("Add attribute..."), this); nodeAddAttrBtn->setToolTip( tr("Add a new custom attribute key/value to the selected rows (or all visible rows if none are selected).")); // VLine: Search | Refresh QFrame *nodeSep1 = new QFrame(this); nodeSep1->setFrameShape(QFrame::VLine); nodeSep1->setFrameShadow(QFrame::Sunken); // VLine: Refresh | Export/Import QFrame *nodeSep2 = new QFrame(this); nodeSep2->setFrameShape(QFrame::VLine); nodeSep2->setFrameShadow(QFrame::Sunken); // VLine: Export/Import | Bulk-edit QFrame *nodeSep3 = new QFrame(this); nodeSep3->setFrameShape(QFrame::VLine); nodeSep3->setFrameShadow(QFrame::Sunken); // VLine: Set property | Remove/Add attribute QFrame *nodeSep4 = new QFrame(this); nodeSep4->setFrameShape(QFrame::VLine); nodeSep4->setFrameShadow(QFrame::Sunken); QHBoxLayout *nodeTopBar = new QHBoxLayout; nodeTopBar->addWidget(m_nodeSearch); nodeTopBar->addWidget(nodeSep1); nodeTopBar->addWidget(nodeRefreshBtn); nodeTopBar->addWidget(nodeSep2); nodeTopBar->addWidget(nodeExportCSVBtn); nodeTopBar->addWidget(nodeExportJSONBtn); nodeTopBar->addWidget(nodeImportCSVBtn); nodeTopBar->addWidget(nodeImportJSONBtn); nodeTopBar->addWidget(nodeSep3); nodeTopBar->addWidget(nodeSetPropBtn); nodeTopBar->addWidget(nodeSep4); nodeTopBar->addWidget(nodeRemoveAttrBtn); nodeTopBar->addWidget(nodeAddAttrBtn); nodeTopBar->addStretch(); QWidget *nodeTab = new QWidget(this); QVBoxLayout *nodeLayout = new QVBoxLayout(nodeTab); nodeLayout->setContentsMargins(4, 4, 4, 4); nodeLayout->setSpacing(4); nodeLayout->addLayout(nodeTopBar); nodeLayout->addWidget(m_nodeView, 1); // ----------------------------------------------------------------------- // Edges tab // ----------------------------------------------------------------------- m_edgeView = makeView(); m_edgeView->setModel(m_edgeProxy); m_edgeSearch = new QLineEdit(this); m_edgeSearch->setPlaceholderText(tr("Search...")); m_edgeSearch->setClearButtonEnabled(true); m_edgeSearch->setMaximumWidth(140); QPushButton *edgeRefreshBtn = new QPushButton(tr("Refresh"), this); edgeRefreshBtn->setToolTip(tr("Reload edge data from the current graph")); QPushButton *edgeExportCSVBtn = new QPushButton(tr("Export CSV"), this); edgeExportCSVBtn->setToolTip(tr("Export currently visible rows as CSV")); QPushButton *edgeExportJSONBtn = new QPushButton(tr("Export JSON"), this); edgeExportJSONBtn->setToolTip(tr("Export currently visible rows as JSON")); QPushButton *edgeImportCSVBtn = new QPushButton(tr("Import CSV"), this); edgeImportCSVBtn->setToolTip(tr("Import edge attributes from a CSV file")); QPushButton *edgeImportJSONBtn = new QPushButton(tr("Import JSON"), this); edgeImportJSONBtn->setToolTip(tr("Import edge attributes from a JSON file")); QPushButton *edgeSetPropBtn = new QPushButton(tr("Set property..."), this); edgeSetPropBtn->setToolTip( tr("Set a single property on the selected rows (or all visible rows if none are selected). " "Applies to built-in fields (Label, Weight, Color) and custom attributes.")); QPushButton *edgeRemoveAttrBtn = new QPushButton(tr("Remove attribute..."), this); edgeRemoveAttrBtn->setToolTip( tr("Remove a custom attribute key from the selected rows (or all visible rows if none are selected).")); QPushButton *edgeAddAttrBtn = new QPushButton(tr("Add attribute..."), this); edgeAddAttrBtn->setToolTip( tr("Add a new custom attribute key/value to the selected rows (or all visible rows if none are selected).")); // VLine: Search | Refresh QFrame *edgeSep1 = new QFrame(this); edgeSep1->setFrameShape(QFrame::VLine); edgeSep1->setFrameShadow(QFrame::Sunken); // VLine: Refresh | Export/Import QFrame *edgeSep2 = new QFrame(this); edgeSep2->setFrameShape(QFrame::VLine); edgeSep2->setFrameShadow(QFrame::Sunken); // VLine: Export/Import | Bulk-edit QFrame *edgeSep3 = new QFrame(this); edgeSep3->setFrameShape(QFrame::VLine); edgeSep3->setFrameShadow(QFrame::Sunken); // VLine: Set property | Remove/Add attribute QFrame *edgeSep4 = new QFrame(this); edgeSep4->setFrameShape(QFrame::VLine); edgeSep4->setFrameShadow(QFrame::Sunken); QHBoxLayout *edgeTopBar = new QHBoxLayout; edgeTopBar->addWidget(m_edgeSearch); edgeTopBar->addWidget(edgeSep1); edgeTopBar->addWidget(edgeRefreshBtn); edgeTopBar->addWidget(edgeSep2); edgeTopBar->addWidget(edgeExportCSVBtn); edgeTopBar->addWidget(edgeExportJSONBtn); edgeTopBar->addWidget(edgeImportCSVBtn); edgeTopBar->addWidget(edgeImportJSONBtn); edgeTopBar->addWidget(edgeSep3); edgeTopBar->addWidget(edgeSetPropBtn); edgeTopBar->addWidget(edgeSep4); edgeTopBar->addWidget(edgeRemoveAttrBtn); edgeTopBar->addWidget(edgeAddAttrBtn); edgeTopBar->addStretch(); QWidget *edgeTab = new QWidget(this); QVBoxLayout *edgeLayout = new QVBoxLayout(edgeTab); edgeLayout->setContentsMargins(4, 4, 4, 4); edgeLayout->setSpacing(4); edgeLayout->addLayout(edgeTopBar); edgeLayout->addWidget(m_edgeView, 1); // ----------------------------------------------------------------------- // Tab widget // ----------------------------------------------------------------------- m_tabs = new QTabWidget(this); m_tabs->addTab(nodeTab, tr("Nodes")); m_tabs->addTab(edgeTab, tr("Edges")); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(0); mainLayout->addWidget(m_tabs); // ----------------------------------------------------------------------- // Connections // ----------------------------------------------------------------------- // Search boxes β†’ proxy filter connect(m_nodeSearch, &QLineEdit::textChanged, m_nodeProxy, &QSortFilterProxyModel::setFilterFixedString); connect(m_edgeSearch, &QLineEdit::textChanged, m_edgeProxy, &QSortFilterProxyModel::setFilterFixedString); // Refresh buttons connect(nodeRefreshBtn, &QPushButton::clicked, this, [this]() { if (m_graph) refresh(m_graph); }); connect(edgeRefreshBtn, &QPushButton::clicked, this, [this]() { if (m_graph) refresh(m_graph); }); // Row click β†’ selection signals connect(m_nodeView, &QTableView::clicked, this, &GraphTableWidget::onNodeRowClicked); connect(m_edgeView, &QTableView::clicked, this, &GraphTableWidget::onEdgeRowClicked); // Export buttons connect(nodeExportCSVBtn, &QPushButton::clicked, this, &GraphTableWidget::exportNodesCSV); connect(nodeExportJSONBtn, &QPushButton::clicked, this, &GraphTableWidget::exportNodesJSON); connect(edgeExportCSVBtn, &QPushButton::clicked, this, &GraphTableWidget::exportEdgesCSV); connect(edgeExportJSONBtn, &QPushButton::clicked, this, &GraphTableWidget::exportEdgesJSON); // Import buttons connect(nodeImportCSVBtn, &QPushButton::clicked, this, &GraphTableWidget::importNodesCSV); connect(nodeImportJSONBtn, &QPushButton::clicked, this, &GraphTableWidget::importNodesJSON); connect(edgeImportCSVBtn, &QPushButton::clicked, this, &GraphTableWidget::importEdgesCSV); connect(edgeImportJSONBtn, &QPushButton::clicked, this, &GraphTableWidget::importEdgesJSON); // Bulk-edit buttons connect(nodeSetPropBtn, &QPushButton::clicked, this, &GraphTableWidget::onNodeSetPropertyClicked); connect(nodeRemoveAttrBtn, &QPushButton::clicked, this, &GraphTableWidget::onNodeRemoveAttributeClicked); connect(nodeAddAttrBtn, &QPushButton::clicked, this, &GraphTableWidget::onNodeAddAttributeClicked); connect(edgeSetPropBtn, &QPushButton::clicked, this, &GraphTableWidget::onEdgeSetPropertyClicked); connect(edgeRemoveAttrBtn, &QPushButton::clicked, this, &GraphTableWidget::onEdgeRemoveAttributeClicked); connect(edgeAddAttrBtn, &QPushButton::clicked, this, &GraphTableWidget::onEdgeAddAttributeClicked); } /** * @brief Repopulates both models from @p graph and resizes columns. * * Also re-syncs the table selection from the graph's current selection state, * because beginResetModel/endResetModel clears all view selections. */ void GraphTableWidget::refresh(Graph *graph) { m_graph = graph; m_nodeModel->populate(graph); m_edgeModel->populate(graph); m_nodeView->resizeColumnsToContents(); m_edgeView->resizeColumnsToContents(); // Re-apply graph selection so resolveNodeTargets/resolveEdgeTargets are correct syncNodeSelection(graph->getSelectedVertices()); syncEdgeSelection(graph->getSelectedEdges()); } /** * @brief Maps the proxy index to a source index and emits nodeSelected(). */ void GraphTableWidget::onNodeRowClicked(const QModelIndex &proxyIndex) { const QModelIndex src = m_nodeProxy->mapToSource(proxyIndex); if (!src.isValid()) return; const QVariant num = m_nodeModel->data(m_nodeModel->index(src.row(), 0), Qt::DisplayRole); emit nodeSelected(num.toInt()); } /** * @brief Maps the proxy index to source/target columns and emits edgeSelected(). */ void GraphTableWidget::onEdgeRowClicked(const QModelIndex &proxyIndex) { const QModelIndex src0 = m_edgeProxy->mapToSource(proxyIndex); if (!src0.isValid()) return; const QModelIndex src1 = m_edgeModel->index(src0.row(), 1); const int s = m_edgeModel->data(src0, Qt::DisplayRole).toInt(); const int t = m_edgeModel->data(src1, Qt::DisplayRole).toInt(); emit edgeSelected(s, t); } QAbstractItemModel *GraphTableWidget::nodeModel() const { return m_nodeModel; } QAbstractItemModel *GraphTableWidget::edgeModel() const { return m_edgeModel; } void GraphTableWidget::exportNodesCSV() { doExport(m_nodeProxy, tr("nodes"), true); } void GraphTableWidget::exportNodesJSON() { doExport(m_nodeProxy, tr("nodes"), false); } void GraphTableWidget::exportEdgesCSV() { doExport(m_edgeProxy, tr("edges"), true); } void GraphTableWidget::exportEdgesJSON() { doExport(m_edgeProxy, tr("edges"), false); } /** * @brief Opens a save dialog and writes @p proxyModel via TableExport. * * @p defaultName is used to suggest a filename ("nodes" or "edges"). * @p csv selects CSV (true) or JSON (false) format. */ void GraphTableWidget::doExport(QAbstractItemModel *proxyModel, const QString &defaultName, bool csv) { const QString ext = csv ? tr("csv") : tr("json"); const QString filter = csv ? tr("CSV files (*.csv)") : tr("JSON files (*.json)"); const QString path = QFileDialog::getSaveFileName( this, tr("Export %1 as %2").arg(defaultName.toUpper(), ext.toUpper()), defaultName + QLatin1Char('.') + ext, filter); if (path.isEmpty()) return; const bool ok = csv ? TableExport::toCSV(proxyModel, path) : TableExport::toJSON(proxyModel, path); if (ok) { emit exportStatusMessage(tr("Exported %1 to %2").arg(defaultName, path)); } else { QMessageBox::warning(this, tr("Export failed"), tr("Could not write to %1").arg(path)); } } void GraphTableWidget::importNodesCSV() { doImport(true, true); } void GraphTableWidget::importNodesJSON() { doImport(true, false); } void GraphTableWidget::importEdgesCSV() { doImport(false, true); } void GraphTableWidget::importEdgesJSON() { doImport(false, false); } void GraphTableWidget::setNodeShapeLists(const QStringList &shapes, const QStringList &icons) { m_nodeShapeList = shapes; m_iconPathList = icons; } void GraphTableWidget::showNodesTab() { m_tabs->setCurrentIndex(0); } void GraphTableWidget::showEdgesTab() { m_tabs->setCurrentIndex(1); } /** * @brief Pre-selects the rows in the Nodes tab whose node number is in @p nodeNumbers. * * Rows filtered out by the current search are silently skipped. * Selection is cleared first; unmatched rows are left unselected. */ void GraphTableWidget::syncNodeSelection(const QList &nodeNumbers) { if (nodeNumbers.isEmpty()) { m_nodeView->clearSelection(); return; } // Build number β†’ source-row index map O(model_rows) QHash numToRow; numToRow.reserve(m_nodeModel->rowCount()); for (int row = 0; row < m_nodeModel->rowCount(); ++row) { const int num = m_nodeModel->data( m_nodeModel->index(row, 0), Qt::DisplayRole).toInt(); numToRow[num] = row; } // Map selection to proxy indices O(selected) QItemSelection sel; const int lastCol = m_nodeProxy->columnCount() - 1; for (const int num : nodeNumbers) { auto it = numToRow.find(num); if (it == numToRow.end()) continue; const QModelIndex srcIdx = m_nodeModel->index(it.value(), 0); const QModelIndex proxyIdx = m_nodeProxy->mapFromSource(srcIdx); if (!proxyIdx.isValid()) continue; // filtered out sel.select(proxyIdx, m_nodeProxy->index(proxyIdx.row(), lastCol)); } m_nodeView->selectionModel()->select( sel, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); if (!sel.isEmpty()) m_nodeView->scrollTo(sel.indexes().constFirst()); } /** * @brief Pre-selects the rows in the Edges tab matching @p edges (source, target pairs). */ void GraphTableWidget::syncEdgeSelection(const QList &edges) { if (edges.isEmpty()) { m_edgeView->clearSelection(); return; } // Build (source, target) β†’ source-row map O(model_rows) QHash edgeToRow; edgeToRow.reserve(m_edgeModel->rowCount()); for (int row = 0; row < m_edgeModel->rowCount(); ++row) { const int src = m_edgeModel->data(m_edgeModel->index(row, 0), Qt::DisplayRole).toInt(); const int tgt = m_edgeModel->data(m_edgeModel->index(row, 1), Qt::DisplayRole).toInt(); edgeToRow[qMakePair(src, tgt)] = row; } QItemSelection sel; const int lastCol = m_edgeProxy->columnCount() - 1; for (const SocNetV::SelectedEdge &e : edges) { auto it = edgeToRow.find(e); if (it == edgeToRow.end()) continue; const QModelIndex srcIdx = m_edgeModel->index(it.value(), 0); const QModelIndex proxyIdx = m_edgeProxy->mapFromSource(srcIdx); if (!proxyIdx.isValid()) continue; sel.select(proxyIdx, m_edgeProxy->index(proxyIdx.row(), lastCol)); } m_edgeView->selectionModel()->select( sel, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); if (!sel.isEmpty()) m_edgeView->scrollTo(sel.indexes().constFirst()); } /** * @brief Returns the node numbers for the table's current node selection, * or for all visible proxy rows if nothing is selected. */ QList GraphTableWidget::resolveNodeTargets() const { QList result; const QModelIndexList selected = m_nodeView->selectionModel()->selectedRows(0); if (!selected.isEmpty()) { for (const QModelIndex &proxyIdx : selected) { const QModelIndex src = m_nodeProxy->mapToSource(proxyIdx); result << m_nodeModel->data(src, Qt::DisplayRole).toInt(); } } else { // Fall back to all visible (proxy) rows for (int row = 0; row < m_nodeProxy->rowCount(); ++row) { const QModelIndex proxyIdx = m_nodeProxy->index(row, 0); const QModelIndex src = m_nodeProxy->mapToSource(proxyIdx); result << m_nodeModel->data(src, Qt::DisplayRole).toInt(); } } return result; } /** * @brief Returns (source, target) pairs for the table's current edge selection, * or all visible proxy rows if nothing is selected. */ QList GraphTableWidget::resolveEdgeTargets() const { QList result; const QModelIndexList selected = m_edgeView->selectionModel()->selectedRows(0); auto rowToPair = [this](const QModelIndex &proxyIdx) -> SelectedEdge { const QModelIndex src0 = m_edgeProxy->mapToSource(proxyIdx); const QModelIndex src1 = m_edgeProxy->mapToSource( m_edgeProxy->index(proxyIdx.row(), 1)); const int s = m_edgeModel->data(src0, Qt::DisplayRole).toInt(); const int t = m_edgeModel->data(src1, Qt::DisplayRole).toInt(); return qMakePair(s, t); }; if (!selected.isEmpty()) { for (const QModelIndex &idx : selected) result << rowToPair(idx); } else { for (int row = 0; row < m_edgeProxy->rowCount(); ++row) result << rowToPair(m_edgeProxy->index(row, 0)); } return result; } void GraphTableWidget::onNodeSetPropertyClicked() { if (!m_graph) return; const QList targets = resolveNodeTargets(); if (targets.isEmpty()) return; const bool isFilter = m_nodeView->selectionModel()->selectedRows().isEmpty(); // Collect unique custom attribute keys across targets QSet keySet; for (const int v : targets) for (const QString &k : m_graph->vertexCustomAttributes(v).keys()) keySet.insert(k); const QStringList existingKeys(keySet.begin(), keySet.end()); DialogBulkEdit dlg(DialogBulkEdit::Scope::Nodes, existingKeys, m_nodeShapeList, m_iconPathList, targets.size(), isFilter, this); connect(&dlg, &DialogBulkEdit::userChoices, this, [this, targets](const QString &property, const QString &value) { for (const int v : targets) { if (property == QLatin1String("Label")) { m_graph->vertexLabelSet(v, value); } else if (property == QLatin1String("Size")) { m_graph->vertexSizeSet(v, value.toInt()); } else if (property == QLatin1String("Color")) { m_graph->vertexColorSet(v, value); } else if (property == QLatin1String("Shape")) { const int idx = m_nodeShapeList.indexOf(value); const QString icon = (idx >= 0 && idx < m_iconPathList.size()) ? m_iconPathList[idx] : QString(); m_graph->vertexShapeSet(v, value, icon); } else { m_graph->vertexCustomAttributeSet(v, property, value); } } refresh(m_graph); emit exportStatusMessage( tr("Set '%1' on %2 node(s).").arg(property).arg(targets.size())); }); dlg.exec(); } void GraphTableWidget::onNodeRemoveAttributeClicked() { if (!m_graph) return; const QList targets = resolveNodeTargets(); if (targets.isEmpty()) return; // Collect custom attribute keys present in targets QSet keySet; for (const int v : targets) for (const QString &k : m_graph->vertexCustomAttributes(v).keys()) keySet.insert(k); if (keySet.isEmpty()) { QMessageBox::information(this, tr("Remove Attribute"), tr("No custom attributes found on the selected nodes.")); return; } const QStringList keys(keySet.begin(), keySet.end()); bool ok = false; const QString key = QInputDialog::getItem( this, tr("Remove Attribute"), tr("Select attribute key to remove from %1 node(s):").arg(targets.size()), keys, 0, false, &ok); if (!ok || key.isEmpty()) return; for (const int v : targets) m_graph->vertexCustomAttributeRemove(v, key); refresh(m_graph); emit exportStatusMessage( tr("Removed attribute '%1' from %2 node(s).").arg(key).arg(targets.size())); } void GraphTableWidget::onNodeAddAttributeClicked() { if (!m_graph) return; const QList targets = resolveNodeTargets(); if (targets.isEmpty()) return; // Use object-based QInputDialog to enforce a readable minimum width on all platforms. QInputDialog keyDlg(this); keyDlg.setWindowTitle(tr("Add Attribute")); keyDlg.setLabelText(tr("Attribute name:")); keyDlg.setMinimumWidth(360); if (keyDlg.exec() != QDialog::Accepted || keyDlg.textValue().trimmed().isEmpty()) return; const QString trimmedKey = keyDlg.textValue().trimmed(); QInputDialog valDlg(this); valDlg.setWindowTitle(tr("Add Attribute")); valDlg.setLabelText(tr("Value for '%1':").arg(trimmedKey)); valDlg.setMinimumWidth(360); if (valDlg.exec() != QDialog::Accepted) return; const QString value = valDlg.textValue(); for (const int v : targets) m_graph->vertexCustomAttributeSet(v, trimmedKey, value); refresh(m_graph); emit exportStatusMessage( tr("Added attribute '%1' to %2 node(s).").arg(trimmedKey).arg(targets.size())); } void GraphTableWidget::onEdgeSetPropertyClicked() { if (!m_graph) return; const QList targets = resolveEdgeTargets(); if (targets.isEmpty()) return; const bool isFilter = m_edgeView->selectionModel()->selectedRows().isEmpty(); QSet keySet; for (const SocNetV::SelectedEdge &e : targets) for (const QString &k : m_graph->edgeCustomAttributes(e.first, e.second).keys()) keySet.insert(k); const QStringList existingKeys(keySet.begin(), keySet.end()); DialogBulkEdit dlg(DialogBulkEdit::Scope::Edges, existingKeys, QStringList(), QStringList(), targets.size(), isFilter, this); connect(&dlg, &DialogBulkEdit::userChoices, this, [this, targets](const QString &property, const QString &value) { for (const SocNetV::SelectedEdge &e : targets) { if (property == QLatin1String("Label")) { m_graph->edgeLabelSet(e.first, e.second, value); } else if (property == QLatin1String("Weight")) { m_graph->edgeWeightSet(e.first, e.second, value.toDouble()); } else if (property == QLatin1String("Color")) { m_graph->edgeColorSet(e.first, e.second, value); } else { m_graph->edgeCustomAttributesSet(e.first, e.second, {{property, value}}); } } refresh(m_graph); emit exportStatusMessage( tr("Set '%1' on %2 edge(s).").arg(property).arg(targets.size())); }); dlg.exec(); } void GraphTableWidget::onEdgeRemoveAttributeClicked() { if (!m_graph) return; const QList targets = resolveEdgeTargets(); if (targets.isEmpty()) return; QSet keySet; for (const SocNetV::SelectedEdge &e : targets) for (const QString &k : m_graph->edgeCustomAttributes(e.first, e.second).keys()) keySet.insert(k); if (keySet.isEmpty()) { QMessageBox::information(this, tr("Remove Attribute"), tr("No custom attributes found on the selected edges.")); return; } const QStringList keys(keySet.begin(), keySet.end()); bool ok = false; const QString key = QInputDialog::getItem( this, tr("Remove Attribute"), tr("Select attribute key to remove from %1 edge(s):").arg(targets.size()), keys, 0, false, &ok); if (!ok || key.isEmpty()) return; for (const SocNetV::SelectedEdge &e : targets) { QHash attrs = m_graph->edgeCustomAttributes(e.first, e.second); attrs.remove(key); m_graph->edgeCustomAttributesSet(e.first, e.second, attrs); } refresh(m_graph); emit exportStatusMessage( tr("Removed attribute '%1' from %2 edge(s).").arg(key).arg(targets.size())); } void GraphTableWidget::onEdgeAddAttributeClicked() { if (!m_graph) return; const QList targets = resolveEdgeTargets(); if (targets.isEmpty()) return; QInputDialog keyDlg(this); keyDlg.setWindowTitle(tr("Add Attribute")); keyDlg.setLabelText(tr("Attribute name:")); keyDlg.setMinimumWidth(360); if (keyDlg.exec() != QDialog::Accepted || keyDlg.textValue().trimmed().isEmpty()) return; const QString trimmedKey = keyDlg.textValue().trimmed(); QInputDialog valDlg(this); valDlg.setWindowTitle(tr("Add Attribute")); valDlg.setLabelText(tr("Value for '%1':").arg(trimmedKey)); valDlg.setMinimumWidth(360); if (valDlg.exec() != QDialog::Accepted) return; const QString value = valDlg.textValue(); for (const SocNetV::SelectedEdge &e : targets) m_graph->edgeCustomAttributesSet(e.first, e.second, {{trimmedKey, value}}); refresh(m_graph); emit exportStatusMessage( tr("Added attribute '%1' to %2 edge(s).").arg(trimmedKey).arg(targets.size())); } /** * @brief Opens DialogImportAttributes and, on acceptance, calls the * appropriate Graph import method then refreshes the table. * * @p forNodes true β†’ import node attributes; false β†’ import edge attributes. * @p csv true β†’ CSV format; false β†’ JSON format. */ void GraphTableWidget::doImport(bool forNodes, bool csv) { if (!m_graph) return; const auto scope = forNodes ? DialogImportAttributes::Scope::Nodes : DialogImportAttributes::Scope::Edges; DialogImportAttributes dlg(scope, csv, this); if (dlg.exec() != QDialog::Accepted) return; const TableImport::ParsedTable &table = dlg.parsedTable(); int matched = 0; if (forNodes) { matched = m_graph->vertexAttributesImport( table.headers, table.rows, dlg.idColumn(), dlg.matchByLabel()); } else { matched = m_graph->edgeAttributesImport( table.headers, table.rows, dlg.srcColumn(), dlg.tgtColumn()); } refresh(m_graph); const QString kind = forNodes ? tr("node") : tr("edge"); emit importStatusMessage( tr("Imported attributes into %1 %2(s) from %3 rows") .arg(matched).arg(kind).arg(table.rows.size())); } SocNetV-3.7/src/widgets/graphtablewidget.h000066400000000000000000000117411524402210300206240ustar00rootroot00000000000000/** * @file graphtablewidget.h * @brief Declares GraphTableWidget β€” tabbed node/edge data table panel (#225). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #pragma once #include #include "../global.h" // SelectedEdge = QPair class Graph; class NodeTableModel; class EdgeTableModel; class QAbstractItemModel; class QTabWidget; class QTableView; class QLineEdit; class QSortFilterProxyModel; /** * @brief A QWidget containing a QTabWidget with two tabs β€” Nodes and Edges β€” * each backed by a sortable, searchable table view. * * Call refresh() whenever the graph data changes and the panel is visible. * The widget itself does not auto-refresh; the caller controls when to * rebuild the cache. */ class GraphTableWidget : public QWidget { Q_OBJECT public: explicit GraphTableWidget(QWidget *parent = nullptr); /** Returns the raw (unfiltered) node source model. */ QAbstractItemModel *nodeModel() const; /** Returns the raw (unfiltered) edge source model. */ QAbstractItemModel *edgeModel() const; /** * @brief Supplies the node shape list and icon paths so the bulk-edit * dialog can offer Shape as a property option for nodes. */ void setNodeShapeLists(const QStringList &shapes, const QStringList &icons); public slots: /** * @brief Repopulates both the node and edge models from @p graph. * * Stores a pointer to @p graph for subsequent Refresh button clicks. */ void refresh(Graph *graph); /** * @brief Highlights the rows in the Nodes tab that match @p nodeNumbers. * * Rows not in the list are deselected. Skips nodes that are filtered * out by the current search. O(model_rows + selected). */ void syncNodeSelection(const QList &nodeNumbers); /** * @brief Highlights the rows in the Edges tab that match @p edges. * * Each SelectedEdge is a (source, target) pair. O(model_rows + selected). */ void syncEdgeSelection(const QList &edges); /** Switches the tab widget to the Nodes tab. */ void showNodesTab(); /** Switches the tab widget to the Edges tab. */ void showEdgesTab(); /** Prompts for a file path and exports visible node rows as CSV. */ void exportNodesCSV(); /** Prompts for a file path and exports visible node rows as JSON. */ void exportNodesJSON(); /** Prompts for a file path and exports visible edge rows as CSV. */ void exportEdgesCSV(); /** Prompts for a file path and exports visible edge rows as JSON. */ void exportEdgesJSON(); /** Opens the column-mapping dialog and imports node attributes from a CSV. */ void importNodesCSV(); /** Opens the column-mapping dialog and imports node attributes from a JSON. */ void importNodesJSON(); /** Opens the column-mapping dialog and imports edge attributes from a CSV. */ void importEdgesCSV(); /** Opens the column-mapping dialog and imports edge attributes from a JSON. */ void importEdgesJSON(); signals: /** Emitted when the user clicks a row in the Nodes tab. */ void nodeSelected(int number); /** Emitted when the user clicks a row in the Edges tab. */ void edgeSelected(int source, int target); /** Emitted with a status message after an export attempt. */ void exportStatusMessage(const QString &message); /** Emitted with a status message after an import attempt. */ void importStatusMessage(const QString &message); private slots: void onNodeRowClicked(const QModelIndex &proxyIndex); void onEdgeRowClicked(const QModelIndex &proxyIndex); void onNodeSetPropertyClicked(); void onNodeRemoveAttributeClicked(); void onNodeAddAttributeClicked(); void onEdgeSetPropertyClicked(); void onEdgeRemoveAttributeClicked(); void onEdgeAddAttributeClicked(); private: void doExport(QAbstractItemModel *proxyModel, const QString &defaultName, bool csv); void doImport(bool forNodes, bool csv); /** Returns node numbers for selected rows, or all visible rows if none selected. */ QList resolveNodeTargets() const; /** Returns (source,target) pairs for selected rows, or all visible rows if none selected. */ QList resolveEdgeTargets() const; Graph *m_graph = nullptr; QTabWidget *m_tabs; QTableView *m_nodeView; QTableView *m_edgeView; NodeTableModel *m_nodeModel; EdgeTableModel *m_edgeModel; QSortFilterProxyModel *m_nodeProxy; QSortFilterProxyModel *m_edgeProxy; QLineEdit *m_nodeSearch; QLineEdit *m_edgeSearch; QStringList m_nodeShapeList; QStringList m_iconPathList; }; SocNetV-3.7/src/widgets/nodetablemodel.cpp000066400000000000000000000146401524402210300206210ustar00rootroot00000000000000/** * @file nodetablemodel.cpp * @brief Implements NodeTableModel β€” local cache of node data for the data table * panel (#225). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #include "nodetablemodel.h" #include "../graph.h" #include "../graphvertex.h" #include NodeTableModel::NodeTableModel(QObject *parent) : QAbstractTableModel(parent) { } /** * @brief Rebuilds the internal row cache from @p graph. * * All vertices (enabled and disabled) are included; the Visible column * reflects the vertex's enabled state. Custom attribute keys are taken from * Graph::graphHasVertexCustomAttributes(). */ void NodeTableModel::populate(Graph *graph) { beginResetModel(); m_graph = graph; m_rows.clear(); m_attrKeys.clear(); if (!m_graph) { endResetModel(); return; } m_attrKeys = m_graph->graphHasVertexCustomAttributes(); for (auto it = m_graph->verticesBegin(); it != m_graph->verticesEnd(); ++it) { GraphVertex *v = *it; NodeRow row; row.number = v->number(); row.label = m_graph->vertexLabel(row.number); row.visible = v->isEnabled(); row.shape = m_graph->vertexShape(row.number); row.size = m_graph->vertexSize(row.number); row.color = QColor(m_graph->vertexColor(row.number)).name(); const QHash attrs = m_graph->vertexCustomAttributes(row.number); for (const QString &key : m_attrKeys) { row.attrValues.append(attrs.value(key, QString())); } m_rows.append(row); } endResetModel(); } int NodeTableModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return m_rows.size(); } int NodeTableModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return FIXED_COLS + m_attrKeys.size(); } QVariant NodeTableModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); const int row = index.row(); const int col = index.column(); if (row < 0 || row >= m_rows.size() || col < 0 || col >= columnCount()) return QVariant(); const NodeRow &r = m_rows.at(row); switch (role) { case Qt::DisplayRole: switch (col) { case COL_NUMBER: return QString::number(r.number); case COL_LABEL: return r.label; case COL_VISIBLE: return QString(); // represented by checkbox only case COL_SHAPE: return r.shape; case COL_SIZE: return QString::number(r.size); case COL_COLOR: return r.color; default: return r.attrValues.value(col - FIXED_COLS); } case Qt::CheckStateRole: if (col == COL_VISIBLE) return r.visible ? Qt::Checked : Qt::Unchecked; return QVariant(); case Qt::DecorationRole: if (col == COL_COLOR) return QColor(r.color); return QVariant(); case Qt::TextAlignmentRole: if (col == COL_NUMBER || col == COL_SIZE) return static_cast(Qt::AlignCenter); return QVariant(); case Qt::BackgroundRole: if (col == COL_NUMBER || col == COL_VISIBLE || col == COL_SHAPE) return QBrush(QColor("#f0f0f0")); return QVariant(); default: break; } return QVariant(); } QVariant NodeTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { switch (section) { case COL_NUMBER: return tr("#"); case COL_LABEL: return tr("Label"); case COL_VISIBLE: return tr("Visible"); case COL_SHAPE: return tr("Shape"); case COL_SIZE: return tr("Size"); case COL_COLOR: return tr("Color"); default: if (section - FIXED_COLS < m_attrKeys.size()) return m_attrKeys.at(section - FIXED_COLS); break; } } else { return section + 1; } return QVariant(); } Qt::ItemFlags NodeTableModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; const int col = index.column(); // Read-only columns: #, Visible, Shape if (col == COL_NUMBER || col == COL_VISIBLE || col == COL_SHAPE) return Qt::ItemIsEnabled | Qt::ItemIsSelectable; // Editable columns: Label, Size, Color, custom attrs return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } bool NodeTableModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || role != Qt::EditRole || !m_graph) return false; const int row = index.row(); const int col = index.column(); if (row < 0 || row >= m_rows.size() || col < 0 || col >= columnCount()) return false; NodeRow &r = m_rows[row]; switch (col) { case COL_LABEL: { const QString newVal = value.toString(); if (r.label == newVal) return false; r.label = newVal; m_graph->vertexLabelSet(r.number, newVal); break; } case COL_SIZE: { const int newVal = value.toInt(); if (r.size == newVal) return false; r.size = newVal; m_graph->vertexSizeSet(r.number, newVal); break; } case COL_COLOR: { const QString newVal = QColor(value.toString()).name(); if (r.color == newVal) return false; r.color = newVal; m_graph->vertexColorSet(r.number, newVal); break; } default: if (col >= FIXED_COLS) { const int attrIdx = col - FIXED_COLS; if (attrIdx >= m_attrKeys.size()) return false; const QString newVal = value.toString(); if (r.attrValues.value(attrIdx) == newVal) return false; r.attrValues[attrIdx] = newVal; m_graph->vertexCustomAttributeSet(r.number, m_attrKeys.at(attrIdx), newVal); } else { return false; } break; } emit dataChanged(index, index, {role}); return true; } SocNetV-3.7/src/widgets/nodetablemodel.h000066400000000000000000000046511524402210300202670ustar00rootroot00000000000000/** * @file nodetablemodel.h * @brief Declares NodeTableModel β€” a QAbstractTableModel for node data (#225). * @author Dimitris B. Kalamaras * @copyright * Copyright (C) 2005-2026 by Dimitris B. Kalamaras. * This file is part of SocNetV (Social Network Visualizer). * @license * GNU GPL v3 or later. See . * @see https://socnetv.org */ #pragma once #include #include #include #include class Graph; /** * @brief Table model that caches node data from Graph and writes back via the * Graph API. * * Fixed columns: #, Label, Visible, Shape, Size, Color. * Dynamic columns: one per custom attribute key reported by the graph. */ class NodeTableModel : public QAbstractTableModel { Q_OBJECT public: explicit NodeTableModel(QObject *parent = nullptr); /** * @brief Rebuilds the internal cache from @p graph. * * Calls beginResetModel() / endResetModel() so connected views repaint. */ void populate(Graph *graph); /** Custom attribute keys present in the current graph (dynamic columns). */ QStringList attrKeys() const { return m_attrKeys; } int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; private: struct NodeRow { int number; QString label; bool visible; QString shape; int size; QString color; ///< hex string, e.g. "#ff0000" QList attrValues; ///< parallel to m_attrKeys }; Graph *m_graph = nullptr; QList m_rows; QStringList m_attrKeys; static constexpr int FIXED_COLS = 6; // Column index constants static constexpr int COL_NUMBER = 0; static constexpr int COL_LABEL = 1; static constexpr int COL_VISIBLE = 2; static constexpr int COL_SHAPE = 3; static constexpr int COL_SIZE = 4; static constexpr int COL_COLOR = 5; }; SocNetV-3.7/translations/000077500000000000000000000000001524402210300154165ustar00rootroot00000000000000SocNetV-3.7/translations/socnetv_de.qm000066400000000000000000000074041524402210300201130ustar00rootroot00000000000000<ΈdΚοœ•Ν!Ώ`‘½έ§deB0΄Η0΄Η ή*Π% °+0€+fΎY+˜Ελ+˜Ε Gί=Gί 2JC ¨ρ¦ͺ6•o¬‘ΥΆ€»‰·TΈρν~ dŠŠΈ’ͺ38«Žώ =«Ž n« Ÿ« Π« MA#ΞˆK] .ζΕ ΫΞoΎΒ _Κ›ε ;« d Δρƒ eQΥ!  hΒj -‘Ε “Q dڌ ^i ¬ MainWindow Adjacency Matrix&Adjacency Matrix MainWindowSchlieίen&Close MainWindow DL...&DL... MainWindow GW...&GW... MainWindowList&List MainWindowNeu&New MainWindow Φffnen&Open MainWindow Pajek&Pajek MainWindowDrucken&Print MainWindowSpeichern&Save MainWindow"Fόge Knoten hinzuAdd Node MainWindow.Δndere HintergrundfarbeChange Background Color MainWindowάKlicken um Knoten zu fδrben; Knoten erhalten die selbe Farbe wenn sie identische In- und Out-Nachbarn besitzenoClick this to colorize nodes; Nodes are assigned the same color if they have identical in and out neighborhoods MainWindowΰKlicken um Knoten zu fδrben; Knoten erhalten die selbe Farbe wenn sie Nachbarschaften gleicher Farbsets besitzentClick this to colorize nodes; Nodes are assigned the same color if they have neighborhoods of the same set of colors MainWindowRSchlieίen Schlieίt das aktuelle Netzwerk!Close Closes the actual network MainWindowBeendenE&xit MainWindow<Beenden Beendet die AnwendungExit Quits the application MainWindowGaussianGaussian MainWindowŠGaussian Erschafft ein zufδlliges Netzwerk mit Gauί'scher Verteilung DialogClusteringHierarchical Hierarchical Clustering Variables in: <html><head/><body><p><span style=" font-weight:600;">Rows: </span>Correlate outbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Columns: </span>Correlate inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Both: </span>Correlate both outbound and inbound ties (or distances, depending on the selected matrix above) between pairs of actors. In this case, the input matrix is expanded by listing row vectors followed by column vectors. This is useful only when you have directed data.</p><p><br/></p></body></html> Enable to include matrix diagonal in calculations Print dendrogram (avoid in large nets) <html><head/><body><p>Agglomerative hierarchical clustering builds a hierarchy of actor clusters, based on their tie/distance dissimilarity, starting with single elements and aggregating them into clusters.</p><p>It takes a matrix (adjacency or geodesic distances) and a distance metric between actors as input and constructs a pair-wise dissimilarity matrix. </p><p>Initially, each actor starts in its own cluster (Level 0). In each subsequent Level, the pair of clusters with minimum distance are merged into a larger cluster. Then, the distance between the new cluster and the old ones is computed, using the specified clustering method (i.e. single-linkage clustering). The process is repeated until all actors end up in the same cluster. </p><p>Select an input matrix, a distance/dissimilarity metric and a clustering method (criterion) for the hierarchical cluster analysis. </p></body></html> Clustering method (criterion): <html><head/><body><p>Supported linkage criteria for agglomerative hierarchical clustering: </p><p><span style=" font-weight:600;">Single-linkage (minimum)</span>: The distance between two clusters will be determined by a single element pair, namely those two elements (one in each cluster) that have the shortest distance between them. In each step, the clusters that have the shortest distance will be merged. </p><p><span style=" font-weight:600;">Complete-linkage (maximum)</span>: The distance between two clusters will be determined by any two elements (one in each cluster) that have the longest distance between them. </p><p><span style=" font-weight:600;">Average-linkage (UPGMA)</span>: The distance between two clusters A and B is equal to the average of distances between all pairs of elements in A and B. </p><p><br/></p></body></html> Input matrix: <html><head/><body><p><span style=" font-weight:600;">Adjacency:</span> Use the active network's adjacency matrix as input to hierarchical clustering.</p><p><span style=" font-weight:600;">Distances:</span> Use the active network's geodedic distances matrix as input. </p></body></html> Distance/dissimilarity metric: <html><head/><body><p>Supported distance metrics for hierarchical clustering:</p><p><span style=" font-weight:600;">Euclidean distance</span>: The square root of the sum of squared differences between tie/distance profiles.</p><p><span style=" font-weight:600;">Jaccard distance</span>: The Jaccard index J is the ratio of same ties/distances reported by each pair of actors to the total number of their ties. Does not count absent ties. The Jaccard distance is 1 - J</p><p><span style=" font-weight:600;">Hamming distance</span>: The number of ties/distances to other actors which differ between each pair of actors.</p><p><span style=" font-weight:600;">Manhattan distance</span>: The sum of absolute differences between tie/distance profiles.<br/></p></body></html> Include input matrix diagonal DialogDataSetSelect Famous SNA data sets <html><head/><body><p>Automatically recreate and visualize known data sets of Social Network Analysis, such as Padgett's Florentine families, Zachary's Karate Club, Knoke's Bureaucracies, etc.</p><p>Select the data set you want to re-create from the list.</p></body></html> <html><head/><body><p>Click to select a data set</p></body></html> DialogDissimilarities Tie profile dissimilarities Distance metric: <html><head/><body><p>Select a matching method. </p><p><span style=" font-weight:600;">Exact Matches</span>: Examines pairs of actors for exact tie or distance matches (present or absent) to other actors and returns a proportion to their overall ties. </p><p><span style=" font-weight:600;">Positive Matches (Jaccard/Co-citation)</span>: Looks for same ties/distances reported by both actors. Returns the ratio to the total number of ties reported.</p><p><span style=" font-weight:600;">Hamming distance</span>: Reports the number of ties/distances to other actors which differ between each pair of actors.</p><p><span style=" font-weight:600;">Cosine similarity</span>: Computes the pair-wise similarity of actors as the dot product of their tie/distance vectors divided by the magnitude of these vectors. <br/></p></body></html> <html><head/><body><p>Compute a <span style=" font-weight:600;">dissimilarities matrix</span>, where each element (i,j) is the pair-wise distance / dissimilarity of actors i and j tie profiles to all other actors, according to a selected metric. </p><p>Select a distance metric. For example, the &quot;Euclidean distance&quot; is the square root of the sum of the squared differences of tie values that actors i and j have to other actors. Hover over &quot;Distance Metric&quot; select box for more info on each metric.</p><p>Also, specify where the &quot;variables&quot; are. For instance, select Rows to measure the outbound ties between all pairs of actors. Select Both to measure both inbound and outbound ties. </p></body></html> Variables in: <html><head/><body><p><span style=" font-weight:600;">Rows: </span>Correlate outbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Columns: </span>Correlate inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Both: </span> Correlate both outbound and inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><br/></p></body></html> Enable to include matrix diagonal in calculations Include input matrix diagonal Euclidean distance Manhattan distance Hamming distance Jaccard distance Chebyshev distance DialogEdgeDichotomization Dichotomize Edges <html><head/><body><p>Enter a threshold value to dichotomize the edges of a valued network, in order to create a new binary relation. All ties with equal or higher values will be set to 1, and all lower will be removed. </p></body></html> Weight Threshold DialogExportImage Export to Image Save to file: <html><head/><body><p><span style=" font-weight:600;">Image filename</span></p><p>The path and the filename of the resulting image. </p><p>Click the button on the right to select a new filename.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-size:10pt; font-weight:600;">Image filename</span></p><p><span style=" font-size:10pt;">Click this button to select the path and the filename of the resulting image.<br/></span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">PDF filename</span></p><p>Click this button to select the path and the filename of the PDF. </p><p><br/></p><p><br/></p></body></html> ... Format <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Image format </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Select the format of the resulting image. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">SocNetV automatically supports all image formats supported currently by Qt in your computer platform. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Click on this drop down to see all supported image formats and select a format for your image.</span></p></body></html> Quality Compression Save to image DialogExportPDF Export to PDF Page Orientation <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Page Orientation</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Select the page orientation in the PDF: Portrait or landscape. </span></p></body></html> Save to file: <html><head/><body><p><span style=" font-weight:600;">PDF filename</span></p><p>The path and the filename of the PDF. </p><p>Click the button on the right to select a new filename.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-size:10pt; font-weight:600;">PDF filename</span></p><p><span style=" font-size:10pt;">Click this button to select the path and the filename of the PDF. <br/></span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">PDF filename</span></p><p>Click this button to select the path and the filename of the PDF. </p><p><br/></p><p><br/></p></body></html> ... Quality <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">PDF Quality</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Select the quality of the PDF. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">The default value is Screen, which results in a PDF exactly like what you see on the application canvas.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; text-decoration: underline;">Screen</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt; font-weight:600;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Sets the resolution of the print device to the screen resolution. This has the big advantage that the results obtained when painting on the printer will match more or less exactly the visible output on the screen. It is the easiest to use, as font metrics on the screen and on the printer are the same. This is the default value.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; text-decoration: underline;">Print</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">On Windows, sets the printer resolution to that defined for the printer in use. For PDF printing, sets the resolution of the PDF driver to 1200 dpi.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p></body></html> Resolution (DPI) <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">DPI</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Select the PDF resolution in DPI.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">The default value is 75, to match screen resolution.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p></body></html> Save to pdf PDF (*.pdf) DialogFilterEdgesByWeight Filter edges From this dialog, you can filter edges according to their weight. Select your desired weight threshold, then click on one of the radio buttons further below to control what to do. By default, it will filter edges with weight equal or over the weight threshold. The rest edges will be disabled (and invisible). Weight Threshold: <html><head/><body><p>Enter the weight threshold to use while filtering.</p><p>By default, it filters edges with weights equal or above the selected threshold. </p><p>You can control the behaviour with the radio boxes below. </p></body></html> Enter the weight threshold to use while filtering. By default, it filters all edges with weights equal or above your selected threshold here. You can control the behaviour with the radio boxes below. Select behaviour: Filter edges with weight equal or OVER the above threshold Filter edges with weight equal or BELOW the above threshold This action is not destructive, until you close the app/network. You can undo the filtering by running this action again and selecting a threshold lower than the lowest edge weight (i.e. 0). Obviously, when you save a network with filtering applied, only the filtered edges will be saved. DialogFilterNodesByCentrality Filter nodes Score Threshold: <html><head/><body><p>Enter the score threshold used for hiding nodes.<br/>Use the options below to hide nodes with scores <span style=" font-weight:700;">β‰₯</span> or <span style=" font-weight:700;">≀</span> the threshold.</p></body></html> Enter the threshold to use while filtering. Index: This does not delete nodes; it only hides them in the current view. To undo, run this action again and choose a threshold that hides no nodes (for example, a very large threshold when hiding scores β‰₯ threshold, or a very small threshold when hiding scores ≀ threshold). If you save the network while nodes are hidden, only the visible nodes and their edges will be saved. Select behaviour: Hide nodes with scores β‰₯ threshold Hide nodes with scores ≀ threshold <html><head/><body><p>Hide nodes based on a centrality/prestige score. </p><p>Choose an index and a score threshold, then choose whether to hide nodes with scores β‰₯ the threshold or ≀ the threshold. </p><p><span style=" font-style:italic;">Note: Indices must be computed before they can be used for filtering. Edges connected to hidden nodes will also be hidden. </span></p></body></html> Not computed yet. Run the analysis first. DialogNodeEdit Node Properties <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click the button on the right to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> Custom Icon <html><head/><body><p><span style=" font-weight:600;">Custom Icon </span></p><p>This box only shows the path of the selected custom icon file for all network nodes. If it is empty, it means you have not selected any image yet.</p><p>Click the button on the right to select a new image to be used as custom icon in all the nodes of the network.</p><p>Valid icons are images: JPG, PNG, JPEG, SVG etc.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>This is the path of the default background image. </p><p>Click the button on the right to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Custom Icon </span></p><p>Click this button to select a new custom icon for all nodes of the network. </p><p>Valid icons are images: JPG, PNG, JPEG, SVG etc.</p><p>If you don't select an image file, you must select one of the default shapes from the Node Shape menu above. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click this button to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> ... Custom attributes <html><head/><body><p>Click to remove the selected key-value attribute (you should have already clicked one!).</p></body></html> Remove selected <html><head/><body><p>Enter the key of the attribute, i.e. age or birthdate.</p></body></html> Enter key New Attribute: <html><head/><body><p>Enter the value of this attribute. </p><p>For example, if the key is 'age', then the value might be a number like 19. </p><p>Note: The value supports ISO 8601 dates, i.e. 2011-10-05T14:48:00.000Z</p><p><br/></p></body></html> Enter value Add <html><head/><body><p>Shows the current custom attributes (metadata) of the node. Custom attributes are key-value pairs. </p><p>For example. you could add demographics, work years, age, time events, etc. </p></body></html> Key Value Node shape <html><head/><body><p>Select a node shape. </p><p>If you select &quot;Icon&quot; then you must click on the custom icon button (below) to select the desired icon file from your filesystem. </p></body></html> Node color (click the button to select) Node size <html><head/><body><p>Set the size of the node. </p><p><br/></p><p>Default node size: 8</p></body></html> Node label <html><head/><body><p>Enter a node label. </p><p>If multiple nodes are selected, the label you define here will be set to all of them, along with a numerical suffix, i.e. if you enter &quot;Jim&quot;, then the selected actors will be labeled &quot;Jim1&quot;, &quot;Jim2&quot;, &quot;Jim3&quot; and so on. </p></body></html> Select a new icon Images (*.png *.jpg *.jpeg *.svg);;All (*.*) Select node color DialogNodeFind Find nodes (and select them) <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Find and select nodes (by numbers, labels or index score)</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To find nodes by their number, enter numbers either comma-separated or line by line or array <br /><span style=" font-style:italic;">i.e. 1,2,3 or 1-10) </span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To find nodes by their label enter words either comma separated or line by line <br /><span style=" font-style:italic;">i.e. jim,julia</span> </p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To find and select nodes by their index score, enter the desired score in the form: <br /><span style=" font-style:italic;">&gt; threshold</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">or </p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-style:italic;">&lt; threshold</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">and select the index in the menu below</p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> By Number(s) By Label(s) By Index Score Search for these numbers (enter line by line or csv): <html><head/><body><p>Select how to search nodes (by their number, by their label, or by index score) and enter below what you want to find. Matched nodes will be highlighted.</p></body></html> Index DialogPreviewFile &Encoding: <p>In this area you can preview your text file before actually loading it.</p> <p>SocNetV uses UTF-8 for saving and loading network files, by default. </p><p>If your file is encoded in another encoding, select the correct encoding from the menu and see if strings appear correctly.</p> Preview file & Choose Encoding DialogRandErdosRenyi ErdΕ‘s–RΓ©nyi network generator <html><head/><body><p>Generate random network according to ErdΕ‘s–RΓ©nyi (ER) model. </p><p>In fact, there are two models: in <span style=" font-style:italic;">G(n,p)</span> edges are created with Bernoulli trials, while in <span style=" font-style:italic;">G(n,M) </span>a graph is randomly selected from all graphs with <span style=" font-style:italic;">n</span> nodes and <span style=" font-style:italic;">M</span> edges. Read more in the manual.</p></body></html> <html><head/><body><p>Nodes <span style=" font-style:italic;">n</span></p></body></html> Model <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This will create a new random network using <span style=" font-weight:600;">G(n,p)</span> model, where</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">n</span> is the number of nodes in the final graph</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">p</span> is the probability with which an edge is included in the graph</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If you select this model, you must enter the number of nodes n and the edge probability p. </p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">You may also select if the final network will be undirected or directed and if you want to allow nodes to link to themselves (diagonal non zero).</p></body></html> G(n, p) <html><head/><body><p>This will create a new random network using <span style=" font-weight:600;">G(n,M)</span> model, where</p><p><span style=" font-weight:600;"> n</span> is the number of nodes in the final graph</p><p><span style=" font-weight:600;"> M</span> is the number of edges in the final graph</p><p>If you select this model, you must enter both the number of nodes n and the number of edges M</p><p>You may also select if the final network will be undirected or directed and if you want to allow nodes to link to themselves (diagonal non zero).</p></body></html> G(n, M) <html><head/><body><p>Edges <span style=" font-style:italic;">M </span><span style=" color:#7c7c7c;">for G(n,M) model only</span></p></body></html> <html><head/><body><p>Edge Probability <span style=" color:#7c7c7c;">applicable only in G(n,p) model</span></p></body></html> Graph Mode Undirected Directed Allow diagonals (loops) or set to zero? Yes, allow DialogRandLattice Lattice network generator <html><head/><body><p>Generate a lattice network. You can select how many dimensions the lattice will have (i.e. d=2 for a two-dimensional lattice) and the length of each dimension (i.e. l=3 for a 3x3 lattice of 9 nodes). Read more in the manual.</p></body></html> Nodes <html><head/><body><p><span style=" font-weight:600;">Lattice Nodes </span></p><p>The resulting lattice will have this amount of nodes . </p><p>This value changes automatically as you modify the <span style=" font-style:italic;">length l </span>of each dimension (below).</p></body></html> <html><head/><body><p>Length <span style=" font-style:italic;">l </span>in each dimension </p></body></html> <html><head/><body><p><span style=" font-weight:600;">Length</span></p><p>The size of the lattice in each dimension.</p></body></html> <html><head/><body><p>Dimension <span style=" font-style:italic;">d</span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Dimension </span><span style=" font-weight:600; font-style:italic;">d</span></p><p>The dimension of the lattice.</p><p>I.e. enter 2 for a two-dimensional lattice.</p><p><br/></p></body></html> <html><head/><body><p>Neighborhood <span style=" font-style:italic;">n</span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Neighborhood </span><span style=" font-weight:600; font-style:italic;">n</span></p><p>The distance within which the neighbors on the lattice will be connected</p></body></html> Graph Mode Undirected Directed Circular <html><head/><body><p>If checked, the lattice will be circular</p></body></html> false DialogRandRegular d-Regular network generator <html><head/><body><p>Generate a <span style=" font-style:italic;">d-regular</span> random network of <span style=" font-style:italic;">n</span> nodes. This is a graph where each vertex has the same number of neighbors <span style=" font-style:italic;">d</span>.</p><p>This model produces undirect and directed provided that n &gt; 5, d/<span style=" font-style:italic;">n &lt; 0.5 </span>and <span style=" font-style:italic;">n*d </span>is even. Read more in the manual.</p></body></html> <html><head/><body><p>The number n of nodes in the new Regular Network.</p><p>Note: For a <span style=" font-style:italic;">d</span>-regular graph of <span style=" font-style:italic;">n</span> nodes, it is necessary that <span style=" font-style:italic;">n &gt;= d + 1 </span>and <span style=" font-style:italic;">n*d </span>is even</p></body></html> Nodes <html><head/><body><p>Enter number n of nodes in the new Regular Network.</p><p>Constraints: </p><p><span style=" font-style:italic;">n &gt; 6 </span>and <span style=" font-style:italic;"/></p><p><span style=" font-style:italic;">d/n &gt; 0.5 </span>and</p><p><span style=" font-style:italic;">n*d </span>is even</p></body></html> <html><head/><body><p>Degree <span style=" font-style:italic;">d</span></p></body></html> <html><head/><body><p>Enter the degree <span style=" font-style:italic;">d</span> each new node will have.</p><p>Constraints: </p><p><span style=" font-style:italic;">d/n &gt; 0.5 </span>and</p><p><span style=" font-style:italic;">n*d </span>is even</p><p>In directed Graph Mode, this Degree <span style=" font-style:italic;">d</span> option corresponds to the outDegree and the inDegree, which will be both equal to <span style=" font-style:italic;">d</span> in the generated directed regular network. </p><p><br/></p></body></html> Graph Mode <html><head/><body><p>Click &quot;undirected&quot; to generate an undirected <span style=" font-style:italic;">d</span>-regular network </p></body></html> Undirected <html><head/><body><p>Click &quot;directed&quot; to generate a directed <span style=" font-style:italic;">d</span>-regular network. </p><p>In this case, the Degree <span style=" font-style:italic;">d</span> option above corresponds to the outDegree and the inDegree which will be both equal to <span style=" font-style:italic;">d</span> in the generated regular network. </p><p>For instance, if you select <span style=" font-style:italic;">d</span>=4 and <span style=" font-style:italic;">Graph Mode</span>=directed, then each new node will have outDegree=4 (four outbound edges) and inDegree=4 (four inbound edges). The inbound and outbound edges of each node will not necessarily be from / to the same nodes.</p></body></html> Directed Allow diagonals (loops) or set to zero? Check to allow loops (nodes linking to themselves) in the new network No loops DialogRandScaleFree Scale-free random network generator <html><head/><body><p>Generate a random scale-free network of <span style=" font-style:italic;">n</span> nodes according to the BarabΓ‘si–Albert (BA) model which uses a preferential attachment mechanism. </p><p>The model starts with <span style=" font-style:italic;">m</span><span style=" font-style:italic; vertical-align:sub;">0</span> connected nodes. In each step a new node is added, along with <span style=" font-style:italic;">m</span> edges to existing nodes. Read more in the manual.</p></body></html> <html><head/><body><p>Nodes <span style=" font-style:italic;">n</span></p></body></html> <html><head/><body><p>The amount of nodes in the resulting scale-free graph</p></body></html> <html><head/><body><p>Power of preferential attachment <span style=" font-style:italic;">p</span></p></body></html> <html><head/><body><p>The power p of preferential attachment </p><p>Leave 1 for linear preferential attachment (BA model).</p></body></html> <html><head/><body><p>Initial connected nodes <span style=" font-style:italic;">m</span><span style=" font-style:italic; vertical-align:sub;">0</span></p></body></html> <html><head/><body><p>The number m<span style=" vertical-align:sub;">0</span> of nodes in the initial connected network.</p><p>Leave 1 to start with just one node.</p></body></html> <html><head/><body><p>Edges to add in each step <span style=" font-style:italic;">m</span></p></body></html> <html><head/><body><p>The number of edges to add in each step</p></body></html> <html><head/><body><p>Zero appeal <span style=" font-style:italic;">Ξ±</span></p></body></html> <html><head/><body><p>The initial attractiveness of a node - useful for isolate nodes with d =0</p></body></html> Graph Mode Undirected Directed Allow diagonals (loops) or set to zero? Check to allow loops (nodes linking to themselves) in the new network No loops DialogRandSmallWorld Small-World network generator <html><head/><body><p>Generate random network according to the <span style=" font-style:italic;">Watts &amp; Strogatz</span> model.</p><p>This model produces graphs with small-world properties, including short average path lengths and high clustering. Read more in the manual.</p></body></html> Nodes <html><head/><body><p>The resulting graph will have N nodes and N*d/2 edges</p></body></html> <html><head/><body><p>Mean Degree <span style=" font-style:italic;">d</span></p></body></html> <html><head/><body><p>This is the mean edge degree each new node will have</p></body></html> <html><head/><body><p>Rewiring Probability <span style=" font-style:italic;">Ξ²</span></p></body></html> Graph Mode Undirected Directed Allow diagonals (loops) or set to zero? Check to allow loops (nodes linking to themselves) in the new network No loops DialogSettings Settings & Preferences General Data Exporting <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>Click the small button on the far right corner to select a folder, where all SocNetV files and reports will be saved by default. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> Save folder <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>This is the path of the folder where all SocNetV files and reports will be saved. </p><p>Click the button on the right to select a new folder. </p><p>If the folder does not exist, it wil be created.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>This is the path of the folder where all SocNetV files and reports will be saved. </p><p>Click the button on the right to select a new folder. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the folder does not exist, it wil be created.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>Click this button to select a folder, where all SocNetV files and reports will be saved by default. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>Click this button to select a folder, where all SocNetV files and reports will be saved by default. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the folder does not exist, it wil be created.</p></body></html> ... Reports <html><head/><body><p><span style=" font-weight:600;">Label Length</span></p><p>Change the length of labels <span style=" font-style:italic;">in reports</span>. Use the spin box to the right to change the number of digits SocNetV should write when generating reports with labels, i.e. node labels in centrality reports.</p><p>The length cannot be a negative value. The default value is 10.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> Labels length <html><head/><body><p><span style=" font-weight:600;">Label Length</span></p><p>Sets the length of labels <span style=" font-style:italic;">in reports</span>. This value describes the number of digits SocNetV should write when generating reports with labels, i.e. node labels in centrality reports.</p><p>The length cannot be a negative value. The default value is 8.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Real Number Precision</span></p><p>Change the precision of real numbers <span style=" font-style:italic;">in reports</span>. Use the spin box on the right to select a new precision namely the number of fraction digits SocNetV should write when generating reports with real numbers, i.e. centrality reports..</p><p>The precision cannot be a negative value. The default value is 6.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> Real number precision <html><head/><body><p><span style=" font-weight:600;">Real Number Precision</span></p><p>Sets the precision of real numbers <span style=" font-style:italic;">in reports</span>. This value describes the number of fraction digits SocNetV should write when generating reports with real numbers, i.e. centrality reports..</p><p>The precision cannot be a negative value. The default value is 6.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> <html><head/><body><p><span style=" font-family:'Sans Serif'; font-size:9pt; font-weight:600;">Chart Type</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">This is the chart type that is used in reports, i.e. to plot the distribution of a prominence index, such as Degree Centrality.</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">Use the select box on the right to select a chart type.</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">SocNetV supports the following chart types:</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Spline charts, which present data as a series of data points connected by lines. </span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Area charts, which present data as an area bound by two lines.</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Bar charts, which present data as vertical bars. </span></p></body></html> Chart type <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; font-weight:600;">Chart Type</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">Select which kind of chart type will be used in reports, i.e. to plot the </span><span style=" font-family:'DejaVu Sans'; font-size:10pt;">distribution of a </span><span style=" font-family:'DejaVu Sans'; font-size:10pt;">prominence index, such as Degree Centrality.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'DejaVu Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">SocNetV supports the following chart types:</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Spline charts, which present data as a series of data points connected by lines. </span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Area charts, which present data as an area bound by two lines.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Bar charts, which present data as vertical bars. </span></p></body></html> Image Exporting <html><head/><body><p><span style=" font-weight:600;">SocNetV logo on exported images</span></p><p>Enable or disable the printing of a small SocNetV logo on exported PNG and BMP network images </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Print SocNetV logo on exported Images Debugging and Progressing <html><head/><body><p><span style=" font-weight:600;">Debug Messages</span></p><p>Enable or disable debug messages to strerr. </p><p>Once you enable this and press OK, you must close and run SocNetV again from the command line / terminal, in order to see the debug messages.</p><p>Enabling has a significant cpu cost but lets you know what SocNetV is actually doing.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Debug Messages</span></p><p>Enables or disable debug messages to strerr. </p><p>Enabling has a significant cpu cost but lets you know what SocNetV is actually doing.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Print debug messages to command line <html><head/><body><p><span style=" font-weight:600;">Progress Bars</span></p><p>Enable or disable the application Progress Bars.</p><p>Progress Bars may appear during time-cost operations. </p><p>Enabling Progress Bars has a significant cpu cost but lets you know about the progress of a given operation.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Progress Bars</span></p><p>Enable or disable the application Progress Bars.</p><p>Progress Bars may appear during time-cost operations. </p><p>Enabling Progress Bars has a significant cpu cost but lets you know about the progress of a given operation.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Show progress dialog Application Style Use SocNetV Stylesheet Window options <html><head/><body><p><span style=" font-weight:600;">Control panel</span></p><p>Enable or disable the Control panel (left panel)</p><p>The Control Panel is the widget at the left of the application window, where you can find essential functions and options for Editing, Analyzing and Visualizing your network data.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Show Control panel (left panel) <html><head/><body><p><span style=" font-weight:600;">Toolbar</span></p><p>Enable or disable the application toolbar.</p><p>The toolbar is the widget right below the menu, and carries useful icons. You can disable it if you like from here...</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Show toolbar <html><head/><body><p><span style=" font-weight:600;">Statistics panel</span></p><p>Enable or disable the statistics panel (right panel)</p><p>The Statistics panel is the widget at the right of the application window, where you can see statistics about the whole network such as node and edge/arc count, density etc as well as statistics about the last clicked node (in-degree, out-degree, clustering coefficient etc).</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Show Statistics panel (right panel) <html><head/><body><p><span style=" font-weight:600;">Statusbar</span></p><p>Enable or disable the application statusbar.</p><p>The statusbar is the widget at the bottom of the window, where messages appear. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Show statusbar Nodes <html><head/><body><p><span style=" font-weight:600;">Default node settings</span></p><p>Any change to these settings will apply to all existing nodes immediately.</p><p>Once you press OK, these settings will be saved and they will be used in all future SocNetV sessions.</p></body></html> Node settings <html><head/><body><p><span style=" font-weight:600;">Node color</span></p><p>Click the colored button to select a new color for all nodes.</p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node color when creating new nodes (except when loading network files which declare specific node settings).</p></body></html> Node color <html><head/><body><p><span style=" font-weight:600;">Node color</span></p><p>Click this button to select a new node color for all nodes.</p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node color when creating new nodes (except when loading network files which declare specific node settings).</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node color</span></p><p>Click to select a new default node color.</p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node color.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node shape</span></p><p>Click on any of the following shapes to select a new node shape for all nodes. </p><p>This will apply to all existing nodes immediately.</p><p>Once you press OK, the default node shape will be saved and it will be used in all future SocNetV sessions when creating new nodes (except when loading network files which declare specific shapes for nodes).</p></body></html> Node shape <html><head/><body><p>Select a node shape. </p><p>If you select &quot;Icon&quot; then you must click on the custom icon button (below) to select the desired icon file from your filesystem. </p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click the button on the right to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> Custom Icon <html><head/><body><p><span style=" font-weight:600;">Custom Icon </span></p><p>This box only shows the path of the selected custom icon file for all network nodes. If it is empty, it means you have not selected any image yet.</p><p>Click the button on the right to select a new image to be used as custom icon in all the nodes of the network.</p><p>Valid icons are images: JPG, PNG, JPEG, SVG etc.</p><p><br/></p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>This is the path of the default background image. </p><p>Click the button on the right to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Custom Icon </span></p><p>Click this button to select a new custom icon for all nodes of the network. </p><p>Valid icons are images: JPG, PNG, JPEG, SVG etc.</p><p>If you don't select an image file, you must select one of the default shapes from the Node Shape menu above. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click this button to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node size</span></p><p>Use the spin box to select a new size (in pixels) for all nodes. </p><p>Actual node size will vary according to selected shape. For instance, if the selected shape is circle and the selected size is 8, then the circle will have a diameter of 16 pixels. </p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node size when creating new nodes (except when loading network files which declare specific node settings).</p></body></html> Node size <html><head/><body><p><span style=" font-weight:600;">Node size</span></p><p>Select a new size for all nodes. Actual node size will vary according to selected shape. For instance, if the selected shape is circle and the size is 8, then the circle will have a diameter of 16 pixels. </p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node size.</p></body></html> Node Number settings <html><head/><body><p><span style=" font-weight:600;">Node number color</span></p><p>Change the color for all node numbers. Click the colored button on the right corner to select a new color.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number color when creating new nodes.</p></body></html> Number color <html><head/><body><p><span style=" font-weight:600;">Node number color</span></p><p>Click to select a new color for all node numbers.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number color when creating new nodes.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Number distance from node</span></p><p>Change the distance of each number from the respective node. Use the spin box on the right to select a new distance (in pixels).</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default distance from node.</p></body></html> Number distance <html><head/><body><p><span style=" font-weight:600;">Number distance from nodes</span></p><p>Select a new distance (in pixels) of numbers from the respective nodes.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default distance from node.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node numbers</span></p><p>Enable or disable displaying node numbers.</p><p>Any change will apply to all existing nodes immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> Display node numbers <html><head/><body><p><span style=" font-weight:600;">Node number font size</span></p><p>Change the font size (in pixels) of all node numbers. Use the spin box on the right to select a new font size (in pixels).</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> Number font size <html><head/><body><p><span style=" font-weight:600;">Node number size</span></p><p>Select a new font size (in pixels) for node numbers. Set it to 0 to let the program automagically select a different font size according to the node size.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node numbers inside shapes</span></p><p>Enable or disable displaying node numbers inside the node shapes</p><p>Any change will apply to all existing nodes immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> Display node numbers inside node shapes Node Label settings <html><head/><body><p><span style=" font-weight:600;">Node labels</span></p><p>Enable or disable displaying labels below the nodes.</p><p>Any change will apply to all existing nodes immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> Display node labels Label color <html><head/><body><p><span style=" font-weight:600;">Node label color</span></p><p>Click to select a new color for node labels.</p><p>Any change to this setting will apply to all existing node labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node label color.</p></body></html> Label font size <html><head/><body><p><span style=" font-weight:600;">Node label size</span></p><p>Select a new font size (in pixels) for all node labels.</p><p>Any change to this setting will apply to all existing node labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node label size.</p></body></html> Label distance <html><head/><body><p><span style=" font-weight:600;">Label distance from node</span></p><p>Change the distance of labels from the respective nodes.</p><p>Any change to this setting will apply to all existing node labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default label distance from node.</p></body></html> Edges Edge settings <html><head/><body><p><span style=" font-weight:600;">Edges </span></p><p>Enable or disable displaying edges.</p><p>Any change will apply to all existing edges immediately.</p><p><br/></p></body></html> Display edges <html><head/><body><p><span style=" font-weight:600;">Edges </span></p><p>Enable or disable displaying arrows in directed edges. Useful If you work with directional social network data. You can disable it if your network is undirected.</p><p>Any change will apply to all existing edges immediately and saved for all future sessions (until you change it again).</p><p><br/></p></body></html> Display edge arrows <html><head/><body><p><span style=" font-weight:600;">Default edge shape</span></p><p>Select the default edge shape for all edges. </p><p>Edges can be either straight lines (default) or bezier curves.</p><p>This will apply to all existing edgesimmediately.</p><p>Once you press OK, the default edge shape will be saved and it will be used in all future SocNetV sessions.</p></body></html> Edge shape Straight Lines Be&zier Curves <html><head/><body><p><span style=" font-weight:600;">Edge color</span></p><p>Click the colored button on the right to select a new color for all edges.</p><p>Any change to this setting will apply to all existing edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color. Until you change it again.</p></body></html> Default edge color <html><head/><body><p><span style=" font-weight:600;">Edge color</span></p><p>Click to select a new color for all edges.</p><p>Any change to this setting will apply to all existing edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color. Until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Negative edge color</span></p><p>Click the colored button at the right corner to select a new color for negative weighted edges.</p><p>Any change to this setting will apply to all existing &quot;negative&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;negative&quot; edges. Until you change it again.</p></body></html> Negative valued edge color <html><head/><body><p><span style=" font-weight:600;">Negative edge color</span></p><p>Click this button to select a new default edge color for all negative weighted edges.</p><p>Any change to this setting will apply to all existing &quot;negative&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default color for &quot;negative&quot; edges. Until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Zero edge color</span></p><p>Click the colored button at the right corner to select a new color for edges with zero weights.</p><p>ATTTENTION: Zero-valued edges are being omittted during network analysis computations. This setting is available for those that have data (i.e. weighted edge lists) with zero weights on some edges and they still need to draw those edges, despite the fact that they will not be considered in any SNA computation by SocNetV.</p><p>Any change to this setting will apply to all existing &quot;zero&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;zero&quot; edges. Until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Zero edge color</span></p><p>Click the colored button at the right corner to select a new color for edges with zero weights.</p><p>ATTTENTION: Zero-valued edges are being omittted during network analysis computations. This setting is available for those that have data (i.e. weighted edge lists) with zero weights on some edges and they still need to draw those edges, despite the fact that they will not be considered in any SNA computation by SocNetV.</p><p>Any change to this setting will apply to all existing &quot;zero&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;zero&quot; edges. Until you change it again.</p></body></html> Zero valued edge color <html><head/><body><p><span style=" font-weight:600;">Zero edge color</span></p><p>Click this button to select a new color for edges with zero weights.</p><p>ATTTENTION: Zero-valued edges are being omittted during network analysis computations. This setting is available for those that have data (i.e. weighted edge lists) with zero weights on some edges and they still need to draw those edges, despite the fact that they will not be considered in any SNA computation by SocNetV.</p><p>Any change to this setting will apply to all existing &quot;zero&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;zero&quot; edges. Until you change it again.</p></body></html> Edge offset from node <html><head/><body><p><span style=" font-weight:600;">Edge offset from node </span></p><p>Changes the offset of each edge from each source and target nodes. rs.</p><p>Any change to this setting will apply to all existing edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions when creating new edges.</p></body></html> Weight number settings <html><head/><body><p><span style=" font-weight:600;">Edge weight numbers</span></p><p>Enable or disable edge weight numbers. When enabled, a number will be displayed along every edge indicating the edge weight.</p><p>Any change to this setting will apply to all existing weight numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default when creating new edges.</p></body></html> Display edge weight numbers Weight number color <html><head/><body><p><span style=" font-weight:600;">Edge weight number color</span></p><p>Click to select a new color for all edge weight numbers.</p><p>Any change to this setting will apply to all existing weight numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge weight number color when creating new edges.</p></body></html> Weight number font size <html><head/><body><p><span style=" font-weight:600;">Edge weight number text size</span></p><p>Click to select a new text size (in pixels) for all edge weight numbers.</p><p>Any change to this setting will apply to all existing weight numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge weight number size when creating new edges.</p></body></html> Edge label settings <html><head/><body><p><span style=" font-weight:600;">Edge labels</span></p><p>Enable or disable displaying edge labels.</p><p>Any change will apply to all existing edges immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> Display edge labels <html><head/><body><p><span style=" font-weight:600;">Default edge label color</span></p><p>Click to select a new default color for edge labels.</p><p>Any change to this setting will apply to all existing edge labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge label color.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default edge label size</span></p><p>Select the default font size for all edge labels.</p><p>Any change to this setting will apply to all existing edge labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge label size.</p></body></html> Canvas <html><head/><body><p><span style=" font-weight:600;">Canvas Background</span></p><p>In this section, there are general settings for the canvas, such as the background color or image.</p><p><br/></p><p><br/></p><p><br/></p></body></html> Canvas Background <html><head/><body><p><span style=" font-weight:600;">Background color</span></p><p>Click the button on the right to select a new background color. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> Background color <html><head/><body><p><span style=" font-weight:600;">Background color</span></p><p>Click this button to select a new background color. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background color</span></p><p>Click this button to select a new background color. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Background Image <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>This is the path of the default background image. </p><p>Click the button on the right to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click this button to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Performance Settings</span></p><p>The following options influence the performance of the SocNetV canvas, inside which nodes and edges are being drawn. They affect the look of nodes and edges as well the precision and speed of the graphics engine.</p><p>Most of these settings have noticeable effects only in large networks. For instance, if you have a network with over 3000 actors and 10000 edges you might want to disable Antialiasing, Antialiasing Auto-adjustment and Smooth Pixmap Transformation while enabling Cache Background. </p><p>Also, if you need to visually interact with edges in a large network, you can experiment with the Update Mode setting to find which is suitable for your workload. The default setting &quot;Full&quot; means that the canvas is fully redrawn every time you make a small change.</p></body></html> Hardware Acceleration <html><head/><body><p><span style=" font-weight:600;">Edge highlighting</span></p><p>Enable or disable highlighting of selected edges.</p><p>By default, SocNetV hughlights the edges you select with the mouse, as well as all edges connected to the selected node. Although a useful feature, this can slow down the application responsiveness when the network consists of thousand nodes and edges. </p><p>If disabled, selected edges will not be highlighted.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Cache Background</span></p><p>Enable or disable caching of the canvas background. </p><p>The graphics engine can cache pre-rendered content in a pixmap, which is then drawn onto the viewport. The purpose of such caching is to speed up the total rendering time for areas that are slow to render (i.e. texture, gradient and alpha blended backgrounds). </p><p>If enabled, the canvas background is cached by allocating one pixmap with the full size of the viewport. The cache is invalidated every time the view is transformed. However, when scrolling, only partial invalidation is required. </p><p>If not enabled, nothing is cached and all painting is done directly onto the viewport.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. <br/></p></body></html> Use OpenGL Performance settings <html><head/><body><p><span style=" font-weight:600;">Anti-Aliasing</span></p><p>Enable or disable Anti-Aliasing. If enabled, the graphics engine will antialias edges of primitives if possible. </p><p>Anti-aliasing is a technique which makes nodes, lines and text, smoother and fancier. But it comes at the cost of speed... </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Anti-Aliasing</span></p><p>Enable or disable Anti-Aliasing.</p><p>Anti-aliasing is a technique which makes nodes, lines and text, smoother and fancier. But it comes at the cost of speed... </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> Antialiasing <html><head/><body><p><span style=" font-weight:600;">Antialiasing Auto-Adjustment</span></p><p>Enable or disable antialiasing auto-adjustment of exposed areas. Items that render antialiased lines on the boundaries of their bounding rectangle can end up rendering parts of the line outside. To prevent rendering artifacts, the graphics engine expands all exposed regions by 2 pixels in all directions. </p><p>If you disable this, SocNetV will no longer perform these adjustments, minimizing the areas that require redrawing, which improves performance. A common side effect is that items that do draw with antialiasing can leave painting traces behind on the scene as they are moved.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> Antialising Auto-Adjustment <html><head/><body><p><span style=" font-weight:600;">Smooth pixmap transformations</span></p><p>Enable or disable smooth pixmap transformations. </p><p>If enabled, the engine will use a smooth pixmap transformation algorithm (such as bilinear) rather than nearest neighbor.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> Smooth Pixmap Transformation <html><head/><body><p><span style=" font-weight:600;">Save Painter State</span></p><p>When rendering, the graphics engine can protect (&quot;save&quot;) the painter state when rendering the background or foreground, and when rendering each item. This allows us to leave the painter in an altered state. </p><p>However, if the items consistently do restore the state, you should disable this option to prevent the application from doing the same.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> Save Painter State Edge Highlighting Cache Background <html><head/><body><p><span style=" font-family:'Sans Serif'; font-size:9pt; font-weight:600;">Update Mode</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">Use the drop-down menu on the right to select the update mode when the canvas changes. Usually you do not need to modify this property, but there are some cases where doing so can improve rendering performance. </span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">The default value is Full, where the graphics engine will update the entire canvas when some of its contents change.</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Full</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">When any visible part of the canvas changes or is reexposed, the engine will update the entire canvas. This approach is fastest when the engine spends more time figuring out what to draw than it would spend drawing (e.g., when very many small items are repeatedly updated). This is the default setting as it is the fastest with network of thousands of edges.</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Minimal</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">The engine will determine the minimal region that requires a redraw, minimizing the time spent drawing by avoiding a redraw of areas that have not changed. Although this approach provides the best performance in general, if there are many small visible changes on the scene, the engine might end up spending more time finding the minimal approach than it will spend drawing.</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Bounding Rectangle </span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">The bounding rectangle of all changes in the canvas will be redrawn. This mode has the advantage that the engine searches only one region for changes, minimizing time spent determining what needs redrawing. The disadvantage is that areas that have not changed also need to be redrawn.</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">None</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">The engine will never update the canvas when something changes; This mode disables all item changes. Normally, you don't want to use this!</span></p></body></html> Update mode <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; font-weight:600;">Update Mode</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">Select how to update areas of the canvas that have been reexposed or changed. Usually you do not need to modify this property, but there are some cases where doing so can improve rendering performance. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">The default value is Full, where the graphics engine will update the entire canvas when some of its contents change.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Full</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt; font-weight:600;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">When any visible part of the canvas changes or is reexposed, the engine will update the entire canvas. This approach is fastest when the engine spends more time figuring out what to draw than it would spend drawing (e.g., when very many small items are repeatedly updated). This is the default setting as it is the fastest with network of thousands of edges.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Minimal</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">The engine will determine the minimal region that requires a redraw, minimizing the time spent drawing by avoiding a redraw of areas that have not changed. Although this approach provides the best performance in general, if there are many small visible changes on the scene, the engine might end up spending more time finding the minimal approach than it will spend drawing.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Bounding Rectangle </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">The bounding rectangle of all changes in the canvas will be redrawn. This mode has the advantage that the engine searches only one region for changes, minimizing time spent determining what needs redrawing. The disadvantage is that areas that have not changed also need to be redrawn.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">None</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">The engine will never update the canvas when something changes; This mode disables all item changes. Normally, you don't want to use this!</span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Index Method</span></p><p>Use the drop-down menu on the right to select the indexing algorithm of the graphics engine for managing positional information about items on the canvas.</p><p><span style=" font-weight:600; font-style:italic;">BspTreeIndex</span><span style=" font-style:italic;">: </span>A Binary Space Partitioning tree is applied. All item location algorithms are of an order close to logarithmic complexity, by making use of binary search. Adding, moving and removing items is logarithmic. This approach is best for static scenes (i.e., scenes where most items do not move).</p><p><span style=" font-weight:600; font-style:italic;">NoIndex</span><span style=" font-style:italic;">: </span>No index is applied. Item location is of linear complexity, as all items on the scene are searched. Adding, moving and removing items, however, is done in constant time. This approach is ideal for dynamic scenes, where many items are added, moved or removed continuously.</p></body></html> Index Method <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:600;">Index Method</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'DejaVu Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">Select one of the indexing algorithms the graphics engine provides for managing positional information about items on the canvas.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'DejaVu Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:600; font-style:italic;">BspTreeIndex</span><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-style:italic;">: </span><span style=" font-family:'DejaVu Sans'; font-size:10pt;">A Binary Space Partitioning tree is applied. All item location algorithms are of an order close to logarithmic complexity, by making use of binary search. Adding, moving and removing items is logarithmic. This approach is best for static scenes (i.e., scenes where most items do not move).</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;"> </span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:600; font-style:italic;">NoIndex</span><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-style:italic;">: </span><span style=" font-family:'DejaVu Sans'; font-size:10pt;">No index is applied. Item location is of linear complexity, as all items on the scene are searched. Adding, moving and removing items, however, is done in constant time. This approach is ideal for dynamic scenes, where many items are added, moved or removed continuously.</span></p></body></html> Options Saving options Save zero-weight edges (GraphML only) Select a new data dir Select a background color Select a background image All (*);;PNG (*.png);;JPG (*.jpg) Select a color for Nodes Select a new icon Images (*.png *.jpg *.jpeg *.svg);;All (*.*) Select color for Node Numbers Select color for Node Labels Select color for Edges Select color for negative Edges DialogSimilarityMatches Similarity: Matches Variables in: <html><head/><body><p><span style=" font-weight:600;">Rows: </span>Correlate outbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Columns: </span>Correlate inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Both: </span>Correlate both outbound and inbound ties (or distances, depending on the selected matrix above) between pairs of actors. In this case, the input matrix is expanded by listing row vectors followed by column vectors. This is useful only when you have directed data.</p><p><br/></p></body></html> Enable to include matrix diagonal in calculations Include input matrix diagonal Matching measure: <html><head/><body><p>Select a matching method. </p><p><span style=" font-weight:600;">Exact Matches</span>: Examines pairs of actors for exact tie or distance matches (present or absent) to other actors and returns a proportion to their overall ties. </p><p><span style=" font-weight:600;">Positive Matches (Jaccard/Co-citation)</span>: Looks for same ties/distances reported by both actors. Returns the ratio to the total number of ties reported.</p><p><span style=" font-weight:600;">Hamming distance</span>: Reports the number of ties/distances to other actors which differ between each pair of actors.</p><p><span style=" font-weight:600;">Cosine similarity</span>: Computes the pair-wise similarity of actors as the dot product of their tie/distance vectors divided by the magnitude of these vectors. <br/></p></body></html> <html><head/><body><p>Compute a <span style=" font-weight:600;">similarity matrix</span>, where each element (i,j) is the pair-wise similarity score of actors i and j according to the selected &quot;matching&quot; method. For example, the &quot;Simple Matching&quot; method counts the number of times that actors i and j have the same tie / distance (present or absent) to other actors. </p><p>Select input matrix and where the &quot;variables&quot; are. For instance, select Adjacency matrix and Rows to correlate the outbound ties between all pairs of actors. Select Both to correlate the both inbound and outbound ties. Hover over &quot;Matching measure&quot; select box for more info on each method.</p></body></html> Input matrix: <html><head/><body><p><span style=" font-weight:600;">Adjacency:</span> Use the active network's adjacency matrix as input to correlate ties between all pairs of actors. </p><p><span style=" font-weight:600;">Distances:</span> Use the active network's geodesic distances matrix to correlate distances between all pairs of actors.</p></body></html> DialogSimilarityPearson Pearson Correlations Input matrix: <html><head/><body><p><span style=" font-weight:600;">Adjacency:</span> Use the active network's adjacency matrix as input to correlate ties between all pairs of actors. </p><p><span style=" font-weight:600;">Distances:</span> Use the active network's geodesic distances matrix to correlate distances between all pairs of actors.</p></body></html> Variables in: <html><head/><body><p><span style=" font-weight:600;">Rows: </span>Correlate outbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Columns: </span>Correlate inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Both: </span>Correlate both outbound and inbound ties (or distances, depending on the selected matrix above) between pairs of actors. In this case, the input matrix is expanded by listing row vectors followed by column vectors. This is useful only when you have directed data.</p><p><br/></p></body></html> <html><head/><body><p>Compute <span style=" font-weight:600;">Pearson Product Moment Correlation Coefficients</span> (PPMCC) between the rows, the columns or both of a social network matrix (adjacency or distance matrix). The result is a <span style=" font-weight:600;">correlation matrix </span>containing the correlation coefficients between each variable (i.e. row) and the others. This might be useful if you want to check the pair-wise similarity of the actors, in terms of their ties. </p><p>Select input matrix and what &quot;variables&quot; to correlate. For instance, select Adjacency matrix and Rows to correlate the outbound ties between all pairs of actors. Select Both to correlate the both inbound and outbound ties.</p></body></html> Enable to include matrix diagonal in calculations Include input matrix diagonal DialogSystemInfo System Information <html><head/><body><p>Use the following to provide more meaningful information in your bug reports:</p></body></html> SocNetV has OpenGL support, but you have disabled it. <br>Please enable OpenGL from Settings -> Canvas to enjoy faster drawing on the canvas.<br> DialogWebCrawler Generate network from web links URL patterns to include <html><head/><body><p><span style=" font-weight:600;">Allowed URL Patterns</span></p><p>Enter, in separate lines, one or more URL patterns to <span style=" font-weight:600;">include</span> while crawling. For example:</p><p>example.com/pattern/*</p><p>Do not enter spaces. Leave * to crawl all urls.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Crawl Internal Links </span></p><p>If enabled, the crawler will include and map <span style=" font-weight:600;">internal links, </span> that is pages from the same domain (or, in other words, from the same host ) as the URL being parsed each time.</p><p>If you do not want to crawl internal links, disable this option.</p><p> Please note that you MUST enable either this option or the &quot;Include external links&quot; option, for the crawler to work.</p><p>Default is to crawl internal links only. </p><p>You can further refine what kind of internal links to follow with the two options below: Child links and Parent links. </p></body></html> Crawl internal links <html><head/><body><p><span style=" font-weight:600;">Crawl child links</span></p><p>If enabled, the crawler will map <span style=" font-weight:600;">child URLs</span></p><p>A URL is a <span style=" font-style:italic;">childUrl </span>of another URL if the two URLs share the same scheme and authority, and the latter URL's path is a parent of the path of <span style=" font-style:italic;">childUrl</span>. This applies only to internal URLs.</p><p>For instance, www.socnetv.org/docs/manual.html is a child URL of www.socnetv.org/docs/ </p><p>If you don't want to crawl child URLs, disable this option. </p></body></html> Child links <html><head/><body><p><span style=" font-weight:600;">Crawl parent links</span></p><p>If enabled, the crawler will map <span style=" font-weight:600;">parent URLs</span>. </p><p>A URL is a <span style=" font-style:italic;">parent </span>of another URL if the two URLs share the same scheme and authority, and the former URL's path is a parent of the path of the latter URL. This applies to internal URLs.</p><p>For instance, the URL www.socnetv.org/docs/ is a parent URL of www.socnetv.org/docs/manual.html</p><p>If you don't want to crawl parent links, disable this option. </p></body></html> Parent links URL patterns to exclude <html><head/><body><p><span style=" font-weight:600;">Excluded URL Patterns</span></p><p>Enter, in separate lines, one or more URL patterns to <span style=" font-weight:600;">exclude</span> while crawling. For example:</p><p>example.com/pattern/*</p><p>Do not enter spaces. Leave blank to crawl all urls.</p></body></html> <html><head/><body><p>Set the max links inside a page to be followed and crawled by SocNetV.</p><p>Set this to zero if you don't want to have this limit. In this case SocNetV will follow and crawl every link found in a page.</p></body></html> Max links in each page to follow <html><head/><body><p><span style=" font-weight:600;">Links to social media domains</span></p><p>If enabled, the crawler will map (and possibly crawl) <span style=" font-weight:600;">links to social media websites</span>, such as twitter.com.</p><p>If disabled, the crawler will diregard any link to URLs in the following domains:</p><p>facebook.com<br/>twitter.com<br/>linkedin.com<br/>instagram.com<br/>pinterest.com<br/>telegram.org<br/>telegram.me<br/>youtube.com<br/>reddit.com<br/>plus.google.com</p><p><span style=" font-weight:600;">Note</span>: You can exclude more social media or define your custom social media exclusion list by typing domains in the &quot;URL patterns to exclude&quot; text edit above.</p></body></html> Links to social media <html><head/><body><p>If enabled the application will draw a <span style=" font-weight:600;">self-link</span> when a page contains a link to itself. </p><p>Default is not to allow self-links.</p></body></html> Allow Self-Links <html><head/><body><p><span style=" font-weight:600;">Show external links</span></p><p>If enabled, the crawler will map <span style=" font-weight:600;">links to external domains</span>. </p><p>For instance, if this option is enabled and you start crawling www.supersyntages.gr where there is a link to a page of a different domain, i.e. www.aggelies247.com/news, then a node &quot;www.aggelies247.com/news&quot; will be added to the network. </p><p>If you don't want to show external links at all, just disable this option. </p><p>Please note that you <span style=" font-weight:600;">MUST </span>enable either this option or the &quot;Include internal links&quot; option, for the crawler to work.</p></body></html> Show external links <html><head/><body><p><span style=" font-weight:600;">Crawl external links</span></p><p>If enabled, the crawler will <span style=" font-weight:600;">map external links AND crawl them for new links as well.</span></p><p>For instance, if you enable this option and start crawling the page at https://www.supersyntages.gr where there is a link to another domain, i.e. www.linuxinsider.gr, then the crawler will visit linuxinsider.gr too to find more links. </p><p>If you don't want to crawl external links, disable this option. </p></body></html> Crawl external links <html><head/><body><p>Wait for a random number of milliseconds (<span style=" font-weight:600;">0-1000</span>) between network requests. </p><p>Use of this option is recommended, as it lightens the server load by making the requests less frequent.</p><p>By default this option is enabled.</p></body></html> Delay between requests <html><head/><body><p>Use the built-in web crawler to scan the HTML code of a given initial URL (i.e. a website) and map all internal or external links to other pages found there. </p><p>As new URLs are discovered, the crawler follows them to scan their HTML code for links as well. For more details, see the Manual. </p><p>Enter the initial URL below and change crawling parameters if you like.</p></body></html> Initial URL <html><head/><body><p>Enter the initial url/domain to start crawling from, i.e. https://socnetv.org</p><p>You may omit https:// if you want. </p></body></html> <html><head/><body><p>Set the total urls to be crawled. </p><p>This is the total nodes the result network will have. </p><p>Set value to 0, if you don't want any limits...</p></body></html> Max URLs to crawl <html><head/><body><p>Set the total URLs to be crawled. </p><p>This is the <span style=" font-weight:600;">maximum nodes</span> the result network will have. </p><p>Set value to 0, if you don't want any limits...</p></body></html> EdgeEditDialog Node Properties Enter node label Enter node value (disabled) Select line shape Select link color (click the button) ... Select the link weight (default 1) Graph Computing Information Centralities. Please wait... Computing inverse adjacency matrix. Please wait... Computing IC scores. Please wait... Calculating EVC scores... Computing Eigenvector Centrality scores. Please wait... Computing outDegrees. Please wait... Leading eigenvector computed. Analysing centralities. Please wait... Computing out-Degree Centralities for %1 nodes. Please wait... Computing Influence Range Centrality scores. Please wait Computing Degree Prestige (in-Degree). Please wait ... Computing Proximity Prestige scores. Please wait ... Computing PageRank Prestige scores. Please wait ... Computing Clustering Coefficient. Please wait... Computing Hierarchical Clustering. Please wait... Computing Triad Census. Please wait... Finding cliques: Recursive backtracking for actor Checking if the graph edges are valued. Please wait... Calculating the Arc Reciprocity of the graph... web Creating shortest paths matrix. Please wait Creating geodesic distances matrix. Please wait Edges with weight %1 %2 have been filtered. Unilateral edges have been temporarily disabled. Please compute the selected centrality/prestige index first, then apply the filter. Filter applied: vertices with score %1 %2 are now hidden. Creating Erdos-Renyi Random Network. Please wait... erdos-renyi Creating Scale-Free Random Network. Please wait... scale-free Creating Small-World Random Network. Please wait ... small-world Creating pseudo-random d-regular network. Please wait... d-regular Creating ring-lattice network. Please wait... ring-lattice Creating lattice network. Please wait... lattice Error. Could not write to File %1 saved Adjacency matrix-formatted network saved into file %1 Embedding Random Layout. Please wait... Embedding Random Radial layout. Please wait .... Applying circular layout. Please wait... Computing centrality/prestige scores. Please wait... Embedding Radial layout by Prominence Score. Please wait... Embedding Level layout by Prominence Score. Please wait... Embedding Node Size by Prominence Score layout. Please wait... Embedding Node Color by Prominence Score layout. Please wait... Embedding Eades Spring-Gravitational model. Please wait .... Embedding Fruchterman & Reingold forces model. Please wait ... Embedding Kamada & Kawai spring model. Please wait... Creating Adjacency Matrix. Please wait... Computing Centrality Distribution. Please wait... Creating prominence index distribution line chart... Creating prominence index distribution area chart... Creating prominence index distribution bar chart... Creating reachability matrix. Please wait Computing walks of length %1. Please wait... Computing sociomatrix powers up to %1. Please wait... Computing all sociomatrix powers up to %1. Now computing A^%2. Please wait... Creating Influence Range List. Please wait Creating Influence Domain List. Please wait Added a new relation named: %1. Writing Reciprocity to file. Please wait... RECIPROCITY (r) REPORT Network name: Actors: Reciprocity, <b>r</b>, is a measure of the likelihood of vertices in a directed network to be mutually linked. <br />SocNetV supports two different methods to index the degree of reciprocity in a social network: <br />- The arc reciprocity, which is the fraction of reciprocated ties over all actual ties in the network. <br />- The dyad reciprocity which is the fraction of actor pairs that have reciprocated ties over all pairs of actors that have any connection. <br />In a directed network, the arc reciprocity measures the proportion of directed edges that are bidirectional. If the reciprocity is 1, then the adjacency matrix is structurally symmetric. <br />Likewise, in a directed network, the dyad reciprocity measures the proportion of connected actor dyads that have bidirectional ties between them. <br />In an undirected graph, all edges are reciprocal. Thus the reciprocity of the graph is always 1. <br />Reciprocity can be computed on undirected, directed, and weighted graphs. r range: 0 &le; r &le; 1 Arc reciprocity: %1 / %2 = %3 Of all actual ties in the network, %1% are reciprocated. Dyad reciprocity: Of all pairs of actors that have any ties, %1% have a reciprocated connection. Reciprocity proportions per actor: Actor Label Symmetric nonSymmetric nsym out/nsym nsym in/nsym nsym out/out nsym in/in Symmetric Proportion of reciprocated ties involving the actor to the total incoming and outgoing ties. nonSymmetric One minus symmetric nonSym Out/NonSym Proportion of non-symmetric outgoing ties to the total non-symmetric ties. nonSym In/NonSym Proportion of non-symmetric incoming ties to the total non-symmetric ties. nonSym Out/Out Proportion of non-symmetric outgoing ties to the total outgoing ties. nonSym In/In Proportion of non-symmetric incoming ties to the total incoming ties Reciprocity Report, <br /> Created by <a href="https://socnetv.org" target="_blank">Social Network Visualizer</a> v%1: %2 Computation time: %1 msecs Computation canceled. Writing Eccentricity scores to file. Please wait... ECCENTRICITY (e) REPORT The eccentricity <em>e</em> measures how far, at most, is each node from every other node. <br />In a connected graph, the eccentricity <em>e</em> of a vertex is the maximum geodesic distance between that vertex and all other vertices. <br />In a disconnected graph, the eccentricity <em>e</em> of all vertices is considered to be infinite. e range: 1 &le; e &le; ∞ e All nodes have the same eccentricity. Max e (Graph Diameter) = Min e (Graph Radius) = e classes = e = 1 when the node is connected to all others (star node). e > 1 when the node is not directly connected to all others. Larger eccentricity means the actor is farther from others. e = ∞ there is no path from that node to one or more other nodes. Eccentricity Report, <br /> Writing Information Centralities to file. Please wait... INFORMATION CENTRALITY (IC) The IC index, introduced by Stephenson and Zelen (1991), measures the information flow through all paths between actors weighted by strength of tie and distance. IC' is the standardized index (IC divided by the sumIC). Warning: To compute this index, SocNetV drops all isolated nodes and symmetrizes (if needed) the adjacency matrix. <br />Read the Manual for more. IC range: 0 &le; IC &le; ∞ IC' range: 0 &le; IC' &le; 1 Node IC IC' %IC All nodes have the same IC score. Max IC' = Min IC' = IC classes = IC' Sum = IC' Mean = IC' Variance = IC' DISTRIBUTION GROUP INFORMATION CENTRALIZATION (GIC) Since there is no way to compute Group Information Centralization, <br />you can use Variance as a general centralization index. <br /><br /> Variance = Variance = 0, when all nodes have the same IC value, i.e. a complete or a circle graph). <br /> Larger values of variance suggest larger variability between the IC' values. <br /> Information Centrality report, <br /> Writing Eigenvector Centrality scores to file. Please wait... EIGENVECTOR CENTRALITY (EVC) The Eigenvector Centrality of each node is the i<sub>th</sub> element of the leading eigenvector of the adjacency matrix, that is the eigenvector corresponding to the largest positive eigenvalue. <br />Proposed by Bonacich (1972), the Eigenvector Centrality is an extension of the simpler Degree Centrality because it gives each actor a score proportional to the scores of its neighbors. Thus, a node may have high EVC score if it has lots of ties or it has ties to other nodes with high EVC. <br />The eigenvector centralities are also known as Gould indices. EVC' is the scaled EVC (EVC divided by max EVC). EVC'' is the standardized index (EVC divided by the sum of all EVCs). EVC range: 0 &le; EVC &lt; 1 (The eigenvector has unit euclidean length) EVC' range: 0 &le; EVC' &le; 1 EVC EVC' EVC'' %EVC' All nodes have the same EVC score. Max EVC = Min EVC = EVC classes = EVC Sum = EVC Mean = EVC Variance = EVC' DISTRIBUTION GROUP EIGENVECTOR CENTRALIZATION (GEC) Since there is no way to compute Group Eigenvector Centralization, <br />you can use Variance as a general centralization index. <br /><br /> Variance = 0, when all nodes have the same EVC value, i.e. a complete or a circle graph). <br /> Larger values of variance suggest larger variability between the EVC' values. <br /> Eigenvector Centrality report, <br /> Writing out-Degree Centralities. Please wait... DEGREE CENTRALITY (DC) REPORT In undirected networks, the DC index is the sum of edges attached to a node u. <br />In directed networks, the index is the sum of outbound arcs from node u to all adjacent nodes (also called "outDegree Centrality"). <br />If the network is weighted, the DC score is the sum of weights of outbound edges from node u to all adjacent nodes.<br />Note: To compute inDegree Centrality, use the Degree Prestige measure. DC' is the standardized index (DC divided by N-1 (non-valued nets) or by sumDC (valued nets). DC range: 0 &le; DC &le; DC' range: 0 &le; DC' &le; 1 DC DC' %DC' All nodes have the same DC score. DC Sum = Max DC' = Min DC' = DC' classes = DC' Sum = DC' Mean = DC' Variance = DC' DISTRIBUTION GROUP DEGREE CENTRALIZATION (GDC) GDC = GDC range: GDC = 0, when all out-degrees are equal (i.e. regular lattice). GDC = 1, when one node completely dominates or overshadows the other nodes. Because this graph is weighted, we cannot compute Group Centralization You can use variance as a group-level centralization measure. Degree Centrality report, <br /> Writing Closeness Centrality scores to file. Please wait ... CLOSENESS CENTRALITY (CC) REPORT The CC index is the inverted sum of geodesic distances from each node u to all other nodes. Note: The CC index considers outbound arcs only and isolate nodes are dropped by default. Read the Manual for more. CC' is the standardized index (CC multiplied by (N-1 minus isolates)). CC range: 0 &le; CC &le; ( 1 / Number of node pairs excluding u) CC' range: 0 &le; CC' &le; 1 (CC'=1 when a node is the center of a star graph) CC CC' %CC' All nodes have the same CC score. CC Sum = Max CC' = Min CC' = CC' classes = CC' Sum = CC' Mean = CC' Variance = CC' DISTRIBUTION GROUP CLOSENESS CENTRALIZATION (GCC) GCC = GCC range: GCC = 0, when the lengths of the geodesics are all equal, i.e. a complete or a circle graph. GCC = 1, when one node has geodesics of length 1 to all the other nodes, and the other nodes have geodesics of length 2. to the remaining (N-2) nodes. This is exactly the situation realised by a star graph. Closeness Centrality report, <br /> Writing Influence Range Centrality scores. Please wait INFLUENCE RANGE CLOSENESS CENTRALITY (IRCC) The IRCC index of a node u is the ratio of the fraction of nodes reachable by node u to the average distance of these nodes from u (Wasserman & Faust, formula 5.22, p. 201)<br />Thus, this measure is similar to Closeness Centrality but it counts only outbound distances from each actor to other reachable nodes. <br />This measure is useful for directed networks which are not strongly connected (thus the ordinary CC index cannot be computed).<br />In undirected networks, the IRCC has the same properties and yields the same results as the ordinary Closeness Centrality.<br />Read the Manual for more. IRCC is standardized. IRCC range: 0 &le; IRCC &le; 1 (IRCC is a ratio) IRCC %IRCC' All nodes have the same IRCC score. Max IRCC = Min IRCC = IRCC classes = IRCC Sum = IRCC Mean = IRCC Variance = IRCC DISTRIBUTION Influence Range Closeness Centrality report, <br /> Writing Betweenness Centrality scores to file. Please wait... BETWEENNESS CENTRALITY (BC) The BC index of a node u is the sum of &delta;<sub>(s,t,u)</sub> for all s,t &isin; V where &delta;<sub>(s,t,u)</sub> is the ratio of all geodesics between s and t which run through u. BC' is the standardized index (BC divided by (N-1)(N-2)/2 in symmetric nets or (N-1)(N-2) otherwise. BC range: 0 &le; BC &le; (Number of pairs of nodes excluding u) BC' range: 0 &le; BC' &le; 1 (BC'=1 when the node falls on all geodesics) BC BC' %BC' All nodes have the same BC score. BC Sum = Max BC' = Min BC' = BC' classes = BC' Sum = BC' Mean = BC' Variance = BC' DISTRIBUTION GROUP BETWEENNESS CENTRALIZATION (GBC) GBC = GBC range: GBC = 0, when all the nodes have exactly the same betweenness index. GBC = 1, when one node falls on all other geodesics between all the remaining (N-1) nodes. Betweenness Centrality report, <br /> Writing Stress Centralities. Please wait... STRESS CENTRALITY (SC) The SC index of each node u is the sum of &sigma;<sub>(s,t,u)</sub>): <br />the number of geodesics from s to t through u. SC' is the standardized index (SC divided by sumSC). SC range: 0 &le; SC &le; SC' range: 0 &le; SC' &le; 1 (SC'=1 when the node falls on all geodesics) SC SC' %SC' All nodes have the same SC score. SC Sum = Max SC' = Min SC' = BC classes = SC' Sum = SC' Mean = SC' Variance = SC' DISTRIBUTION Stress Centrality report, <br /> Writing Eccentricity Centralities to file. Please wait... ECCENTRICITY CENTRALITY (EC) The EC score of a node u is the inverse maximum geodesic distance from u to all other nodes in the network. This index is also known as <em>Harary Graph Centrality</em>. EC is standardized. EC range: 0 &le; EC &le; 1 (EC=1 when the actor has ties to all other nodes) EC=EC' %EC' All nodes have the same EC score. Max EC = Min EC = EC classes = EC Sum = EC Mean = EC Variance = EC DISTRIBUTION Eccentricity Centrality report, <br /> Writing Gil-Schmidt Power Centralities to file. Please wait... POWER CENTRALITY (PC) The PC index, introduced by Gil and Schmidt, of a node u is the sum of the sizes of all Nth-order neighbourhoods with weight 1/n. PC' is the standardized index: The PC score divided by the total number of nodes in the same component minus 1 PC range: 0 &le; PC &le; PC' range: 0 &le; PC' &le; 1 (PC'=1 when the node is connected to all (star).) PC PC' %PC' All nodes have the same PC score. PC Sum = Max PC' = Min PC' = PC classes = PC' Sum = PC' Mean = PC' Variance = PC' DISTRIBUTION GROUP POWER CENTRALIZATION (GPC) GPC = GPC range: GPC = 0, when all in-degrees are equal (i.e. regular lattice). GPC = 1, when one node is linked to all other nodes (i.e. star). Use mean or variance instead. Power Centrality report, <br /> Writing Degree Prestige (in-Degree) scores to file. Please wait ... DEGREE PRESTIGE (DP) The DP index, also known as InDegree Centrality, of a node u is the sum of inbound edges to that node from all adjacent nodes. <br />If the network is weighted, DP is the sum of inbound arc weights (Indegree) to node u from all adjacent nodes. DP' is the standardized index (DP divided by N-1). DP range: 0 &le; DP &le; DP' range: 0 &le; DP' &le; 1 DP DP' %DP' All nodes have the same DP score. DP Sum = Max DP' = Min DP' = DP' classes = DP' Sum = DP' Mean = DP' Variance = DP' DISTRIBUTION GROUP DEGREE PRESTIGE (GDP) GDP = GDP range: GDP = 0, when all in-degrees are equal (i.e. regular lattice). GDP = 1, when one node is chosen by all other nodes (i.e. star). Degree Prestige report, <br /> Writing Proximity Prestige scores to file. Please wait ... PROXIMITY PRESTIGE (PP) The PP index of a node u is the ratio of the proportion of nodes who can reach u to the average distance these nodes are from u (Wasserman & Faust, formula 5.25, p. 204)<br />Thus, it is similar to Closeness Centrality but it counts only inbound distances to each actor, thus it is a measure of actor prestige. <br />This metric is useful for directed networks which are not strongly connected (thus the ordinary CC index cannot be computed).<br />In undirected networks, the PP has the same properties and yields the same results as Closeness Centrality.<br />Read the Manual for more. <br /> PP range: 0 &le; PP &le; 1 (PP is a ratio) PP=PP' %PP All nodes have the same PP score. Max PP = Min PP = PP classes = PP Sum = PP Mean = PP Variance = PP DISTRIBUTION Proximity Prestige report, <br /> Writing PageRank scores to file. Please wait ... PAGERANK PRESTIGE (PRP) The PRP is an importance ranking index for each node based on the structure of its incoming links/edges and the rank of the nodes linking to it. <br />For each node u the algorithm counts all inbound links (edges) to it, but it normalizes each inbound link from a node v by the outDegree of v. <br />The PR values correspond to the principal eigenvector of the normalized link matrix.<br />Note: In weighted relations, each backlink to a node u from another node v is considered to have weight=1 but it is normalized by the sum of outbound edge weights of v. Therefore, nodes with high outLink weights give smaller percentage of their PR to node u. PRP' is the scaled PRP (PRP divided by max PRP). PRP range: (1-d)/N = &le; PRP PRP' range: 0 &le; PRP' &le; 1 PRP PRP' %PRP' All nodes have the same PRP score. Max PRP = Min PRP = PRP classes = PRP Sum = PRP Mean = PRP Variance = PRP' DISTRIBUTION PageRank Prestige report, <br /> Writing Walks matrix to file: Computing Walks... WALKS OF LENGTH %1 MATRIX TOTAL WALKS MATRIX The Walks of length %1 matrix is a NxN matrix where each element (i,j) is the number of walks of length %1 between actor i and actor j, or 0 if no walk exists. <br />A walk is a sequence of edges and vertices, where each edge's endpoints are the two vertices adjacent to it. In a walk, vertices and edges may repeat. <br />Warning: Walks count unordered pairs of nodes. The Total Walks matrix of a social network is a NxN matrix where each element (i,j) is the total number of walks of any length (less than or equal to %1) between actor i and actor j, or 0 if no walk exists. <br />A walk is a sequence of edges and vertices, where each edge's endpoints are the two vertices adjacent to it. In a walk, vertices and edges may repeat. <br />Warning: Walks count unordered pairs of nodes. Walks report, <br /> Reachability Matrix (XR) Two nodes are reachable if there is a walk between them (their geodesic distance is non-zero). If nodes i and j are reachable then XR(i,j)=1 otherwise XR(i,j)=0. Writing Clustering Coefficients to file. Please wait... CLUSTERING COEFFICIENT (CLC) REPORT The local Clustering Coefficient, introduced by Watts and Strogatz (1998) quantifies how close each node and its neighbors are to being a complete subgraph (clique). For each node <em>u</em>, the local CLC score is the proportion of actual links between its neighbors divided by the number of links that could possibly exist between them. <br />The CLC index is used to characterize the transitivity of a network. A value close to one indicates that the node is involved in many transitive relations. CLC' is the normalized CLC, divided by maximum CLC found in this network. CLC range: 0 &le; CLC &le; 1 0 &le; CLC' &le; 1 CLC CLC' %CLC' All nodes have the same local CLC score. Max CLC = Min CLC = CLC Mean = CLC Variance = GROUP / NETWORK AVERAGE CLUSTERING COEFFICIENT (GCLC) GCLC = Range: 0 < GCLC < 1 <br/ > GCLC = 0, when there are no cliques (i.e. acyclic tree). <br /> GCLC = 1, when every node and its neighborhood are complete cliques. Clustering Coefficient report, <br /> Computing triad census. Please wait.... Writing Triad Census to file. Please wait... TRIAD CENSUS (TRC) REPORT A Triad Census counts all the different types (classes) of observed triads within a network. <br />The triad types are coded and labeled according to their number of mutual, asymmetric and non-existent (null) dyads. <br />SocNetV follows the M-A-N labeling scheme, as described by Holland, Leinhardt and Davis in their studies. <br />In the M-A-N scheme, each triad type has a label with four characters: <br /> - The first character is the number of mutual (M) dyads in the triad. Possible values: 0, 1, 2, 3.<br />- The second character is the number of asymmetric (A) dyads in the triad. Possible values: 0, 1, 2, 3.<br />- The third character is the number of null (N) dyads in the triad. Possible values: 0, 1, 2, 3.<br />- The fourth character is inferred from features or the nature of the triad, i.e. presence of cycle or transitivity. Possible values: none, D ("Down"), U ("Up"), C ("Cyclic"), T ("Transitive") Type Census Triad Census report, <br /> Computing Clique Census and writing it to a file. Please wait... Computing Clique Census. Please wait.. Writing Clique Census to file. Please wait.. CLIQUE CENSUS (CLQs) REPORT A clique is the largest subgroup of actors in the social network who are all directly connected to each other (maximal complete subgraph). <br />SocNetV applies the Bron–Kerbosch algorithm to produce a census of all maximal cliques in the network and reports some useful statistics such as disaggregation by vertex and co-membership information. <br /> Maximal Cliques found: Clique No Clique members Actor by clique analysis: Proportion of clique members adjacent <sub>Actor</sub>/<sup>Clique</sup> Actor by actor analysis: Co-membership matrix <sub>Actor</sub>/<sup>Actor</sup> Hierarchical clustering of overlap matrix: Actors Computing HCA for Cliques. Please wait.. Writing HCA for Cliques. Please wait.. Clique by clique analysis: Co-membership matrix Clique Clique Census Report, <br /> Computing hierarchical clustering. Please wait... Error: unsupported matrix type. Writing Hierarchical Cluster Analysis to file. Please wait... HIERARCHICAL CLUSTERING (HCA) Input matrix: Distance/dissimilarity metric: Clustering method/criterion: Analysis results Structural Equivalence Matrix: Hierarchical Clustering of Equivalence Matrix: Hierarchical Cluster Analysis report, <br /> Clustering Dendrogram (SVG) Examining pair-wise similarity of actors... SIMILARITY MATRIX: MATCHING COEFFICIENTS (SMMC) Variables in: Matching measure: Examining pair-wise tie profile dissimilarities of actors... Writing tie profile dissimilarities to file: DISSIMILARITIES MATRIX Metric: Diagonal: Range: 0 &lt; C &lt; 1 0 &lt; C Analysis results DSM = 0 when two actors have no tie profile dissimilarities. The actors have the same ties to all others. DSM &gt; 0 when the two actors have differences in their ties to other actors. Dissimilarity Matrix Report, <br /> Writing Similarity coefficients to file. Please wait... SMMC range: 0 &lt; C SMMC = 0 when two actors are absolutely similar (no tie/distance differences). SMMC &gt; 0 when two actors have some differences in their ties/distances, i.e. SMMC = 3 means the two actors have 3 differences in their tie/distance profiles to other actors. when there is no tie profile similarity at all. when two actors have some matches in their ties/distances, i.e. SMMC = 1 means the two actors have their ties to other actors exactly the same all the time. Similarity Matrix by Matching Measure Report, <br /> Calculating Pearson Correlations... Writing Pearson coefficients to file: PEARSON CORRELATION COEFFICIENTS (PCC) MATRIX PCC range: PCC = 0 when there is no correlation at all. PCC &gt; 0 when there is positive correlation, i.e. +1 means actors with same patterns of ties/distances. PCC &lt; 0 when there is negative correlation, i.e. -1 for actors with exactly opposite patterns of ties. Pearson Correlation Coefficients Report, <br /> Campnet dataset The dataset is the interactions among 18 people, including 4 instructors, participating in a 3-week workshop. Each person was asked to rank everyone else in terms of how much time they spent with them. This dataset shows only top 3 choices for each respondent(week 2 and week 3). Thus, there is a 1 for xij if person i listed person j as one of their top 3 interactors. The Camp data were collected by Steve Borgatti, Russ Bernard and Bert Pelto in 1992 at the NSF Summer Institute for Ethnographic Research Methods. During the 3-week workshop, all the participants and instructors were housed at the same motel and spent a great deal of time together. The participants were all faculty in Anthropology except Holly, who was a PhD student. Herschel graph The Herschel graph is the smallest nonhamiltonian polyhedral graph. It is the unique such graph on 11 nodes, and has 18 edges. High-tech Managers Krackhardt's High-tech Managers is a famous social network of 21 managers of a high-tech US company. The company manufactured high-tech equipment and had just over 100 employees with 21 managers. David Krackhardt collected the data to assess the effects of a recent management intervention program. The network consists of 3 relations: - Advice - Friendship - Reports To Each manager was asked to whom do you go to for advice and who is your friend. Data for the "whom do you report" relation was taken from company documents. This data is used by Wasserman and Faust in their seminal network analysis book. Krackhardt D. (1987). Cognitive social structures. Social Networks, 9, 104-134. Padgett's Florentine_Families This famous data set includes 16 families who were fighting each other to gain political control of the city of Florence circa 1430. Among the 16 families, the Medicis and the Strozzis were the two most prominent with factions formed around them. The data set is actually a subset of the original data on social relations among 116 Renaissance Florentine Families collected by John Padgett. This subset was used by Breiger & Pattison (1986) in their paper about local role analysis. Padgett researched historical documents to code two relations: Business ties (loans, credits, partnerships) Marrital ties (marriage alliances). Breiger R. and Pattison P. (1986). Cumulated social roles: The duality of persons and their algebras. Social Networks, 8, 215-256. Zachary Karate Club The Zachary Karate Club is a well-known social network of 34 members of a university karate club studied by Wayne W. Zachary from 1970 to 1972. During the study, disputes among two members led to club splitting into two groups. Zachary documented 78 ties between members who interacted outside the club and used the collected data and an information flow model to explain the split-up. There are two relations (matrices) in this network:The ZACHE relation represents the presence or absence of ties among the actors. The ZACHC relation indicates the relative strength of their associations (number of situations in and outside the club in which interactions occurred). Zachary W. (1977). An information flow model for conflict and fission in small groups. Journal of Anthropological Research, 33, 452-473. Galaskiewicz's CEOs and Clubs The affiliation network of the chief executive officers and their spouses from 26 corporations and banks in 15 clubs, corporate and cultural boards. Membership was during the period 1978-1981 This is a 26x15 affiliation matrix, where the rows correspond to the 26 CEOs and the columns to the 15 clubs. This data was originally collected by Galaskiewicz (1985) and is used by Wasserman and Faust in Social Network Analysis: Methods and Applications (1994). Galaskiewicz, J. (1985). Social Organization of an Urban Grants Economy. New York: Academic Press. Thurman's Office Networks and Coalitions In the late 70s, B. Thurman spent 16 months observing the interactions among employees in the overseas office of a large international corporation. During this time, two major disputes erupted in a subgroup of fifteen people. Thurman analyzed the outcome of these disputes in terms of the network of formal and informal associations among those involved. This labeled dataset contains two relations (15x15 matrices): THURA is a 15x15 non-symmetric, binary matrix showing the formal organizational chart of the employees. THURM is a 15x15 symmetric binary matrix which shows the actors linked by multiplex ties. Thurman B. (1979). In the office: Networks and coalitions. Social Networks, 2, 47-63 Corporate Interlocks in Netherlands A 16x16 symmetric, binary matrix.This data represent corporate interlocks among the major business entities in the Netherlands. The data were gathered during a 6-year research project which was concluded in 1976 in nine European countries and the USA Stokman F., Wasseur F. and Elsas D. (1985). The Dutch network: Types of interlocks and network structure. In F. Stokman, R. Ziegler & J. Scott (eds), Networks of corporate power. Cambridge: Polity Press, 1985 Corporate Interlocks in West Germany A 15x15 symmetric, binary matrix.This data represent corporate interlocks among the major business entities in the West Germany. The data were gathered during a 6-year research project which was concluded in 1976 in nine European countries and the USA Ziegler R., Bender R. and Biehler H. (1985). Industry and banking in the German corporate network. In F. Stokman, R. Ziegler & J. Scott (eds), Networks of corporate power. Cambridge: Polity Press, 1985. Bernard and Killworth Fraternity Bernard & Killworth recorded the interactions among students living in a fraternity at a West Virginia college. Subjects had been residents in the fraternity from 3 months to 3 years. This network dataset contains two relations: The BKFRAB relation is symmetric and valued. It counts the number of times a pair of subjects were seen in conversation by an unobtrusive observer (observation time: 21 hours a day, for five days). The BKFRAC relation is non-symmetric and valued. Contains rankings made by the subjects themselves of how frequently they interacted with other subjects in the observation week. Knoke D. and Wood J. (1981). Organized for action: Commitment in voluntary associations. New Brunswick, NJ: Rutgers University Press. Knoke D. and Kuklinski J. (1982). Network analysis, Beverly Hills, CA: Sage Freeman's EIES Networks This data comes from an early experiment on computer mediated communication. Fifty academics were allowed to contact each other via an Electronic Information Exchange System (EIES). The data collected consisted of all messages sent plus acquaintance relationships at two time periods. The data includes the 32 actors who completed the study and the following three 32x32 relations: TIME_1 non-symmetric, valued TIME_2 non-symmetric, valued NUMBER_OF_MESSAGES non-symmetric, valued TIME_1 and TIME_2 give the acquaintance information at the beginning and end of the study. This is coded as follows: 4 = close personal fiend, 3 = friend, 2= person I've met, 1 = person I've heard of but not met, and 0 = person unknown to me (or no reply). NUMBER_OF MESSAGES is the total number of messages person i sent to j over the entire period of the study. Freeman's EIES network (Acquaintanceship) at time 1 Freeman's EIES network (Acquaintanceship) at time 2 Freeman's EIES network (Messages) Freeman's 34 possible graphs of N=5 This data comes from Freeman's (1979) seminal paper "Centrality in social networks". It illustrates all 34 possible graphs of five nodes. Freeman used them to calculate and compare the three measures of Centrality: Degree, Betweenness and Closeness. Use Relation buttons on the toolbar to move between the graphs. Mexican Power Network in the 1940s Knoke Bureaucracies In 1978, Knoke & Wood collected data from workers at 95 organizations in Indianapolis. Respondents indicated with which other organizations their own organization had any of 13 different types of relationships. Knoke and Kuklinski (1982) selected a subset of 10 organizations and two relationships: information exchange and money exchange. This dataset is directed and not symmetric. Information exchange is recorded in KNOKI relation while money exchange in KNOKM . Stephenson & Zelen's AIDS patients network (sex contact) The data described by Auerbach et al. (1984) and Klovdahl (1985) consists of information on 40 homosexual men diagnosed with AIDS. Initially, 19 men residing in the Los Angeles and Orange County area were interviewed about their previous sexual contacts. This information led to the subsequent identification of an additional 21 sexual partners in San Francisco, New York and other parts of the United States. All 40 homosexual men were linked to each other through sexual contact. Galada baboon colony network (H22a) A network of the Galada baboon colony, as described by Dunbar and Dunbar (1975). This is the first set of observations (H22a) and was made on 12 baboons. The lines connecting two points (baboons) represent nonagonistic interactions (generally grooming behavior) and the frequency of such interactions is recorded by the edge weight. Data derived from Stephenson & Zelen seminal 1989 paper where they introduced Information Centrality. Wasserman & Faust's 7 actors graphs Wasserman & Faust's Countries Trade Data (manufactured goods) This data set is just a famous non-planar mathematical graph, named after Julius Petersen, who constructed it in 1898. The Petersen graph is undirected with 10 vertices and 15 edges and the smallest bridgeless cubic graph with no three-edge-coloring. This small graph serves as a useful example and counterexample for many problems in graph theory. Adjacency recomputed. Writing Adjacency Matrix... Need to recompute Adjacency Matrix. Please wait... Adjacency recomputed. Writing Laplacian Matrix... Adjacency recomputed. Writing Degree Matrix... Distances recomputed. Writing Distances Matrix... Distances recomputed. Writing Shortest Paths Matrix... Computing Inverse Adjacency Matrix. Please wait... Inverse Adjacency Matrix computed. Writing Matrix... Writing Reachability Matrix... Need to recompute tie profile distances. Please wait... Tie profile distances recomputed. Writing matrix... ADJACENCY MATRIX REPORT LAPLACIAN MATRIX REPORT DEGREE MATRIX REPORT DISTANCES MATRIX REPORT SHORTEST PATHS (GEODESICS) MATRIX REPORT INVERSE ADJACENCY MATRIX REPORT REACHABILITY MATRIX REPORT TRANSPOSE OF ADJACENCY MATRIX REPORT COCITATION MATRIX REPORT EUCLIDEAN DISTANCE MATRIX REPORT HAMMING DISTANCE MATRIX REPORT JACCARD DISTANCE MATRIX REPORT MANHATTAN DISTANCE MATRIX REPORT The adjacency matrix, AM, of a social network is a NxN matrix where each element (i,j) is the value of the edge from actor i to actor j, or 0 if no edge exists. The laplacian matrix L of a social network is a NxN matrix with L = D - A, where D the degree matrix and A the adjacency matrix. The elements of L are: <br />- L<sub>i,j</sub> = d<sub>i</sub>, if i = j, <br />- L<sub>i,j</sub> = -1, if i &ne; j and there is an edge (i,j)<br />- and all other elements zero.<br /> The degree matrix D of a social network is a NxN matrix where each element (i,i) is the degree of actor i and all other elements are zero. The distance matrix of a social network is a NxN matrix where each element (i,j) is the geodesic distance (length of shortest path) from actor i to actor j, or infinity if no shortest path exists. The geodesics matrix of a social network is a NxN matrix where each element (i,j) is the number of shortest paths(geodesics) from actor i to actor j, or infinity if no shortest path exists. The adjacency matrix is singular. The reachability matrix R of a social network is a NxN matrix where each element R(i,j) is 1 if actors j is reachable from i otherwise 0. <br />Two nodes are reachable if there is a walk between them (their geodesic distance is non-zero). <br />Essentially the reachability matrix is a dichotomized geodesics matrix. The adjacency matrix AM of a social network is a NxN matrix where each element (i,j) is the value of the edge from actor i to actor j, or 0 if no edge exists. This is the transpose of the adjacency matrix, AM<sup>T</sup>, a matrix whose (i,j) element is the (j,i) element of AM. The Cocitation matrix, C = A<sup>T</sup> * A, is a NxN matrix where each element (i,j) is the number of actors that have outbound ties/links to both actors i and j. The diagonal elements, C<sub>ii</sub>, of the Cocitation matrix are equal to the number of inbound edges of i (inDegree). C is a symmetric matrix. The Euclidean distances matrix is a NxN matrix where each element (i,j) is the Euclidean distanceof the tie profiles between actors i and j, namely the square root of the sum of their squared differences. The Hamming distances matrix is a NxN matrix where each element (i,j) is the Hamming distanceof the tie profiles between actors i and j, namely the number of different ties to other actors. The Jaccard distances matrix is a NxN matrix where each element (i,j) is the Jaccard distanceof the tie profiles between actors i and j. The Manhattan distances matrix is a NxN matrix where each element (i,j) is the Manhattan distanceof the tie profiles between actors i and j, namely the sum of their absolute differences. The Chebyshev distances matrix is a NxN matrix where each element (i,j) is the Chebyshev distanceof the tie profiles between actors i and j, namely the greatest of their differences. Matrix report, <br /> Writing matrix to file. Please wait... <sub>Actor</sup>/<sup>Actor</sup> Writing Adjacency Matrix to file. Please wait... ADJACENCY MATRIX The adjacency matrix of a social network is a NxN matrix Adjacency matrix report, <br /> Plotting Adjacency Matrix. Please wait... ADJACENCY MATRIX PLOT This a plot of the network's adjacency matrix, a NxN matrix where each element (i,j) is filled if there is an edge from actor i to actor j, or not filled if no edge exists. Adjacency matrix plot, <br /> Computing Similarity coefficients matrix. Please wait... New node (numbered %1) added at position (%2,%3). Double-click on it to start a new edge from it. -clique Creating subgraph. Please wait... Found %1 matching nodes. Could not find any nodes matching your choices. MainWindow &New Neu &Open Γ–ffnen &Save Speichern &Adjacency Matrix Adjacency Matrix &Pajek Pajek &List List &DL... DL... &GW... GW... &Close Schließen Close Closes the actual network Schließen Schließt das aktuelle Netzwerk &Print Drucken E&xit Beenden Exit Quits the application Beenden Beendet die Anwendung Ring Lattice Ring Lattice Gaussian Gaussian Gaussian Creates a random network of Gaussian distribution Gaussian Erschafft ein zufΓ€lliges Netzwerk mit Gauß'scher Verteilung Small World Small World Add Node FΓΌge Knoten hinzu Remove Node Entferne Knoten Change Background Color Γ„ndere Hintergrundfarbe Strong Structural Strong Structural Nodes are assigned the same color if they have identical in and out neighborhoods Knoten erhalten die selbe Farbe wenn sie identische In- und Out-Nachbarn besitzen Click this to colorize nodes; Nodes are assigned the same color if they have identical in and out neighborhoods Klicken um Knoten zu fΓ€rben; Knoten erhalten die selbe Farbe wenn sie identische In- und Out-Nachbarn besitzen Regular Regular Nodes are assigned the same color if they have neighborhoods of the same set of colors Knoten erhalten die selbe Farbe wenn sie Nachbarschaften gleicher Farbsets besitzen Click this to colorize nodes; Nodes are assigned the same color if they have neighborhoods of the same set of colors Klicken um Knoten zu fΓ€rben; Knoten erhalten die selbe Farbe wenn sie Nachbarschaften gleicher Farbsets besitzen Random Random Eccentricity Fruchterman-Reingold Repelling forces between all nodes, and attracting forces between adjacent nodes. Fruchterman-Reingold Layout Embeds a layout all nodes according to a model in which repelling forces are used between every pair of nodes, while attracting forces are used only between adjacent nodes. The algorithm continues until the system retains its equilibrium state where all forces cancel each other. Bezier Curves Manual Read the manual... Manual Displays the documentation of SocNetV Tip of the Day Read useful tips Quick Tips Displays some useful and quick tips About SocNetV About Basic information about SocNetV About Qt About About Qt &Network &Edit Filter... &Layout &Options &Help Network Layout Nothing to save. There are no vertices. Graph already saved. Save to GraphML? Default File Format: GraphML This network will be saved in GraphML format which is the default file format of SocNetV. Is this OK? If not, press Cancel, then go to Network > Export menu to see other supported formats to export your data to. Save aborted... Enter or select a filename to save the network... Save Network to GraphML File Named... Appending .graphml extension. Missing file extension. Appended the standard .graphml extension to the given filename. Final Filename: Using .graphml extension. Wrong file extension. Appended the standard .graphml extension to the given filename. Error! Could not save this file: %1 Network saved under filename: %1 Closing network file... Closing Network... Ready. Ready Yes No Choose a network file... Error loading requested file. Aborted. Opening aborted Welcome to %1, version %2 Closing SocNetV. Bye! Save changes Modified network has not been saved! Do you want to save the changes to the network file? Error loading settings file Error loading settings Error! I cannot read the settings file in %1 You can continue using SocNetV with default settings but any changes to them will not be saved for future sessions Please, check permissions in your home folder and contact the developer team. Error writing settings file Error writing settings I cannot write the settings file in %1 You can continue using SocNetV with default settings but any changes to them will not be saved for future sessions Please, check permissions in your home folder and contact the developer team. <p><b>The canvas of SocNetV</b></p><p>Inside this area you create and edit networks, load networks from files and visualize them according to the selected metrics. </p><p>To create a new node, <em>double-click</em> anywhere.</p><p>To add an edge between two nodes, <em>double-click</em> on the first node (source) then double-click on the second (target) .</p><p>To move around the canvas, use the keyboard arrows.</p><p>To change network appearance, <em>right click on empty space</em>. </p><p>To edit the properties of a node, <em>right-click</em> on it. </p><p>To edit the properties of an edge, <em>right-click</em> on it.</p> Create a new network New network New Creates a new social network. First, checks if current network needs to be saved. Open network Open a GraphML formatted file of social network data. Open Opens a file of a social network in GraphML format &GML Import GML-formatted file Import GML Imports a social network from a GML-formatted file Import Pajek-formatted file Import Pajek Imports a social network from a Pajek-formatted file Import Adjacency matrix Import Sociomatrix Imports a social network from an Adjacency matrix-formatted file Graph&Viz (.dot) Import dot file Import GraphViz Imports a social network from a GraphViz formatted file &UCINET (.dl)... ImportDL-formatted file (UCINET) Import UCINET Imports social network data from a DL-formatted file &Edge list Import an edge list file. Import edge list Import a network from an edgelist file. SocNetV supports EdgeList files with edge weights as well as simple EdgeList files where the edges are non-value (see manual) &Two Mode Sociomatrix Import two-mode sociomatrix (affiliation network) file Import Two-Mode Sociomatrix Imports a two-mode network from a sociomatrix file. Two-mode networks are described by affiliation network matrices, where A(i,j) codes the events/organizations each actor is affiliated with. Save social network to a file Save. Saves the social network to file Save As... Save network under a new filename Save As Saves the social network under a new filename Export to I&mage... Export the visible part of the network to image Export to Image Exports the visible part of the current social network to an image E&xport to PDF... Export the visible part of the network to a PDF file Export to PDF Exports the visible part of the current social network to a PDF document. Export social network to an adjacency/sociomatrix file Export network to Adjacency format Exports the social network to an adjacency matrix-formatted file Export social network to a Pajek-formatted file Export Pajek Exports the social network to a Pajek-formatted file Export to List-formatted file. Export List Exports the network to a List-formatted file Export network to UCINET-formatted file Export UCINET Exports the active network to a DL-formatted Export to GW-formatted file Export Exports the active network to a GW formatted file Close the actual network Send the currrent social network to the printer Print Sends whatever is viewable on the canvas to your printer. To print the whole social network, you might want to zoom-out. Quit SocNetV. Are you sure? Open &Text Editor Open a text editor to take notes, copy/paste network data, etc <p><b>Text Editor</b></p><p>Opens a simple text editor where you can copy paste network data, of any supported format, and save to a file. Then you can import that file to SocNetV. </p> &View Loaded File Display the loaded social network file. View Loaded File Displays the loaded social network file View &Adjacency Matrix Display the adjacency matrix of the network. <p><b>View Adjacency Matrix</b></p><p>Displays the adjacency matrix of the active network. </p><p>The adjacency matrix of a social network is a matrix where each element a(i,j) is equal to the weight of the arc from actor (node) i to actor j. <p>If the actors are not connected, then a(i,j)=0. </p> P&lot Adjacency Matrix (text) Plots the adjacency matrix in a text file using unicode characters. <p><b>Plot Adjacency Matrix (text)</b></p><p>Plots the adjacency matrix in a text file using unicode characters. </p><p>In every element (i,j) of the "image", a black square means actors i and j are connectedwhereas a white square means they are disconnected.</p> Create From &Known Data Sets Load one of the 'famous' social network data sets included in SocNetV. <p><b>Famous Data Sets</b></p><p>SocNetV includes a number of known (also called famous) data sets in Social Network Analysis, such as Krackhardt's high-tech managers, etc. Click this menu item or press F7 to load a data set.</p> Scale-free Create a random network with a power-law degree distribution. <p><b>Scale-free (power-law)</b></p><p>A scale-free network is a network whose degree distribution follows a power law. SocNetV generates random scale-free networks according to the BarabΓ‘si–Albert (BA) model using a preferential attachment mechanism.</p> Create a small-world random network, according to the Watts & Strogatz model. <p><b>Small World </b></p><p>Creates a random small-world network, according to the Watts & Strogatz model. </p><p>A small-world network has short average path lengths and high clustering coefficient.</p> ErdΕ‘s–RΓ©nyi Create a random network according to the ErdΕ‘s–RΓ©nyi model <p><b>ErdΕ‘s–RΓ©nyi </b></p><p>Creates a random network either of G(n, p) model or G(n,M) model. </p><p>The former model creates edges with Bernoulli trials (probability p).</p><p>The latter creates a graph of exactly M edges.</p> Lattice Create a lattice network. <p><b>Lattice </b></p><p>Creates a random lattice network</p><p>A lattice is a network whose drawing forms a regular tiling. Lattices are also known as meshes or grids.</p> d-Regular Create a d-regular random network, where every actor has the same degree d. <p><b>d-Regular</b></p><p>Creates a random network where each actor has the same number <em>d</em> of neighbours, aka the same degree d.</p> Create a ring lattice random network. <p><b>Ring Lattice </b></p><p>Creates a ring lattice random network. </p><p>A ring lattice is a graph with N vertices each connected to d neighbors, d / 2 on each side.</p> Create a Gaussian distributed random network. &Web Crawler Use the web crawler to create a network from all links found in a given website <p><b>Web Crawler </b></p><p>Creates a network of linked webpages, starting from an initial webpage using the built-in Web Crawler. </p><p>The web crawler visits the given URL (website or webpage) and parses its contents to find links to other pages (internal or external). If there are such links, it adds them to a queue of URLs. Then, all the URLs in the queue list are visited in a FIFO order and parsed to find more links which are also added to the url queue. The process repeats until it reaches user-defined limits: </p><p>Maximum urls to visit (max nodes in the resulting network)</p> <p>Maximum links per page</p><p>Except the initial url and the limits, you can also specify patterns of urls to include or exclude, types of links to follow (internal, external or both) as well as if you want delay between requests (strongly advised)</p>. Select/Move <p><b>Mouse mode: Interactive</b></p> <p>In this interactive mode, you can click on nodes/edges and move them around with your mouse. </p><p>Also, you can select multiple items with a rubber band selection area. To move the canvas, use the keyboard arrows.</p> Enable the interactive mouse mode to be able to click and move items and select them with a rubber band. <p><b>Mouse Mode: Interactive</b></p><p>In this mode, you can interact with the items on the canvas using the mouse: </p><p>a) double-click to create new nodes, <p>b) left-click or right-click on items (i.e. nodes, edges) to edit their properties</p><p>c) move nodes by dragging them with your mouse. </p><p>d) select multiple items with a rubber band.</p><p>To move the canvas (up/down, left/right), use the keyboard arrows. Scroll/Pan <p><b>Mouse mode: Scrolling</b></p> <p>In this non-interactive mode, you can easily scroll the canvas by dragging the mouse around. All mouse actions are disabled.</p> Enable this non-interactive mode to easily scroll the canvas by dragging the mouse around. <p><b>Mouse mode: Scrolling</b></p><p>In this mode, you cannot interact with the canvas using the mouse.</p><p>The cursor changes into a pointing hand, and dragging the mouse around will only scroll the scrolbars.</p> <p>You will not be able to select any items or move them around with the mouse.</p><p>Note: You will still be able to edit the network using the menu or the toolbar actions and icons.</p> Next Relation Goto the next relation of the network (if any). Next Relation Loads the next relation of the network (if any) Previous Relation Goto the previous relation of the network (if any). Previous Relation Loads the previous relation of the network (if any) Add New Relation Add a new relation to the network. Nodes will be preserved, edges will be removed. Add New Relation Adds a new relation to the active network. Nodes will be preserved, edges will be removed. Rename Relation Rename current relation Rename the current relation of the network. Rename Relation Renames the current relation of the network. Zoom In Zoom In. Zooms in the network Zoom in the network. Alternatives: use the canvas button, or press Ctrl++, or use mouse wheel while pressing Ctrl. Zoom Out Zoom Out. Zooms out of the actual network Zoom out the network. Alternatives: use the canvas button, or press Ctrl+-, or use mouse wheel while pressing Ctrl. Rotate counterclockwise Rotate counterclockwise. You can also use the button underneath the canvas. Rotates the network counterclockwise. You can also use the far left button below the canvas. Rotate clockwise Rotate clockwise. You can also use the button underneath the canvas. Rotates the network clockwise. You can also use the far right button below the canvas. Reset Zoom and Rotation Reset zoom and rotation to zero. Resets any zoom and rotation transformations to zero. Select All Select all nodes Select All Selects all nodes and edges in the network Select None Ctrl+Alt+A Deselect all nodes and edges Deselect all Clears the node selection Find Nodes Find and select one or more nodes by their number or label. Find Node Finds one or more nodes by their number or label and highlights them by doubling its size. Ctrl+. Add a new node to the network in a random position. Alternately, double-click on a specific position the canvas. Add a new node to the network in a random position. Alternately, create a new node by double-clicking on a specific position the canvas. Add new node Add a new node to the network in a random position. Alternately, you can create a new node by double-clicking on a specific position the canvas. Remove selected node(s). If no nodes are selected, you will be prompted for a node number. Remove selected node(s). If no nodes are selected, you will be prompted for a node number. Remove node Removes selected node(s) from the network. Alternately, you can remove a node by right-clicking on it. If no nodes are selected, you will be prompted for a node number. Selected Node Properties Change the properties of the selected node(s) There must be some nodes on the canvas! Change the basic properties of the selected node(s). There must be some nodes on the canvas! Selected Node Properties If there are one or more nodes selected, it opens a properties dialog to edit their label, size, color, shape etc. You must have some node selected. Create a clique from selected nodes Connect all selected nodes with edges to create a clique -- There must be some nodes selected! Clique from Selected Nodes Adds all possible edges between selected nodes, so that they become a complete subgraph (clique) You must have some nodes selected. Create a star from selected nodes Connect selected nodes with edges/arcs to create a star -- There must be some nodes selected! Star from Selected Nodes Adds edges between selected nodes, so that they become a star subgraph. You must have some nodes selected. Create a cycle from selected nodes Cycle from Selected Nodes Connect selected nodes so that they become a cycle subgraph. A cycle graph or circular graph is a graph that consists of a single cycle, or in other words, the vertices are connected in a closed chain. The cycle graph with n vertices is called Cβ‚™ You must have some nodes selected. Create a line from selected nodes Connect selected nodes with edges/arcs to create a line-- There must be some nodes selected! Line from Selected Nodes Adds edges between selected nodes, so that they become a line subgraph. You must have some nodes selected. Change All Nodes Color (this session) Choose a new color for all nodes (in this session only). Nodes Color Changes all nodes color at once. This setting will apply to this session only. To permanently change it, go to Settings. Change All Nodes Size (this session) Change the size of all nodes (in this session only) Change All Nodes Size Click to select and apply a new size for all nodes at once. This setting will apply to this session only. To permanently change it, go to Settings. Change All Nodes Shape (this session) Change the shape of all nodes (this session only) Change All Nodes Shape Click to select and apply a new shape for all nodes at once.This setting will apply to this session only. To permanently change it, go to Settings. Change All Node Numbers Size (this session) Change the font size of the numbers of all nodes(in this session only) Change Node Numbers Size Click to select and apply a new font size for all node numbersThis setting will apply to this session only. To permanently change it, go to Settings. Change All Node Numbers Color (this session) Change the color of the numbers of all nodes.(in this session only) Node Numbers Color Click to select and apply a new color to all node numbers.This setting will apply to this session only. To permanently change it, go to Settings. Change All Node Labels Size (this session) Change the font size of the labels of all nodes(this session only) Node Labels Size Click to select and apply a new font-size to all node labelsThis setting will apply to this session only. To permanently change it, go to Settings. Change All Node Labels Color (this session) Change the color of the labels of all nodes (for this session only) Labels Color Click to select and apply a new color to all node labels.This setting will apply to this session only. To permanently change it, go to Settings. Add Edge (arc) Add a directed edge (arc) from a node to another. Add a new edge from a node to another. You can also create an edge between two nodes by double-clicking on them consecutively. Add edge Adds a new edge from a node to another. Alternately, you can create a new edge between two nodes by double-clicking on them consecutively. Remove Edge Remove selected edges from the network. If no edge has been clicked or selected, you will be prompted to enter edge source and target nodes for the edge to remove. Remove selected Edge(s) Remove Edge Removes edges from the network. If one or more edges has been clicked or selected, they are removed. Otherwise, you will be prompted to enter edge source and target nodes for the edge to remove. Change Edge Label Change the Label of an Edge Change Edge Label Changes the label of an Edge Change Edge Color Change the Color of an Edge Change Edge Color Changes the Color of an Edge Change Edge Weight Change the weight of an Edge Edge Weight Changes the Weight of an Edge Change All Edges Color Change the color of all Edges. All Edges Color Changes the color of all Edges Symmetrize All Edges Make all directed ties to be reciprocated (thus, a symmetric graph). <p><b>Symmetrize All Edges</b></p><p>Forces all edges in this relation to be reciprocated: <p>If there is a directed edge from node A to node B then a new directed edge from node B to node A will be created, with the same weight. </p><p>The result is a symmetric network.</p> Symmetrize by Strong Ties Create a new symmetric relation by counting reciprocated ties only (strong ties). <p><b>Symmetrize Edges by Strong Ties:</b></p><p>Creates a new symmetric relation by keeping strong ties only. </p><p>A tie between actors A and B is considered strong if both A -> B and B -> A exist. Therefore, in the new relation, a reciprocated edge will be created between actors A and B only if both arcs A->B and B->A were present in the current or all relations. </p><p>If the network is multi-relational, it will ask you whether ties in the current relation or all relations are to be considered.</p> Undirected Edges Enable to transform all arcs to undirected edges and hereafter work with undirected edges . Undirected Edges Transforms all directed arcs to undirected edges. The result is a undirected and symmetric network.After that, every new edge you add, will be undirected too.If you disable this, then all edges become directed again. Cocitation Network Create a new symmetric relation by connecting actors that are cocitated by others. <p><b>Symmetrize Edges by examining Cocitation:</b></p><p>Creates a new symmetric relation by connecting actors that are cocitated by others. In the new relation, an edge will be created between actor i and actor j only if C(i,j) > 0, where C the Cocitation Matrix. </p><p>Thus the actor pairs cited by more common neighbors will appear with a stronger tie between them than pairs those cited by fewer common neighbors. The resulting relation is symmetric.</p> Dichotomize Valued Edges Create a new binary relation/graph in a valued network using edge dichotomization. Dichotomize Edges Creates a new binary relation in a valued network using edge dichotomization according to a given threshold value. In the new dichotomized relation, an edge will exist between actor i and actor j only if e(i,j) > threshold, where threshold is a user-defined value.Thus the dichotomization procedure is as follows: Choose a threshold value, set all ties with equal or higher values to equal one, and all lower to equal zero.The result is a binary (dichotomized) graph. The process is also known as compression and slicing Transform Nodes to Edges Transforms the network so that nodes become Edges and vice versa Transform Nodes EdgesAct Transforms network so that nodes become Edges and vice versa Filter Nodes By Centrality Temporarily filter out nodes according to their centrality score. Filter Nodes By Centrality Filters out nodes according to their score in a user-selected centrality index. Disable Isolate Nodes Temporarily filter out nodes with no edges Filter Isolate Nodes Enables or disables displaying of isolate nodes. Isolate nodes are those with no edges... Filter Edges by Weight Temporarily filter edges of some weight out of the network Filter Edges Filters edges according to their weight. Disable unilateral edges Temporarily disable all unilateral (non-reciprocal) edges in this relation. Keeps only "strong" ties. Unilateral edges In directed networks, a tie between two actors is unilateral when only one actor identifies the other as connected (i.e. friend, vote, etc). A unilateral tie is depicted as a single arc. These ties are considered weak, as opposed to reciprocal ties where both actors identify each other as connected. Strong ties are depicted as either a single undirected edge or as two reciprocated arcs between two nodes. By selecting this option, all unilateral edges in this relation will be disabled. Layout the network actors in random positions. Random Layout This layout algorithm repositions all network actors in random positions. Random Circles Layout the network in random concentric circles Random Circles Layout Repositions the nodes randomly on circles Degree Centrality Place all nodes on concentric circles of radius inversely proportional to their Degree Centrality. Degree Centrality (DC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Degree Centrality score. Nodes with higher DC are closer to the centre. Closeness Centrality Place all nodes on concentric circles of radius inversely proportional to their Closeness Centrality. Closeness Centrality (CC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Closeness Centrality. Nodes having higher CC are closer to the centre. Influence Range Closeness Centrality Place all nodes on concentric circles of radius inversely proportional to their Influence Range Closeness Centrality. Influence Range Closeness Centrality (IRCC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their IRCC score. Nodes having higher IRCC are closer to the centre. Betweenness Centrality Place all nodes on concentric circles of radius inversely proportional to their Betweenness Centrality. Betweenness Centrality (BC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Betweenness Centrality. Nodes having higher BC are closer to the centre. Stress Centrality Place all nodes on concentric circles of radius inversely proportional to their Stress Centrality. Stress Centrality (SC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Stress Centrality score. Nodes having higher SC are closer to the centre. Eccentricity Centrality Place all nodes on concentric circles of radius inversely proportional to their Eccentricity Centrality (aka Harary Graph Centrality). Eccentricity Centrality (EC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Eccentricity Centrality (aka Harary Graph Centrality) score. Nodes having higher EC are closer to the centre. Power Centrality Place all nodes on concentric circles of radius inversely proportional to their Power Centrality. Power Centrality (PC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Power Centrality score. Nodes having higher PC are closer to the centre. Information Centrality Place all nodes on concentric circles of radius inversely proportional to their Information Centrality. Information Centrality (IC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Information Centrality score. Nodes of higher IC are closer to the centre. Eigenvector Centrality Place all nodes on concentric circles of radius inversely proportional to their Eigenvector Centrality. Eigenvector Centrality (EVC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Eigenvector Centrality score. Nodes of higher EVC are closer to the centre. Degree Prestige Place all nodes on concentric circles of radius inversely proportional to their Degree Prestige (inDegree). Degree Prestige (DP) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their inDegree score. Nodes having higher DP are closer to the centre. PageRank Prestige Place all nodes on concentric circles of radius inversely proportional to their PRP index. PageRank Prestige (PRP) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their PageRank score. Nodes having higher PRP are closer to the centre. Proximity Prestige Place all nodes on concentric circles of radius inversely proportional to their Proximity Prestige. Proximity Prestige (PP) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their PP index. Nodes having higher PP score are closer to the centre. Place all nodes on horizontal levels of height proportional to their Degree Centrality. Degree Centrality (DC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their DC score. Nodes having higher DC are closer to the top. Place all nodes on horizontal levels of height proportional to their Closeness Centrality. Closeness Centrality (CC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Closeness Centrality score. Nodes of higher CC are closer to the top. This layout can be computed only for connected graphs. Place all nodes on horizontal levels of height proportional to their Influence Range Closeness Centrality. Influence Range Closeness Centrality (IRCC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their IRCC score. Nodes having higher IRCC are closer to the top. This layout can be computed for not connected graphs. Place all nodes on horizontal levels of height proportional to their Betweenness Centrality. Betweenness Centrality (BC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Betweenness Centrality score. Nodes having higher BC are closer to the top. Place nodes on horizontal levels of height proportional to their Stress Centrality. Stress Centrality (SC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Stress Centrality score. Nodes having higher SC are closer to the top. Place nodes on horizontal levels of height proportional to their Eccentricity Centrality (aka Harary Graph Centrality). Eccentricity Centrality (EC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Eccentricity Centrality (aka Harary Graph Centrality) score. Nodes having higher EC are closer to the top. Place nodes on horizontal levels of height proportional to their Power Centrality. Power Centrality (PC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Power Centrality score. Nodes having higher PC are closer to the top. Place nodes on horizontal levels of height proportional to their Information Centrality. Information Centrality (IC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Information Centrality score. Nodes having higher IC are closer to the top. Place nodes on horizontal levels of height proportional to their Eigenvector Centrality. Eigenvector Centrality (EVC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Eigenvector Centrality score. Nodes having higher EVC are closer to the top. Place nodes on horizontal levels of height proportional to their Degree Prestige. Degree Prestige (DP) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Degree Prestige score. Nodes having higher DP are closer to the top. Place nodes on horizontal levels of height proportional to their PageRank Prestige. PageRank Prestige (PRP) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their PageRank Prestige score. Nodes having higher PRP are closer to the top. Place nodes on horizontal levels of height proportional to their Proximity Prestige. Proximity Prestige (PP) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Proximity Prestige score. Nodes having higher PP are closer to the top. Resize all nodes to be proportional to their Degree Centrality. Degree Centrality (DC) Node Size Layout Changes the size of all nodes to be proportional to their DC (inDegree) score. Nodes having higher DC will appear bigger. Resize all nodes to be proportional to their Closeness Centrality. Closeness Centrality (CC) Node Size Layout Changes the size of all nodes to be proportional to their CC score. Nodes of higher CC will appear bigger. This layout can be computed only for connected graphs. Resize all nodes to be proportional to their Influence Range Closeness Centrality. Influence Range Closeness Centrality (IRCC) Node Size Layout Changes the size of all nodes to be proportional to their IRCC score. Nodes having higher IRCC will appear bigger. This layout can be computed for not connected graphs. Resize all nodes to be proportional to their Betweenness Centrality. Betweenness Centrality (BC) Node Size Layout Changes the size of all nodes to be proportional to their Betweenness Centrality score. Nodes having higher BC will appear bigger. Resize all nodes to be proportional to their Stress Centrality. Stress Centrality (SC) Node Size Layout Changes the size of all nodes to be proportional to their Stress Centrality score. Nodes having higher SC will appear bigger. Resize all nodes to be proportional to their Eccentricity Centrality (aka Harary Graph Centrality). Eccentricity Centrality (EC) NodeSizes Layout Changes the size of all nodes to be proportional to their Eccentricity Centrality (aka Harary Graph Centrality) score. Nodes having higher EC will appear bigger. Resize all nodes to be proportional to their Power Centrality. Power Centrality (PC) Node Size Layout Changes the size of all nodes to be proportional to their Power Centrality score. Nodes having higher PC will appear bigger. Resize all nodes to be proportional to their Information Centrality. Information Centrality (IC) Node Size Layout Changes the size of all nodes to be proportional to their Information Centrality score. Nodes having higher IC will appear bigger. Resize all nodes to be proportional to their Eigenvector Centrality. Eigenvector Centrality (EVC) Node Size Layout Changes the size of all nodes to be proportional to their Eigenvector Centrality score. Nodes having higher EVC will appear bigger. Resize all nodes to be proportional to their Degree Prestige. Degree Prestige (DP) Node Size Layout Changes the size of all nodes to be proportional to their Degree Prestige score. Nodes having higher DP will appear bigger. Resize all nodes to be proportional to their PageRank Prestige. PageRank Prestige (PRP) Node Size Layout Changes the size of all nodes to be proportional to their PageRank Prestige score. Nodes having higher PRP will appear bigger. Resize all nodes to be proportional to their Proximity Prestige. Proximity Prestige (PP) Node Size Layout Changes the size of all nodes to be proportional to their Proximity Prestige score. Nodes having higher PP will appear bigger. Change the color of all nodes to reflect their Degree Centrality. Degree Centrality (DC) Node Color Layout Changes the color of all nodes to reflect their DC (inDegree) score. Nodes having higher DC will have warmer color (i.e. red). Change the color of all nodes to reflect their Closeness Centrality. Closeness Centrality (CC) Node Color Layout Changes the color of all nodes to reflect their CC score. Nodes of higher CC will have warmer color (i.e. red). This layout can be computed only for connected graphs. Change the color of all nodes to proportional to their Influence Range Closeness Centrality. Influence Range Closeness Centrality (IRCC) Node Color Layout Changes the color of all nodes to reflect their IRCC score. Nodes having higher IRCC will have warmer color (i.e. red). This layout can be computed for not connected graphs. Change the color of all nodes to reflect their Betweenness Centrality. Betweenness Centrality (BC) Node Color Layout Changes the color of all nodes to reflect their Betweenness Centrality score. Nodes having higher BC will have warmer color (i.e. red). Change the color of all nodes to reflect their Stress Centrality. Stress Centrality (SC) Node Color Layout Changes the color of all nodes to reflect their Stress Centrality score. Nodes having higher SC will have warmer color (i.e. red). Change the color of all nodes to reflect their Eccentricity Centrality (aka Harary Graph Centrality). Eccentricity Centrality (EC) NodeColors Layout Changes the color of all nodes to reflect their Eccentricity Centrality (aka Harary Graph Centrality) score. Nodes having higher EC will have warmer color (i.e. red). Change the color of all nodes to reflect their Power Centrality. Power Centrality (PC) Node Color Layout Changes the color of all nodes to reflect their Power Centrality score. Nodes having higher PC will have warmer color (i.e. red). Change the color of all nodes to reflect their Information Centrality. Information Centrality (IC) Node Color Layout Changes the color of all nodes to reflect their Information Centrality score. Nodes having higher IC will have warmer color (i.e. red). Change the color of all nodes to reflect their Eigenvector Centrality. Eigenvector Centrality (EVC) Node Color Layout Changes the color of all nodes to reflect their Eigenvector Centrality score. Nodes having higher EVC will have warmer color (i.e. red). Change the color of all nodes to reflect their Degree Prestige. Degree Prestige (DP) Node Color Layout Changes the color of all nodes to reflect their Degree Prestige score. Nodes having higher DP will have warmer color (i.e. red). Change the color of all nodes to reflect their PageRank Prestige. PageRank Prestige (PRP) Node Color Layout Changes the color of all nodes to reflect their PageRank Prestige score. Nodes having higher PRP will have warmer color (i.e. red). Change the color of all nodes to reflect their Proximity Prestige. Proximity Prestige (PP) Node Color Layout Changes the color of all nodes to reflect their PageRank Prestige score. Nodes of higher PP will have warmer color (i.e. red). Spring Embedder (Eades) Layout Eades Spring-Gravitational model. Spring Embedder Layout The Spring Embedder model (Eades, 1984), part of the Force Directed Placement (FDP) family, embeds a mechanical system in the graph by replacing nodes with rings and edges with springs. In our implementation, nodes are replaced by physical bodies (i.e. electrons) which exert repelling forces to each other, while edges are replaced by springs which exert attractive forces to the adjacent nodes. The nodes are placed in some initial layout and let go so that the spring forces move the system to a minimal energy state. The algorithm continues until the system retains an equilibrium state in which all forces cancel each other. Kamada-Kawai Embeds the Kamada-Kawai FDP layout model, the best variant of the Spring Embedder family of models. <p><em>Kamada-Kawai</em></p><p>The best variant of the Spring Embedder family of models. <p>In this the graph is considered to be a dynamic system where every edge is between two actors is a 'spring' of a desirable length, which corresponds to their graph theoretic distance. </p><p>In this way, the optimal layout of the graph is the state with the minimum imbalance. The degree of imbalance is formulated as the total spring energy: the square summation of the differences between desirable distances and real ones for all pairs of vertices.</p> Layout GuideLines Toggles layout guidelines on or off. Layout Guidelines Layout Guidelines are circular or horizontal lines usually created when embedding prominence-based visualization models on the network. Disable this checkbox to hide guidelines Invert Adjacency Matrix Invert the adjacency matrix, if possible Invert Adjacency Matrix Inverts the adjacency matrix using linear algebra methods. Transpose Adjacency Matrix View the transpose of adjacency matrix Transpose Adjacency Matrix Computes and displays the adjacency matrix tranpose. Cocitation Matrix Compute the Cocitation matrix of this network. Cocitation Matrix Computes and displays the cocitation matrix of the network. The Cocitation matrix, C=A*A^T, is a NxN matrix where each element (i,j) is the number of actors that have outbound ties/links to both actors i and j. Degree Matrix Compute the Degree matrix of the network Degree Matrix Compute the Degree matrix of the network. Laplacian Matrix Compute the Laplacian matrix of the network Laplacian Matrix Compute the Laplacian matrix of the network. Reciprocity Compute the arc and dyad reciprocity of the network. Arc and Dyad Reciprocity The arc reciprocity of a network/graph is the fraction of reciprocated ties over all present ties of the graph. The dyad reciprocity of a network/graph is the fraction of actor pairs that have reciprocated ties over all connected pairs of actors. In a directed network, the arc reciprocity measures the proportion of directed edges that are bidirectional. If the reciprocity is 1, then the adjacency matrix is structurally symmetric. Likewise, in a directed network, the dyad reciprocity measures the proportion of connected actor dyads that have bidirectional ties between them. In an undirected graph, all edges are reciprocal. Thus the reciprocity of the graph is always 1. Reciprocity can be computed on undirected, directed, and weighted graphs. Symmetry Test Check whether the network is symmetric or not Symmetry Checks whether the network is symmetric or not. A network is symmetric when all edges are reciprocal, or, in mathematical language, when the adjacency matrix is symmetric. Geodesic Distance between 2 nodes Compute the length of the shortest path (geodesic distance) between 2 nodes. Distance Computes the geodesic distance between two nodes.In graph theory, the geodesic distance of two nodes is the length (number of edges) of the shortest path between them. Geodesic Distances Matrix Compute the matrix of geodesic distances between all pair of nodes. Distances Matrix Computes the matrix of distances between all pairs of actors/nodes in the social network.A distances matrix is a n x n matrix, in which the (i,j) element is the distance from node i to node jThe distance of two nodes is the length of the shortest path between them. Geodesics Matrix Compute the number of shortest paths (geodesics) between each pair of nodes Geodesics Matrix Displays a n x n matrix, where the (i,j) element is the number of shortest paths (geodesics) between node i and node j. Graph Diameter Compute the diameter of the network, the maximum geodesic distance between any actors. Diameter The diameter of a network is the maximum geodesic distance (maximum shortest path length) between any two nodes of the network. Average Distance Compute the average graph distance for all possible pairs of nodes. Average Graph Distance This is the average length of shortest paths (geodesics) for all possible pairs of nodes. It is a measure of the efficiency or compactness of the network. Compute the Eccentricity of each actor and group Eccentricity Eccentricity The eccentricity of each node i in a network or graph is the largest geodesic distance between node i and any other node j. Therefore, it reflects how far, at most, is each node from every other node. The maximum eccentricity is the graph diameter while the minimum is the graph radius. This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1. Connectedness Check whether the network is a connected graph, a connected digraph or a disconnected graph/digraph... Connectedness In graph theory, a graph is <b>connected</b> if there is a path between every pair of nodes. A digraph is <b>strongly connected</b> if there the a path from i to j and from j to i for all pairs (i,j). A digraph is weakly connected if at least a pair of nodes are joined by a semipath. A digraph or a graph is disconnected if at least one node is isolate. Walks of a given length Compute the number of walks of a given length between any nodes. Walks of a given length A walk is a sequence of alternating vertices and edges such as v<sub>0</sub>e<sub>1</sub>, v<sub>1</sub>e<sub>2</sub>, v<sub>2</sub>e<sub>3</sub>, …, e<sub>k</sub>v<sub>k</sub>, where each edge, e<sub>i</sub> is defined as e<sub>i</sub> = {v<sub>i-1</sub>, v<sub>i</sub>}. This function counts the number of walks of a given length between each pair of nodes, by studying the powers of the sociomatrix. Total Walks Calculate the total number of walks of every possible length between all nodes Total Walks A walk is a sequence of alternating vertices and edges such as v<sub>0</sub>e<sub>1</sub>, v<sub>1</sub>e<sub>2</sub>, v<sub>2</sub>e<sub>3</sub>, …, e<sub>k</sub>v<sub>k</sub>, where each edge, e<sub>i</sub> is defined as e<sub>i</sub> = {v<sub>i-1</sub>, v<sub>i</sub>}. This function counts the number of walks of any length between each pair of nodes, by studying the powers of the sociomatrix. Reachability Matrix Compute the Reachability Matrix of the network. Reachability Matrix Calculates the reachability matrix X<sup>R</sup> of the graph where the {i,j} element is 1 if the vertices i and j are reachable. Actually, this just checks whether the corresponding element of Distances matrix is not zero. Local and Network Clustering Coefficient Compute the Watts & Strogatz Clustering Coefficient for every actor and the network average. Local and Network Clustering Coefficient The local Clustering Coefficient (Watts & Strogatz, 1998) of an actor quantifies how close the actor and her neighbors are to being a clique and can be used as an indication of network transitivity. Clique Census Compute the clique census: find all maximal connected subgraphs. Clique Census Produces the census of network cliques (maximal connected subgraphs), along with disaggregation by actor and co-membership information. Triad Census (M-A-N labeling) Calculate the triad census for all actors. Triad Census A triad census counts all the different kinds of observed triads within a network and codes them according to their number of mutual, asymmetric and non-existent dyads using the M-A-N labeling scheme. Pearson correlation coefficients Compute Pearson Correlation Coefficients between pairs of actors. Most useful with valued/weighted ties (non-binary). Pearson correlation coefficients Computes a correlation matrix, where the elements are the Pearson correlation coefficients between pairs of actors in terms of their tie profiles or distances (in, out or both). The Pearson product-moment correlation coefficient (PPMCC or PCC or Pearson's r)is a measure of the linear dependence/association between two variables X and Y. This correlation measure of similarity is particularly useful when ties are valued/weighted denoting strength, cost or probability. Note that in very sparse networks (very low density), measures such as"exact matches", "correlation" and "distance" will show little variation among the actors, causing difficulty in classifying the actors in structural equivalence classes. Similarity by measure (Exact, Jaccard, Hamming, Cosine, Euclidean) Compute a pair-wise actor similarity matrix based on a measure of their ties (or distances) "matches" . Actor Similarity by measure Computes a pair-wise actor similarity matrix, where each element (i,j) is the ratio of tie (or distance) matches of actors i and j to all other actors. SocNetV supports the following matching measures: Simple Matching (Exact Matches)Jaccard Index (Positive Matches or Co-citation)Hamming distanceCosine similarityEuclidean distanceFor instance, if you select Exact Matches, a matrix element (i,j) = 0.5, means that actors i and j have the same ties present or absent to other actors 50% of the time. These measures of similarity are particularly useful when ties are binary (not valued). Note that in very sparse networks (very low density), measures such as"exact matches", "correlation" and "distance" will show little variation among the actors, causing difficulty in classifying the actors in structural equivalence classes. Tie Profile Dissimilarities/Distances Compute tie profile dissimilarities/distances (Euclidean, Manhattan, Jaccard, Hamming) between all pair of nodes. Tie Profile Dissimilarities/Distances Computes a matrix of tie profile distances/dissimilarities between all pairs of actors/nodes in the social network using an ordinary metric such as Euclidean distance, Manhattan distance, Jaccard distance or Hamming distance).The resulted distance matrix is a n x n matrix, in which the (i,j) element is the distance or dissimilarity between the tie profiles of node i and node j. Hierarchical clustering Perform agglomerative cluster analysis of the actors in the social network Hierarchical clustering Hierarchical clustering (or hierarchical cluster analysis, HCA) is a method of cluster analysis which builds a hierarchy of clusters, based on their elements dissimilarity. In SNA context these clusters usually consist of network actors. This method takes the social network distance matrix as input and uses the Agglomerative "bottom up" approach where each actor starts in its own cluster (Level 0). In each subsequent Level, as we move up the clustering hierarchy, a pair of clusters are merged into a larger cluster, until all actors end up in the same cluster. To decide which clusters should be combined at each level, a measure of dissimilarity between sets of observations is required. This measure consists of a metric for the distance between actors (i.e. manhattan distance) and a linkage criterion (i.e. single-linkage clustering). This linkage criterion (essentially a definition of distance between clusters), differentiates between the different HCA methods.Note that the complexity of agglomerative clustering is O( n^2 log(n) ), therefore is too slow for large data sets. Degree Centrality (DC) Compute Degree Centrality indices for every actor and group Degree Centralization. Degree Centrality (DC) For each node v, the DC index is the number of edges attached to it (in undirected graphs) or the total number of arcs (outLinks) starting from it (in digraphs). This is often considered a measure of actor activity. This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs. In weighted relations, DC is the sum of weights of all edges/outLinks attached to v. Closeness Centrality (CC) Compute Closeness Centrality indices for every actor and group Closeness Centralization. Closeness Centrality (CC) For each node v, CC the inverse sum of the shortest distances between v and every other node. CC is interpreted as the ability to access information through the "grapevine" of network members. Nodes with high closeness centrality are those who can reach many other nodes in few steps. This index can be calculated in both graphs and digraphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1. Influence Range Closeness Centrality (IRCC) Compute Influence Range Closeness Centrality indices for every actor focusing on how proximate each one isto others in its influence range Influence Range Closeness Centrality (IRCC) For each node v, IRCC is the standardized inverse average distance between v and every reachable node. This improved CC index is optimized for graphs and directed graphs which are not strongly connected. Unlike the ordinary CC, which is the inverted sum of distances from node v to all others (thus undefined if a node is isolated or the digraph is not strongly connected), IRCC considers only distances from node v to nodes in its influence range J (nodes reachable from v). The IRCC formula used is the ratio of the fraction of nodes reachable by v (|J|/(n-1)) to the average distance of these nodes from v (sum(d(v,j))/|J| Betweenness Centrality (BC) Betweenness Centrality (BC) For each node v, BC is the ratio of all geodesics between pairs of nodes which run through v. It reflects how often an node lies on the geodesics between the other nodes of the network. It can be interpreted as a measure of control. A node which lies between many others is assumed to have a higher likelihood of being able to control information flow in the network. Note that betweenness centrality assumes that all geodesics have equal weight or are equally likely to be chosen for the flow of information between any two nodes. This is reasonable only on "regular" networks where all nodes have similar degrees. On networks with significant degree variance you might want to try informational centrality instead. This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1. Compute Betweenness Centrality indices and group Betweenness Centralization. Stress Centrality (SC) Compute Stress Centrality indices for every actor and group Stress Centralization. Stress Centrality (SC) For each node v, SC is the total number of geodesics between all other nodes which run through v. A node with high SC is considered 'stressed', since it is traversed by a high number of geodesics. When one node falls on all other geodesics between all the remaining (N-1) nodes, then we have a star graph with maximum Stress Centrality. This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1. Eccentricity Centrality (EC) Compute Eccentricity Centrality (aka Harary Graph Centrality) scores for each node. Eccentricity Centrality (EC) This index is also known as Harary Graph Centrality. For each node i, the EC is the inverse of the maximum geodesic distance of that v to all other nodes in the network. Nodes with high EC have short distances to all other nodes This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1. Gil and Schmidt Power Centrality (PC) Compute Power Centrality indices (aka Gil-Schmidt Power Centrality) for every actor and group Power Centralization Power Centrality (PC) For each node v, this index sums its degree (with weight 1), with the size of the 2nd-order neighbourhood (with weight 2), and in general, with the size of the kth order neighbourhood (with weight k). Thus, for each node in the network the most important other nodes are its immediate neighbours and then in decreasing importance the nodes of the 2nd-order neighbourhood, 3rd-order neighbourhood etc. For each node, the sum obtained is normalised by the total numbers of nodes in the same component minus 1. Power centrality has been devised by Gil-Schmidt. This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1 (therefore not considered). Information Centrality (IC) Compute Information Centrality indices and group Information Centralization Information Centrality (IC) Information centrality counts all paths between nodes weighted by strength of tie and distance. This centrality measure developed by Stephenson and Zelen (1989) focuses on how information might flow through many different paths. This index should be calculated only for graphs. Note: To compute this index, SocNetV drops all isolated nodes. Eigenvector Centrality (EVC) Compute Eigenvector Centrality indices and group Eigenvector Centralization Eigenvector Centrality (EVC) Computes the Eigenvector centrality of each node in a social network which is defined as the ith element of the leading eigenvector of the adjacency matrix. The leading eigenvector is the eigenvector corresponding to the largest positive eigenvalue.The Eigenvector Centrality, proposed by Bonacich (1989), is an extension of the simpler Degree Centrality because it gives each actor a score proportional to the scores of its neighbors. Thus, a node may be important, in terms of its EC, because it has lots of ties or it has fewer ties to important other nodes. Degree Prestige (DP) Compute Degree Prestige (InDegree) indices InDegree (Degree Prestige) For each node k, this the number of arcs ending at k. Nodes with higher in-degree are considered more prominent among others. In directed graphs, this index measures the prestige of each node/actor. Thus it is called Degree Prestige. Nodes who are prestigious tend to receive many nominations or choices (in-links). The largest the index is, the more prestigious is the node. This index can be calculated only for digraphs. In weighted relations, DP is the sum of weights of all arcs/inLinks ending at node v. PageRank Prestige (PRP) Compute PageRank Prestige indices for every actor PageRank Prestige An importance ranking for each node based on the link structure of the network. PageRank, developed by Page and Brin (1997), focuses on how nodes are connected to each other, treating each edge from a node as a citation/backlink/vote to another. In essence, for each node PageRank counts all backlinks to it, but it does so by not counting all edges equally while it normalizes each edge from a node by the total number of edges from it. PageRank is calculated iteratively and it corresponds to the principal eigenvector of the normalized link matrix. This index can be calculated in both graphs and digraphs but is usually best suited for directed graphs since it is a prestige measure. It can also be calculated in weighted graphs. In weighted relations, each backlink to a node v from another node u is considered to have weight=1 but it is normalized by the sum of outLinks weights (outDegree) of u. Therefore, nodes with high outLink weights give smaller percentage of their PR to node v. Proximity Prestige (PP) Calculate and display Proximity Prestige (digraphs only) Proximity Prestige (PP) This index measures how proximate a node v is to the nodes in its influence domain I (the influence domain I of a node is the number of other nodes that can reach it). In PP calculation, proximity is based on distances to rather than distances from node v. To put it simply, in PP what matters is how close are all the other nodes to node v. The algorithm takes the average distance to node v of all nodes in its influence domain, standardizes it by multiplying with (N-1)/I and takes its reciprocal. In essence, the formula SocNetV uses to calculate PP is the ratio of the fraction of nodes that can reach node v, to the average distance of that nodes to v: PP = (I/(N-1))/(sum{d(u,v)}/I) where the sum is over all nodes in I. Display Node Numbers Toggle displaying of node numbers (this session only) Display Node Numbers Enables or disables displaying of node numbers This setting will apply to this session only. To permanently change it, go to Settings. Display Numbers Inside Nodes Toggle displaying of numbers inside nodes (this session only) Display Numbers Inside Nodes Enables or disables displaying node numbers inside nodes. This setting will apply to this session only. To permanently change it, go to Settings. Display Node Labels Toggle displaying of node labels (this session only) Display Node Labels Enables or disables node labels. This setting will apply to this session only. To permanently change it, go to Settings. Display Edges Toggle displaying edges (this session only) Display Edges Enables or disables displaying of edgesThis setting will apply to this session only. To permanently change it, go to Settings. Display Edge Weights Toggle displaying of numbers of edge weights (this session only) Display Edge Weights Enables or disables displaying edge weight numbers. This setting will apply to this session only. To permanently change it, go to Settings. Consider Edge Weights in Calculations Toggle considering edge weights during calculations (i.e. distances, centrality, etc) (this session only) Consider Edge Weights in Calculations Enables or disables considering edge weights during calculations (i.e. distances, centrality, etc). This setting will apply to this session only. To permanently change it, go to Settings. Display Edge Labels Toggle displaying of Edge labels, if any (this session only) Display Edge Labes Enables or disables displaying edge labels. This setting will apply to this session only. To permanently change it, go to Settings. Display Edge Arrows Toggle displaying directional Arrows on edges (this session only) Display edge Arrows Enables or disables displaying of arrows on edges. Useful if all links are reciprocal (undirected graph). This setting will apply to this session only. To permanently change it, go to Settings. Edge Thickness reflects Weight Draw edges as thick as their weights (if specified) Edge thickness reflects weight Click to toggle having all edges as thick as their weight (if specified) Draw Edges as Bezier curves Edges Bezier Enable or disables drawing Edges as Bezier curves.This setting will apply to this session only. To permanently change it, go to Settings. Change the canvasbackground color Background Color Changes the background color of the canvas Background Image (this session) Select and display a custom image in the background(for this session only) Background image Enable to select an image file from your computer, which will be displayed in the background instead of plain color.This setting will apply to this session only. To permanently change it, go to Settings. Full screen (this session) Toggle full screen mode (for this session only) Full Screen Mode Enable to show application window in full screen mode. This setting will apply to this session only. To permanently change it, go to Settings. Settings Open the Settings dialog where you can save your preferences for all future sessions Open the Settings dialog to save your preferences for all future sessions Settings Opens the Settings dialog where you can edit and save settings permanently for all subsequent sessions. Check for Updates Open a browser to SocNetV website to check for a new version... Check Updates Open a browser to SocNetV website so that you can check yourself for updates System Information Show information about your system <p><b>System Information</b></p><p>Shows useful information about your system, which you can include in your bug reports. </p> Recent &files... &Import ... Create &Random Network... Export to other... Nodes... Edges... &Analyze Adjacency Matrix and Matrices... Cohesion... Centrality and Prestige indices... Communities and Subgroups... Structural Equivalence... Random... Radial by prominence index... On Levels by prominence index... Node Size by prominence index... Node Color by prominence index... Force-Directed Placement... &Canvas... <p><b>Current relation<b></p><p>To rename the current relation, click here, enter new name and press Enter.</p> Name of the current relation. To rename it, enter a new name and press Enter. To select another relation, click the Down arrow (on the right). <p><b>Relations combo</b></p><p>This displays the currently selected relation of the network. </p><p>To rename the current relation, click on the name, enter a new name and press Enter. </p><p>To select another relation (if any), click the Down arrow (on the right).</p> Auto Create: Create a network automatically (famous, random, or by using the web crawler). <p><b>Auto network creation</b></p> <p>Create a new network automatically.</p><p>You may create a random network, recreate famous data-sets or use the built-in web crawler to create a network of webpages. </p> Subgraph: Create a basic subgraph with selected nodes. <p><b>Subgraph creation</b></p> <p>Create a basic subgraph from selected nodes.</p><p>Select some nodes with your mouse and then click on one of theseoptions to create a basic subgraph with them. </p><p>You can create a star, clique, line, etc subgraph.</p><p>There must be some nodes selected!</p> Edge Mode: Select the edge mode: directed or undirected. <p><b>Edge mode</b></p><p>In social networks and graphs, edges can be directed or undirected (and the corresponding network is called directed or undirected as well).</p><p>This option lets you choose what the kind of edges you want in your network.<p><p>By selecting an option here, all edges of the network will change automatically. <p><p>For instance, if the network is directed and and you select "undirected" then all the directed edges will become undirected <p> Transform: Select a method to transform the network, i.e. transform all directed edges to undirected. <p><b>Transform Network Edges </b></p><p>Select a method to transform network edges. Available methods: </p><p><em>Symmetrize All Edges</em></p><p>Forces all edges in this relation to be reciprocated: <p>If there is a directed edge from node A to node B then a new directed edge from node B to node A will be created, with the same weight. </p><p>The result is a symmetric network.</p><p><em>Symmetrize Edges by Strong Ties:</em></p><p>Creates a new symmetric relation by keeping strong ties only. </p><p>A tie between actors A and B is considered strong if both A -> B and B -> A exist. Therefore, in the new relation, a reciprocated edge will be created between actors A and B only if both arcs A->B and B->A were present in the current or all relations. </p><p>If the network is multi-relational, it will ask you whether ties in the current relation or all relations are to be considered.</p><p><em>Symmetrize Edges by examining Cocitation:</em></p><p>Creates a new symmetric relation by connecting actors that are cocitated by others. In the new relation, an edge will be created between actor i and actor j only if C(i,j) > 0, where C the Cocitation Matrix. </p><p>Thus the actor pairs cited by more common neighbors will appear with a stronger tie between them than pairs those cited by fewer common neighbors. The resulting relation is symmetric.</p><p><em>Dichotomize Edges</em></p><p>Creates a new binary relation in a valued network using edge dichotomization according to a given threshold value. In the new dichotomized relation, an edge will exist between actor i and actor j only if e(i,j) > threshold, where threshold is a user-defined value.The process is also known as compression and slicing.</p> Matrix: Select which matrix to compute and display, based on the adjacency matrix of the current network. <p><b>Matrix Analysis</b></p><p>Compute and display the adjacency matrix and other matrices based on the adjacency matrix of the current network. Available options:<p><em>Adjacency Matrix</em></p><p><em>Adjacency Matrix Plot</em></p><p><em>Inverse of Adjacency Matrix</em></p><p><em>Transpose of Adjacency Matrix</em></p><p><em>Cocitation Matrix </em></p><p><em>Degree Matrix </em></p><p><em>Laplacian Matrix </em></p> Cohesion: Select a graph-theoretic measure, i.e. distances, walks, graph diameter, eccentricity. <p><b>Analyze Cohesion</b></p><p><Compute basic graph-theoretic measures. <p><em>Reciprocity:</em><p><p>Measures the likelihood that pairs of nodes in a directed network are mutually linked.</p><p><em>Symmetry:</em><p><p>Checks if the directed network is symmetric or not.<p><p><em>Distances:</em></p><p>Computes the matrix of geodesic distances between all pairs of nodes.<p><p><em>Average Distance:</em></p><p>Computes the average distance between all nodes.<p><p><em>Graph Diameter:</em></p><p>The maximum distance between any two nodes in the network.</p><p><em>Walks:</em></p><p>A walk is a sequence of edges and vertices (nodes), where each edge's endpoints are the two vertices adjacent to it. In a walk, vertices and edges may repeat.<p><em>Eccentricity:</em></p><p>The Eccentricity of each node is how far, at most, is from every other actor in the network.</p><p><em>Reachability:</em></p><p>Creates a matrix where an element (i,j) = 1 only if the actors i and j are reachable.</p><p><em>Clustering Coefficient (CLC):</em></p><p>The CLC score of each node is the proportion of actual links between its neighbors divided by the number of links that could possibly exist between them. Quantifies how close each actor and its neighbors are to form a complete subgraph (clique)</p> Prominence: Select a prominence metric to compute for each actor and the whole network. <p><b>Prominence Analysis</b></p><p>Compute Centrality and Prestige indices, to measure how <em>prominent</em> (important) each actor (node) is inside the network. </p><p>Centrality measures quantify how central is each node by examining its ties and its geodesic distances (shortest path lengths) to other nodes. Most Centrality indices were designed for undirected graphs. </p><p>Prestige indices focus on "choices received" to a node. These indices measure the nominations or ties to each node from all others (or inLinks). Prestige indices are suitable (and can be calculated only) on directed graphs.</p><p>Available measures:</p><p><em>Degree Centrality (DC) </em></p><p>The sum of outbound edges or the sum of weights of outbound edges from each node <em>i</em> to all adjacent nodes. Note: This is the outDegree Centrality. To compute inDegree Centrality, use the Degree Prestige measure.</p><p><em>Closeness Centrality (CC):</em></p>The inverted sum of geodesic distances from each node <em>u</em> to all other nodes. <p><em>IR Closeness Centrality (IRCC):</em></p><p>The ratio of the fraction of nodes reachable by each node <em>u</em> to the average distance of these nodes from <em>u</em>.</p><p><em>Betweenness Centrality (BC):</em></p><p>The sum of delta<sub>(s,t,u)</sub> for all s,t ∈ V where delta<sub>(s,t,u)</sub> is the ratio of all geodesics between nodes <em>s</em> and <em>t</em> which run through node <em>u</em>.</p> <p><em>Stress Centrality (SC):</em></p><p>The sum of sigma<sub>(s,t,u)</sub> for all s,t ∈ V where sigma<sub>(s,t,u)</sub> is the number of geodesics between nodes <em>s</em> and <em>t</em> which run through node <em>u</em>.</p> <p><em>Eccentricity Centrality (EC):</em></p><p>Also known as Harary Graph Centrality. The inverse maximum geodesic distance from node <em>u</em> to all other nodes in the network.<p><em>Power Centrality (PC):</em></p><p>The sum of the sizes of all N<sub>th</sub>-order neighbourhoods of node <em>u</em> with weight 1/n.</p><p><em>Information Centrality (IC):</em></p><p>Measures the information flow through all paths between actors weighted by strength of tie and distance.</p><p><em>Eigenvector Centrality (EVC):</em></p><p>The EVC score of each node <em>i</em> is the i<sub>th</sub> element of the leading eigenvector of the adjacency matrix, that is the eigenvector corresponding to the largest positive eigenvalue. <p><em>Degree Prestige (DP):</em></p><p>Also known as InDegree Centrality, it is the sum of inbound edges to a node <em>u</em> from all adjacent nodes. </p><p><em>PageRank Prestige (PRP):</em></p><p>For each node <em>u</em> counts all inbound links (edges) to it, but it normalizes each inbound link from another node <em>v</em> by the outDegree of <em>v</em>. </p><p><em>Proximity Prestige (PP):</em></p><p>The ratio of the proportion of nodes who can reach each node <em>u</em> to the average distance these nodes are from it. Similar to Closeness Centrality but it counts only inbound distances to each actor, thus it is a measure of actor prestige.</p> Communities: Select a community detection measure / cohesive subgroup algorithm, i.e. cliques, triad census etc. <p><b>Community Analysis</b></p><p>Community detection measures and cohesive subgroup algorithms, to identify meaningful subgraphs in the graph.</p><p><b>Available measures</b></p><p><em>Clique Census:</em><p><p>Computes aggregate counts of all maximal cliques of actors by size, actor by clique analysis, clique co-memberships</p><p><em>Triad Census:</em><p><p>Computes the Holland, Leinhardt and Davis triad census, which counts all different classes of triads coded according to theirnumber of Mutual, Asymmetric and Non-existest dyads (M-A-N scheme)</p> Equivalence: Select a method to measure structural equivalence, i.e. Pearson Coefficients, tie profile similarities, hierarchical clustering, etc. <p><b>Structural Equivalence Analysis</b></p><p>Select one of the available structural equivalence measures and visualization algorithms. <p><p>Available options</p><p><em>Pearson Coefficients<.em></p><p><em>Tie profile similarities</em></p><p><em>Dissimilarities</em></p><p><em>Hierarchical Clustering Analysis</em></p> Analyze Index: Select a prominence-based layout model <p><b>Visualize by prominence index</b></p><p>Apply a prominence-based layout model to the network.</p><p>For instance, you can apply a degree centrality layout. </p><p>Note: For each prominence index, you must select a layout type (below).</p><p>Available measures:</p><p><em>Degree Centrality (DC) </em></p><p>The sum of outbound edges or the sum of weights of outbound edges from each node <em>i</em> to all adjacent nodes. Note: This is the outDegree Centrality. To compute inDegree Centrality, use the Degree Prestige measure.</p><p><em>Closeness Centrality (CC):</em></p>The inverted sum of geodesic distances from each node <em>u</em> to all other nodes. <p><em>IR Closeness Centrality (IRCC):</em></p><p>The ratio of the fraction of nodes reachable by each node <em>u</em> to the average distance of these nodes from <em>u</em>.</p><p><em>Betweenness Centrality (BC):</em></p><p>The sum of delta<sub>(s,t,u)</sub> for all s,t ∈ V where delta<sub>(s,t,u)</sub> is the ratio of all geodesics between nodes <em>s</em> and <em>t</em> which run through node <em>u</em>.</p> <p><em>Stress Centrality (SC):</em></p><p>The sum of sigma<sub>(s,t,u)</sub> for all s,t ∈ V where sigma<sub>(s,t,u)</sub> is the number of geodesics between nodes <em>s</em> and <em>t</em> which run through node <em>u</em>.</p> <p><em>Eccentricity Centrality (EC):</em></p><p>Also known as Harary Graph Centrality. The inverse maximum geodesic distance from node <em>u</em> to all other nodes in the network.<p><em>Power Centrality (PC):</em></p><p>The sum of the sizes of all N<sub>th</sub>-order neighbourhoods of node <em>u</em> with weight 1/n.</p><p><em>Information Centrality (IC):</em></p><p>Measures the information flow through all paths between actors weighted by strength of tie and distance.</p><p><em>Eigenvector Centrality (EVC):</em></p><p>The EVC score of each node <em>i</em> is the i<sub>th</sub> element of the leading eigenvector of the adjacency matrix, that is the eigenvector corresponding to the largest positive eigenvalue. <p><em>Degree Prestige (DP):</em></p><p>Also known as InDegree Centrality, it is the sum of inbound edges to a node <em>u</em> from all adjacent nodes. </p><p><em>PageRank Prestige (PRP):</em></p><p>For each node <em>u</em> counts all inbound links (edges) to it, but it normalizes each inbound link from another node <em>v</em> by the outDegree of <em>v</em>. </p><p><em>Proximity Prestige (PP):</em></p><p>The ratio of the proportion of nodes who can reach each node <em>u</em> to the average distance these nodes are from it. Similar to Closeness Centrality but it counts only inbound distances to each actor, thus it is a measure of actor prestige.</p> Type: Select layout type for the selected model <p><b>Layout Type</b></p></p>Select a layout type (radial, level, node size or node color) for the selected prominence-based model you want to apply to the network. Please note that node coloring works only for basic shapes (box, circle, etc) not for image icons.</p> Apply By Prominence Index <p><b>Visualize by prominence index</b/></p><p>Apply a prominence-based layout model to the network. </p><p>For instance, you can apply a Degree Centrality layout. </p><p>For each prominence index, you must select a layout type:</p><p>Radial, Levels, NodeSize or NodeColor.</p><p>Please note that node coloring works only for basic shapes (box, circle, etc) not for image icons.</p> Model: None Eades Spring Embedder Select a Force-Directed layout model. <p><b>Visualize by a Force-Directed Placement layout model.</b></p> <p>Available models: </p><p><em>Kamada-Kawai</em></p><p>The best variant of the Spring Embedder family of models. <p>In this the graph is considered to be a dynamic system where every edge is between two actors is a 'spring' of a desirable length, which corresponds to their graph theoretic distance. </p><p>In this way, the optimal layout of the graph is the state with the minimum imbalance. The degree of imbalance is formulated as the total spring energy: the square summation of the differences between desirable distances and real ones for all pairs of vertices.</p><p><em>Fruchterman-Reingold:</em></p><p>In this model, the vertices behave as atomic particles or celestial bodies, exerting attractive and repulsive forces to each other. Again, only vertices that are neighbours attract each other but, unlike Eades Spring Embedder, all vertices repel each other.</p><p><em>Eades Spring Embedder:</em></p><p>A spring-gravitational model, where each node is regarded as physical object (ring) repelling all other non-adjacent nodes, while springs between connected nodes attract them.</p> By Force-Directed Model Control Panel The type of the network: directed or undirected. Toggle the menu option Edit->Edges->Undirected Edges to change it The loaded network, if any, is directed and any link you add between nodes will be a directed arc. If you want to work with undirected edges and/or transform the loaded network (if any) to undirected toggle the option Edit->Edges->Undirected or press CTRL+E+U Directed Directed data mode. Toggle the menu option Edit->Edges->Undirected Edges to change it The loaded network, if any, is directed and any link you add between nodes will be a directed arc. If you want to work with undirected edges and/or transform the loaded network (if any) to undirected toggle the option Edit->Edges->Undirected. The loaded network, if any, is directed and any link you add between nodes will be a directed arc. If you want to work with undirected edges and/or transform the loaded network (if any) to undirected toggle the option Edit->Edges->Undirected Nodes: Each actor in a social netwok is visualized as a node (aka vertex). <p><b>Nodes</b></p><p>Each actor in a social netwok is visualized as a node (aka vertex) in a graph. This is total number of actors (aka nodes or vertices) in this social network.</p> The total number of actors (aka nodes or vertices) in the social network. This is the total number of actors (aka nodes or vertices) in the social network. Arcs: Each link between a pair of actors in a social network is visualized as an edge or arc. <p><b>Edges</b></p>Each link between a pair of actors in a social network is visualized as an undirected edge or a directed edge (aka arc). The total number of directed edges in the social network. This is the total number of directed edges (links between actors) in the social network. Density: The density d is the ratio of existing edges to all possible edges <p><b>Density</b></p><p>The density <em>d</em> of a social network is the ratio of existing edges to all possible edges ( n*(n-1) ) between the nodes of the network</p>. The network density, the ratio of existing edges to all possible edges ( n*(n-1) ) between nodes. <p>This is the density of the network. <p>The density of a network is the ratio of existing edges to all possible edges ( n*(n-1) ) between nodes.</p> Selection Selected nodes. The number of selected nodes (vertices). Selected edges. The number of selected edges. Clicked Node Number: The node number of the last clicked node. The node number of the last clicked node. Zero means no node clicked. This is the node number of the last clicked node. Becomes zero when you click on something other than a node. The node number of the last clicked node. Zero if you clicked something else. In-Degree: The inDegree of a node is the sum of all inbound edge weights. The sum of all inbound edge weights of the last clicked node. Zero if you clicked something else. This is the sum of all inbound edge weights of last clicked node. Becomes zero when you click on something other than a node. Out-Degree: The outDegree of a node is the sum of all outbound edge weights. The sum of all outbound edge weights of the last clicked node. Zero if you clicked something else. This is the sum of all outbound edge weights of the last clicked node. Becomes zero when you click on something other than a node. Clicked Edge Name: The name of the last clicked edge. This is the name of the last clicked edge. Becomes zero when you click on somethingto other than an edge The name of the last clicked edge.Zero when you click on something else. Weight: The weight of the clicked edge. This is the weight of the last clicked edge. Becomes zero when you click on something other than an edge The weight of the last clicked edge. Zero when you click on something else. The weight of the reciprocal edge. This is the reciprocal weight of the last clicked reciprocated edge. Becomes zero when you click on something other than an edge The reciprocal weight of the last clicked reciprocated edge. Becomes zero when you click on something other than an edge Statistics Panel Zoom in the network. Zoom in the network. Or press Cltr and use mouse wheel. Zoom In. Zooms in the network (Ctrl++).You can also press Cltr and use the mouse wheel. Zoom out. Zoom out of the actual network. Or press Cltr and use mouse wheel. Zoom out. Zooms out of the actual network. (Ctrl+-)You can also press Cltr and use the mouse wheel. Zoom slider: Drag up to zoom in. Drag down to zoom out. Rotates the canvas counterclockwise Rotates the canvas counterclockwise. Rotates the canvas clockwise. Rotate slider: Drag to left to rotate clockwise. Drag to right to rotate counterclockwise. Rotate slider: Drag to left to rotate clockwise. Drag to right to rotate counterclockwise. Reset Reset zoom and rotation to zero (or press Ctrl+0) Reset zoom and rotation to zero (Ctrl+0) Application initialization. Please wait... BackgroundImage on. &%1 %2 Useful information Error Nothing to do! Load or create a social network first No network! Load social network data or create a new social network first. Nothing to do! Load social network data or create edges first No edges! Load social network data or create some edges first. GraphML (*.graphml *.xml);;All (*) Pajek (*.net *.paj *.pajek);;All (*) Adjacency (*.csv *.sm *.adj *.txt);;All (*) GraphViz (*.dot);;All (*) UCINET (*.dl *.dat);;All (*) GML (*.gml);;All (*) Weighted Edge List (*.txt *.list *.edgelist *.lst *.wlst);;All (*) Simple Edge List (*.txt *.list *.edgelist *.lst);;All (*) Two-Mode Sociomatrix (*.2sm *.aff);;All (*) GraphML (*.graphml *.xml);;GML (*.gml *.xml);;Pajek (*.net *.pajek *.paj);;UCINET (*.dl *.dat);;Adjacency (*.csv *.adj *.sm *.txt);;GraphViz (*.dot);;Weighted Edge List (*.txt *.edgelist *.list *.lst *.wlst);;Simple Edge List (*.txt *.edgelist *.list *.lst);;Two-Mode Sociomatrix (*.2sm *.aff);;All (*) GraphML GML UCINET Adjacency GraphViz Edge List (weighted) Edge List (simple, non-weighted) Two-mode sociomatrix Selected file has ambiguous file extension! You selected: %1 The name of this file has either an unknown extension or an extension used by different network file formats. SocNetV supports the following social network file formats. In parentheses the expected extension. - GraphML (.graphml or .xml) - GML (.gml or .xml) - Pajek (.paj or .pajek or .net) - UCINET (.dl .dat) - GraphViz (.dot) - Adjacency Matrix (.csv, .txt, .sm or .adj) - Simple Edge List (.list or .lst) - Weighted Edge List (.wlist or .wlst) - Two-Mode / affiliation (.2sm or .aff) If you are sure the file is of a supported format, please select the right format from the list below. Opening network file aborted. Saving file... Select type... Select type of edge list format SocNetV can parse two kinds of edgelist formats: A. Edge lists with edge weights, where each line has exactly 3 columns: source target weight, i.e.: 1 2 1 2 3 1 3 4 2 4 5 1 B. Simple edge lists without weights, where each line has two or more columns in the form: source, target1, target2, ... , i.e.: 1 2 3 4 5 6 2 3 4 3 5 8 7 Please select the appropriate type of edge list format of the file you want to load: Weighted Simple non-weighted No file selected. Cannot read file %1: %2 Opt for labels Node labels? This file contains an adjacency matrix (sociomatrix). Please specify whether there are node labels defined on the first (comment) line. Column delimiter in file SocNetV supports edge list and adjacency files with arbitrary column delimiters. The default delimiter is one or more spaces. If the column delimiter in this file is other than simple space or TAB, please enter it below. For instance, if the delimiter is a comma or pipe enter "," or "|" respectively. Leave empty to use space or TAB as delimiter. Error loading file Error loading network file Sorry, the selected file is not in a supported format or encoding, or contains formatting errors. The error message was: %1 What now? Review the message above to see if it helps you to fix the data file. Try a different codec in the preview window or if the file is of a legacy format (i.e. Pajek, UCINET, GraphViz, etc), please use the options in the Import sub menu. Unrecognized format. Please specify the file-format using the Import Menu. %1 formatted network, named '%2', loaded. Nodes: %3, Edges: %4, Density: %5. Elapsed time: %6 ms Add new relation Enter a name for a new relation between the actors. A relation is a collection of ties of a specific kind between the network actors. For instance, enter "friendship" if the edges of this relation refer to the set of friendships between pairs of actors. Enter a name for the new relation (or press Cancel): Error. Relation name is used! The relation name is already used. Please use another relation name that is not already used. Error. No relation name entered! You did not type a name for this new relation New relation cancelled. New relation named %1, added. Added a new relation named: %1. Enter a new name for this relation. Not a valid name. You did not enter a valid name for this relation. Opening Image export dialog. No filename. Exporting to Image aborted. Network exported to image file. Image filename: %1 Error exporting to image file! Error while exporting network to image file: Opening PDF export dialog. No filename. Exporting to PDF aborted. Network exported to PDF file. PDF filename: %1 Exporting active network under new filename... Export Network to File Named... Pajek (*.paj *.net *.pajek);;All (*) Missing file extension. I will use .paj instead. Missing file extension. I will use the .paj extension. Appending an extension .paj to the given filename... Adjacency (*.csv *.txt *.adj *.sm *.net);;All (*) Missing file extension. I will use .csv instead. Missing file extension. I will use the .csv extension. Appending an extension .csv to the given filename... Weighted graph. Social network with valued/weighted edges Social network with valued/weighted edges This social network includes valued/weighted edges (the depicted graph is weighted). Do you want to save the edge weights in the adjacency file? Select Yes if you want to save edge values in the resulting file. Select No, if you don't want edge values to be saved. In the later case, all non-zero values will be truncated to 1. Displaying network data file %1 New network not saved yet. You might want to save it first. This new network you created has not been saved yet. Do you want to open a file dialog to save your work (then I will display the file)? Current network has been modified. Save to the original file? Current social network has been modified since last save. Do you want to save it to the original file? New Network File Enter your network data here Creating and writing adjacency matrix Adjacency matrix saved as Creating plot of adjacency matrix of %1 nodes. Very large network to plot! Warning: Really large network To plot a %1 x %1 matrix arranged in HTML table, I will need time to write a very large .html file , circa %2 MB in size. Instead, I can create a simpler / smaller HTML file without table. Press Yes to continue with simpler version, Press No to create large file with HTML table. Visual form of adjacency matrix saved as Generate a random Erdos-Renyi network. Creating new Erdos-Renyi random network. Please wait... ErdΕ‘s–RΓ©nyi network creation cancelled or did not finish. ErdΕ‘s–RΓ©nyi random network created. Random network created. A new random network has been created according to the ErdΕ‘s–RΓ©nyi model. On average, edges should be %1. This graph is almost surely connected because: probability > ln(n) that is: %2 < %3 On average, edges should be %1. This graph is almost surely not connected because: probability < ln(n) that is: %2 < %3 Generate a random Scale-Free network. Scale-free network creation cancelled or did not finish. Scale-free random network created. Random network created. A new scale-free random network with %1 nodes has been created according to the BarabΓ‘si–Albert model. A scale-free network is a network whose degree distribution follows a power law. Generate a random Small-World network. Small-world network creation cancelled or did not finish. Small-World random network created. Random network created. A new random network with %1 nodes has been created according to the Watts & Strogatz model. A small-world network has short average path lengths and high clustering coefficient. Generate a d-regular random network. d-regular network creation cancelled or did not finish. d-regular network created. Random network created. A new d-regular random network with %1 nodes has been created. Each node has the same number <em>%1</em> of neighbours, aka the same degree d. Create ring lattice This will create a ring lattice network, where each node has degree d: d/2 edges to the right and d/2 to the left. Please enter the number of nodes you want: Create ring lattice... Now, enter an even number d. This is the total number of edges each new node will have: Error. Cannot create such network. Error. Cannot create such network! The degree %1 is not an even number. A ring lattice is a graph with N vertices each connected to d neighbors, d / 2 on each side. Please try again entering an even number as degree. Ring lattice random network created. Random network created. A new ring-lattice random network with %1 nodes has been created. A ring lattice is a graph with N vertices each connected to d neighbors, d / 2 on each side. Generate a lattice network. Lattice network creation cancelled or did not finish. Lattice random network created. Random network created. A new lattice random network with %1 nodes has been created. A lattice is a network whose drawing forms a regular tiling. Lattices are also known as meshes or grids. No SSL support. I cannot verify that your computer Operating System has OpenSSL support. OpenSSL is an Open Source software library for the Transport Layer Security (TLS) protocol (aka SSL), for applications that secure communications over computer networks. It is widely used by Internet servers, including the majority of HTTPS websites. Without OpenSSL libraries installed in your computer, I cannot crawl webpages/URLs using https:// So, please download and install OpenSSL in your OS and try again. Hint: Go to Help > System Information to see which OpenSSL version you need to install. Shows the total number of undirected edges in the network. The total number of undirected edges in the network. Undirected data mode. Toggle the menu option Edit->Edges->Undirected Edges to change it The loaded network, if any, is undirected and any edge you add between nodes will be undirected. If you want to work with directed edges and/or transform the loaded network (if any) to directed disable the option Edit->Edges->Undirected or press CTRL+E+U The loaded network, if any, is undirected and any edge you add between nodes will be undirected. If you want to work with directed edges and/or transform the loaded network (if any) to directed disable the option Edit->Edges->Undirected Edges: Shows the total number of directed edges in the network. The total number of directed edges in the network. The loaded network, if any, is directed and any link you add between nodes will be a directed arc. If you want to work with undirected edges and/or transform the loaded network (if any) to undirected enable the option Edit->Edges->Undirected Remove nodes node Selected nodes: %1 Selection cleared New random positioned node (numbered %1) added. Node find dialog opened. Enter your choices. Error. Cannot remove node! Error. Cannot remove this node! This a network with more than 1 relations. If you remove a node from the active relation, and then ask me to go to the previous or the next relation, then I would crash because I would try to display edges from a deleted node.You cannot remove nodes in multirelational networks. Removed %1 nodes. Remove node Node removed completely. Choose a node between ( Updated the properties of node %1. Updated the properties of %1 nodes. Error. Not enough nodes selected. Cannot create new clique because you have not selected enough nodes. Select at least three nodes first. Clique created. A new clique has been created from Not enough nodes selected. Cannot create new star subgraph because you have not selected enough nodes. To create a star subgraph from selected nodes, enter the number of the central actor ( Star subgraph created. A new star subgraph has been created with nodes. Cannot create new cycle subgraph because you have not selected enough nodes. Cycle subgraph created. A new cycle subgraph has been created with select nodes. Cannot create new line subgraph because you have not selected enough nodes. Line subgraph created. A new line subgraph has been created with selected nodes. Change all nodes' color. Invalid color. Select new size for all nodes: Change node shapes aborted. Select an icon Images (*.png *.jpg *.jpeg *.svg);;All (*.*) All shapes have been changed. Node shape has been changed. Change all node numbers size to: (1-16) Changed node numbers size. Node number color changed. Change all node numbers distance from their nodes to: (1-16) Change node number distance aborted. Changed node number distance. Change all node labels text size to: (1-16) Changed node label size. Label colors changed. Change all node labels distance from their nodes to: (1-16) Change node label distance aborted. Changed node label distance. ## NODE (selected nodes: Create a clique from selected nodes Create a star from Create a cycle from Create a line from Create a clique from selected nodes Create a star from selected nodes Create a cycle from selected nodes Create a line from selected nodes Position (%1, %2): Node %3, label %4 - In-Degree: %5, Out-Degree: %6 Position (%1,%2): Double-click to create a new node. Undirected edge %1 <--> %2 of weight %3 has been selected. Click anywhere else to unselect it. Reciprocated edge %1 <--> %2 has been selected. Weight %1 --> %2 = %3, Weight %2 --> %1 = %4. Click anywhere else to unselect it. Directed edge %1 --> %2 of weight %3 has been selected. Click again to unselect it. This will draw a new edge between two nodes. Enter source node ( Error. That node does not exist! Are you sure you entered the correct node number? Source node: Now enter a target node [ Source and target nodes accepted. Please, enter the weight of new edge: Error. That edge already exists! Are you sure you entered the correct node numbers? New edge %1 -> %2 created, weight %3. Remove edge Error. Cannot find that edge! Select edge This is a reciprocated edge. Select direction to remove: Select edge source node: ( Select edge target node: ( Change edge label Enter label: Changed edge label. Change edge label aborted. Changed edges color. edges color change aborted. Select new color.... Edge color changed. Change edge color aborted. This is a reciprocated edge. Select direction: New edge weight: Change edge weight cancelled. All ties have been symmetrized. All ties between nodes have been symmetrized. The network is now symmetric. New cocitation relation added. Ready New cocitation relation has been added to the network. In the new relation, there are ties only between pairs of nodes who were cocited by others. New binary relation added. New dichotomized relation created A new relation called "%1" has been added to the network, using the given dichotomization threshold. Edge dichotomization finished. Select Symmetrize social network by examining strong ties This network has multiple relations. Symmetrize by examining reciprocated ties across all relations or just the current relation? all relations current relation New symmetric relation created from strong ties New relation created from strong ties A new relation "%1" has been added to the network. by counting reciprocated ties only. This relation is binary and symmetric. Undirected data mode. All existing directed edges transformed to undirected. Ready Undirected data mode. Any edge you add will be undirected. Ready Directed data mode. All existing undirected edges transformed to directed. Ready Directed data mode. Any new edge you add will be directed. Ready Directed data mode. All existing undirected edges transformed to directed. Directed data mode. Any new edge you add will be directed. Isolated nodes disabled. Isolated nodes enabled. Unilateral (weak) edges disabled. Unilateral (weak) edges enabled. Nodes in random positions. Nodes in random concentric circles. Eades Spring Embedder β€” Size Warning The Eades Spring Embedder was designed for graphs with fewer than 30 nodes. This network has %1 nodes. The layout may get stuck in local minima and the result may not be aesthetically pleasing. Consider using the Fruchterman-Reingold or Kamada-Kawai models instead, which handle larger graphs better. Spring-Gravitational (Eades) model embedded. Fruchterman & Reingold model embedded. Kamada & Kawai model embedded. Please note that this function is <b>SLOW</b> on large networks (n>200), since it will calculate a (n x n) matrix A with: <br>Aii=1+weighted_degree_ni <br>Aij=1 if (i,j)=0 <br>Aij=1-wij if (i,j)=wij <br>Next, it will compute the inverse matrix C of A. The computation of the inverse matrix is a CPU intensive function although it uses LU decomposition. <br>How slow is this? For instance, to compute IC scores of 600 nodes on a modern i7 4790K CPU you will need to wait for 2 minutes at least. <br>Are you sure you want to continue? Nodes in inner circles have higher %1 score. Nodes in upper levels have higher %1 score. Bigger nodes have greater %1 score. Nodes with warmer color have greater %1 score. Layout Guides are displayed Layout Guides removed Reciprocity report saved as: Symmetric network. The adjacency matrix is symmetric. Non symmetric network. The adjacency matrix is not symmetric. Inverting adjacency matrix. Computation canceled. Inverse matrix saved as: Transposing adjacency matrix. Transpose adjacency matrix saved as: Computing Cocitation matrix. Cocitation matrix saved as: Computing Degree matrix. Degree matrix saved as: Computing Laplacian matrix Laplacian matrix saved as: Non-Weighted Network You do not work on a weighted network at the moment. Therefore, I will not consider edge weights during computations. This option applies only when you load or create a weighted network Weighted Network This is a weighted network. Consider edge weights? The ties in this network have weights (non-unit values) assigned to them. Do you want me to take these edge weights into account (i.e. when computing distances) ? Inverse edge weights during calculations? If the edge weights denote cost or real distances (i.e. miles between cities), press No, since the distance between two nodes should be the quickest or cheaper one. If the weights denote value or strength (i.e. votes or interaction), press Yes to inverse the weights, since the distance between two nodes should be the most valuable one. Select source node (%1..%2): Select target node (%1..%2): Geodesic Distance: %1 Nodes %1 and %2 are connected through at least one path. The length of the shortest path is %3. Nodes %1 and %2 are not connected. In this case, their geodesic distance is considered to be infinite. Computing geodesic distances. Please wait... Geodesic Distances matrix saved as: Computing geodesics (number of shortest paths) for each pair. Please wait... Geodesics Matrix saved as: Computing graph diameter. Please wait... Network diameter computed. Network diameter computed. D = %1 The diameter of a network is the maximum geodesic distance (maximum shortest path length) between any two nodes. Note, since this is a weighted network, the diameter can be greater than N. The diameter of a network is the maximum geodesic distance (maximum shortest path length) between any two nodes. Note, edge weights were disregarded during the computation. This is the diameter of the corresponding network without weights. The diameter of a network is the maximum geodesic distance (maximum shortest path length) between any two nodes. Note, since this is a non-weighted network, the diameter is always smaller than N-1. Computing Average Graph Distance. Please wait... Average graph distance computed. Average graph distance computed. d = %1 The average graph distance is the average length of shortest paths (geodesics) for all possible pairs of nodes. The average distance in this connected network is the sum of pair-wise distances divided by N * (N - 1). Average distance computed. Average distance computed. d = %1 The average graph distance is the average length of shortest paths (geodesics) for all possible pairs of nodes. The average distance in this disconnected network is the sum of pair-wise distances divided by the number of existing geodesics. Eccentricities saved as: This empty network is considered connected! Empty network is considered connected! A null network (empty graph) is considered connected. This 1-actor network is considered connected! A 1-actor network (singleton graph) is always considered connected. This directed network is strongly connected. A 1-actor network (singleton graph) is considered connected. This undirected network is connected. This network has an undirected graph which is connected. This directed network is disconnected. There are pairs of nodes that are not connected with any directed path. This undirected network is not connected. There are pairs of nodes that are not connected with any path. Select desired length of walk: (2 to %1) Walks of length %1 matrix saved as: Please note that this function is VERY SLOW on large networks (n>50), since it will calculate all powers of the sociomatrix up to n-1 in order to find out all possible walks. If you need to make a simple reachability test, we advise to use the Reachability Matrix function instead. Are you sure you want to continue? Computing total walks matrix. Please wait... Computing reachability matrix. Please wait... Reachability matrix saved as: Clustering Coefficients saved as: Clique Census saved as: Triad Census saved as: Similarity matrix saved as: Tie profile dissimilarities matrix saved as: Pearson correlation coefficients matrix saved as: Hierarchical Cluster Analysis saved as: Opening Out-Degree Centralities report... Out-Degree Centralities report saved as: Opening Closeness Centralities report... Closeness Centralities report saved as: Opening Influence Range Closeness Centralities report... Influence Range Closeness Centralities report saved as: Opening Betweenness Centralities report... Betweenness Centralities report saved as: Warning! Running Degree Prestige index on an undirected network. This network is not directed (undirected graph). The Degree Prestige index counts inbound edges, therefore it is meaningful on directed networks. For undirected networks, such as this one, Degree Prestige is the same as Degree Centrality. Opening Degree Prestige (in-degree) report... Degree Prestige (in-degree) report saved as: Opening PageRank Prestige report... PageRank Prestige report saved as: Opening Proximity Prestige report... Proximity Prestige report saved as: Opening Information Centralities report... Information Centralities report saved as: Opening Eigenvector Centralities report... Eigenvector Centralities report saved as: Opening Stress Centralities report... Stress Centralities report saved as: Opening Gil-Schmidt Power Centralities report... Gil-Schmidt Power Centralities report saved as: Eccentricity Centralities report saved as: Distribution of %1 values: Min value: %2 Max value: %3 Please note that, due to the small size of this widget, if you display a distribution in Bar Chart where there are more than 10 values, the widget will not show all bars. In this case, use Line or Area Chart (from Settings). In any case, the large chart in the HTML report is better than this widget... Toggle Edges. Please wait... Edges are invisible now. Click again the same menu to display them. Edges visible again... Toggling Edges' Arrows. Please wait... Arrows in edges: on. Arrows in edges: off. Toggle edges bezier. Please wait... Change all edges offset from their nodes to: (1-16) Change edge offset aborted. Changed edge offset from nodes. Toggle Edges Labels. Please wait... Edge labels are invisible now. Click the same option again to display them. Edge labels are visible again... Toggle zero-weight edges saving. Please wait... Zero-weight edges will be saved to graphml files. Zero-weight edges will NOT be saved to graphml files. Toggle openGL. Please wait... Using openGL off. Using OpenGL on. Toggle anti-aliasing. Please wait... To create a new node: - double-click somewhere on the canvas - or press the keyboard shortcut CTRL+. (dot) - or press the Add Node button on the left panel SocNetV can work with either undirected or directed data. When you start SocNetV for the first time, the application uses the 'directed data' mode; every edge you create is directed. To enter the 'undirected data' mode, press CTRL+E+U or enable the menu option Edit->Edges->Undirected Edges If your screen is small, and the canvas appears even smaller hide the Control and/or Statistics panel. Then the canvas will expand to the whole application window. Open the Settings/Preferences dialog->Window options and disable the two panels. A scale-free network is a network whose degree distribution follows a power law. SocNetV generates random scale-free networks according to the BarabΓ‘si–Albert (BA) model using a preferential attachment mechanism. To delete a node permanently: - right-click on it and select Remove Node - or press CTRL+ALT+. and enter its number - or press the Remove Node button on the Control Panel To rotate the network: - drag the bottom slider to left or right - or click the buttons on the corners of the bottom slider - or press CTRL and the left or right arrow. To create a new edge between nodes A and B: - double-click on node A, then double-click on node B. - or middle-click on node A, and again on node B. - or right-click on the node, then select Add Edge from the popup. - or press the keyboard shortcut CTRL+/ - or press the Add Edge button on the Control Panel Add a label to an edge by right-clicking on it and selecting Change Label. You can change the background color of the canvas. Do it from the menu Options > View or permanently save this setting in Settings/Preferences. Default node colors, shapes and sizes can be changed. Open the Settings/Preferences dialog and use the options on the Node tab. The Statistics Panel shows network-level information (i.e. density) as well as info about any node you clicked on (inDegrees, outDegrees, clustering). You can move any node by left-clicking and dragging it with your mouse. If you want you can move multiple nodes at once. Left-click on empty space on the canvas and drag to create a rectangle selection around them. Then left-click on one of the selected nodes and drag it. To save the node positions in a network, you need to save your data in a format which supports node positions, suchs as GraphML or Pajek. Embed visualization models on the network from the options in the Layout menu or the select boxes on the left Control Panel. To change the label of a node right-click on it, and click Selected Node Properties from the popup menu. All basic operations of SocNetV are available from the left Control panel or by right-clicking on a Node or an Edge or on canvas empty space. Node info (number, position, degree, etc) is displayed on the Status bar, when you left-click on it. Edge information is displayed on the Status bar, when you left-click on it. Save your work often, especially when working with large data sets. SocNetV alogorithms are faster when working with saved data. You can change the precision of real numbers in reports. Go to Settings > General and change it under Reports > Real number precision. The Closeness Centrality (CC) of a node v, is the inverse sum of the shortest distances between v and every other node. CC is interpreted as the ability to access information through the 'grapevine' of network members. Nodes with high closeness centrality are those who can reach many other nodes in few steps. This index can be calculated in both graphs and digraphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1. The Information Centrality (IC) index counts all paths between nodes weighted by strength of tie and distance. This centrality measure developed by Stephenson and Zelen (1989) focuses on how information might flow through many different paths. This index should be calculated only for undirected graphs. Note: To compute this index, SocNetV drops all isolated nodes. Opening the SocNetV Manual in your default web browser.... Newer SocNetV version available! <p>Your version: <p>Remote version: <b> <p><b>There is a newer SocNetV version available!</b></p><p>Do you want to download the latest version now?</p><p>Press Yes, and I will open your default web browser for you to download the latest SocNetV package...</p> Opening SocNetV website in your default web browser.... <p>Remote version: <p>You are running the latest and greatest version of SocNetV.<br/>Nothing to do!</p> <b>Soc</b>ial <b>Net</b>work <b>V</b>isualizer (SocNetV) <p><b>Version</b>: <p>Website: <a href="https://socnetv.org">https://socnetv.org</a></p> <p>(C) 2005-2026 by Dimitris B. Kalamaras</p> <p><a href="https://socnetv.org/contact">Have questions? Contact us!</a></p> <p><b>Fortune cookie: </b><br> " <p><b>License:</b><p> <p>This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.</p> <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p> <p>You should have received a copy of the GNU General Public License along with this program; If not, see http://www.gnu.org/licenses/</p> Pajek Saving network under new filename... Saving aborted Network has not been saved. Do you want to save before closing it? Printing... Choose a node to remove between ( Source node: ( Target node: ( Distance between two nodes Distance calculation operation cancelled. Nothing to do... Toggle Nodes Numbers. Please wait... Node Numbers are invisible now. Click the same option again to display them. Node Numbers are visible again... Toggle Numbers inside nodes. Please wait... Numbers inside nodes... Numbers outside nodes... Node Labels are visible again... Toggle Nodes Labels. Please wait... Node Labels are invisible now. Click the same option again to display them. Change font size: Aborted. Two-Mode Sociomatrix β€” Select Import Mode Two-mode sociomatrix import This file contains a two-mode (bipartite) sociomatrix, where rows represent one set of actors (e.g. persons) and columns represent another set (e.g. events or groups). How would you like to import it? Bipartite graph: creates both sets of nodes and connects each person to the events they attend. (Recommended) Person network: creates only the person nodes and connects two persons if they share at least one event. Event network: creates only the event nodes and connects two events if they share at least one person. Bipartite graph Person network Event network Toggle Edges Weights. Please wait... Edge weights are invisible now. Click the same option again to display them. Edge weights are visible again... Anti-aliasing off. Anti-aliasing on. Toggle anti-aliasing auto adjust. Please wait... Antialiasing auto-adjustment off. Antialiasing auto-adjustment on. Toggle smooth pixmap transformations. Please wait... Smooth pixmap transformations off. Smooth pixmap transformations on. Toggle saving painter state. Please wait... Saving painter state off. Saving painter state on. Toggle canvas background caching state. Please wait... Canvas background caching off. Canvas background caching on. Toggle edge highlighting state. Please wait... Edge highlighting off. Edge highlighting on. Setting canvas update mode. Please wait... Canvas update mode: Setting canvas index method. Please wait... Canvas index method: SocNetV logo print off. SocNetV logo print on. Toggle progressbar... Progress bars off. Progress bars on. Debug messages off. Debug messages on. Background changed. Toggle BackgroundImage... BackgroundImage off. Select one image Images (*.png *.jpg *.jpeg);;All (*.*) Full screen mode off. Press F11 again to enter full screen. Full screen mode on. Press F11 again to exit full screen. Toggle toolbar... Toolbar off. Toolbar on. Toggle statusbar... Status bar off. Status bar on. Toggle left panel... Left Panel off. Left Panel on. Right Panel off. Right Panel on. Cannot read stylesheet file %1: %2 Tip Of The Day Parser Cannot open file: %1 Invalid UCINET-formatted file. The file does not start with DL in first non-comment line %1 Problem interpreting UCINET-formatted file. Cannot convert N value to integer at line %1. Problem interpreting UCINET-formatted file. Cannot convert NM value to integer at line %1 Problem interpreting UCINET-formatted file. Cannot convert NR value to integer at line %1 Problem interpreting UCINET-formatted file. Cannot convert NC value to integer at line %1 Invalid UCINET format declaration. Expected 'FULLMATRIX' or 'edgelist' but found: %1 Error reading UCINET-formatted file: Number of nodes found (%1) does not match declared N=%2 Matrix row size mismatch. Expected %1 but got %2 at line %3. Problem interpreting UCINET fullmatrix-formatted file. In edge (%1->%2), the weight (%3) could not be converted to number, at line %4. Problem interpreting UCINET two-mode fullmatrix-formatted file. The file declared %1 columns initially, but I found a different number %2 of matrix columns, at line %3. Problem interpreting UCINET two-mode file. In edge (%1->%2), the weight (%3) cannot be converted to number, at line %4. Problem interpreting UCINET-formatted file. The file was declared as edgelist but I found a line which did not have 3 elements (source, target, weight), at line %1 Error while reading UCINET-formatted file. Cannot convert N value to integer. Problem interpreting UCINET file. Cannot convert NM value to integer. Error while reading UCINET-formatted file. Cannot convert NR value to integer. Error while reading UCINET-formatted file. Cannot convert NC value to integer. Not a Pajek-formatted file. First not-comment line %1 (at file line %2) does not start with Network or Vertices Not a Pajek-formatted file. First not-comment line does not start with Network or Vertices Invalid Pajek-formatted file. It declares a node with nodeNumber smaller than previous nodes. Invalid Pajek-formatted file. The file declares an edge with a zero source or target nodeNumber. However, each node should have a nodeNumber > 0. Invalid Pajek-formatted file. The file declares arc with a zero source or target nodeNumber. However, each node should have a nodeNumber > 0. Invalid Pajek-formatted file. Could not find node declarations in this file. Invalid adjacency-formatted file. Non-comment line %1 includes reserved keywords ('%2'). Parsing aborted. Invalid Adjacency-formatted file. Node labels line is empty or improperly formatted. Parsing aborted. Invalid Adjacency-formatted file. Row %1 at line %2 has a different number of elements (%3) than expected (%4). Parsing aborted. fileLine: %1 is a comment... Invalid Adjacency-formatted file. Not a NxN matrix. Row %1 declares %2 edges. Expected: %3. Parsing aborted. Error reading Adjacency-formatted file. Element (%1, %2) contains invalid data ('%3'). Parsing aborted. Invalid two-mode sociomatrix file. Non-comment line %1 includes keywords reserved by other file formats (vertices, graphml, network, graph, digraph, DL, xml) Invalid two-mode sociomatrix file. Row %1 has a different number of elements than the first data row. Invalid two-mode sociomatrix file: no data rows found. Invalid GraphML file. XML at startElement but element name not graphml. Invalid GraphML file. XML tokenString at line %1 invalid. Invalid GraphML file. XML has error at line %1, token name %2: %3 Not an GML-formatted file. Non-comment line %1 includes keywords reserved by other file formats (i.e vertices, graphml, network, digraph, DL, xml) Not a proper GML-formatted file. Node id tag at line %1 has non-arithmetic value. Not a proper GML-formatted file. Edge source tag at line %1 has non-arithmetic value. Not a proper GML-formatted file. Edge target tag at line %1 has non-arithmetic value. Not a proper GML-formatted file. Edge weight tag at line %1 has an invalid value. Not a proper GML-formatted file. Node center tag at line %1 cannot be converted to qreal. Not a proper GML-formatted file. Node type tag at line %1 has no value. Not a proper GML-formatted file. Node fill tag at line %1 has no value. Invalid GraphViz (dot) file. The file does not contain 'digraph' or 'graph'. Not a GraphViz-formatted file. First non-comment line includes keywords reserved by other file formats (i.e vertices, graphml, network, DL, xml). Not properly GraphViz-formatted file. First non-comment line should start with "(di)graph netname {" Invalid DOT edge statement: mixed '->' and '--' operators on the same line. Not properly GraphViz-formatted file. Node definition without closing ] Not properly GraphViz-formatted file. Node definition without opening [ Not an EdgeList-formatted file. A non-comment line includes keywords reserved by other file formats (i.e vertices, graphml, network, graph, digraph, DL, xml) Not a properly EdgeList-formatted file. Row %1 has not 3 elements as expected (i.e. source, target, weight) Not an EdgeList-formatted file. Non-comment line %1 includes keywords reserved by other file formats (i.e vertices, graphml, network, graph, digraph, DL, xml) QObject Computing geodesic distances. Please wait... not a GraphML file. invalid GraphML or encoding. Default custom icon for nodes does not exist in the filesystem. The declared icon file was: %1 TextEditor Save file &New Neu Ctrl+N Strg+N Create a new file &Open... Ctrl+O Strg+O Open an existing file &Save Speichern Ctrl+S Strg+S Save the document to disk Save &As... Speichern unter Save the document under a new name E&xit Beenden Ctrl+Q Strg+Q Exit the application Cu&t Ctrl+X Strg+X Cut the current selection's contents to the clipboard &Copy Ctrl+C Copy the current selection's contents to the clipboard &Paste Ctrl+V Paste the clipboard's contents into the current selection &About Show the application's About box About &Qt Show the Qt library's About box &File Datei &Edit &Help File Edit Ready TextEditor The document has been modified. Do you want to save your changes? SocNetV Editor Cannot read file %1: %2. File loaded Cannot write file %1: %2. File saved %1[*] - %2 main Network file to load on startup. You can load a network from a file using `socnetv file.net` where file.net/csv/dot/graphml must be of valid format. See README. Force showing progress dialogs/bars during computations. Do not maximize the app window. Show in full screen mode. Print debug messages to stdout/console. Available verbosity <level>s: 'none', 'min' or 'full'. Default: 'min'. level SocNetV-3.7/translations/socnetv_es.qm000066400000000000000000000001171524402210300201240ustar00rootroot00000000000000<ΈdΚοœ•Ν!Ώ`‘½έ§esBi MainWindowˆSocNetV-3.7/translations/socnetv_es.ts000066400000000000000000026711751524402210300201610ustar00rootroot00000000000000 DialogClusteringHierarchical Hierarchical Clustering Variables in: <html><head/><body><p><span style=" font-weight:600;">Rows: </span>Correlate outbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Columns: </span>Correlate inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Both: </span>Correlate both outbound and inbound ties (or distances, depending on the selected matrix above) between pairs of actors. In this case, the input matrix is expanded by listing row vectors followed by column vectors. This is useful only when you have directed data.</p><p><br/></p></body></html> Enable to include matrix diagonal in calculations Print dendrogram (avoid in large nets) <html><head/><body><p>Agglomerative hierarchical clustering builds a hierarchy of actor clusters, based on their tie/distance dissimilarity, starting with single elements and aggregating them into clusters.</p><p>It takes a matrix (adjacency or geodesic distances) and a distance metric between actors as input and constructs a pair-wise dissimilarity matrix. </p><p>Initially, each actor starts in its own cluster (Level 0). In each subsequent Level, the pair of clusters with minimum distance are merged into a larger cluster. Then, the distance between the new cluster and the old ones is computed, using the specified clustering method (i.e. single-linkage clustering). The process is repeated until all actors end up in the same cluster. </p><p>Select an input matrix, a distance/dissimilarity metric and a clustering method (criterion) for the hierarchical cluster analysis. </p></body></html> Clustering method (criterion): <html><head/><body><p>Supported linkage criteria for agglomerative hierarchical clustering: </p><p><span style=" font-weight:600;">Single-linkage (minimum)</span>: The distance between two clusters will be determined by a single element pair, namely those two elements (one in each cluster) that have the shortest distance between them. In each step, the clusters that have the shortest distance will be merged. </p><p><span style=" font-weight:600;">Complete-linkage (maximum)</span>: The distance between two clusters will be determined by any two elements (one in each cluster) that have the longest distance between them. </p><p><span style=" font-weight:600;">Average-linkage (UPGMA)</span>: The distance between two clusters A and B is equal to the average of distances between all pairs of elements in A and B. </p><p><br/></p></body></html> Input matrix: <html><head/><body><p><span style=" font-weight:600;">Adjacency:</span> Use the active network's adjacency matrix as input to hierarchical clustering.</p><p><span style=" font-weight:600;">Distances:</span> Use the active network's geodedic distances matrix as input. </p></body></html> Distance/dissimilarity metric: <html><head/><body><p>Supported distance metrics for hierarchical clustering:</p><p><span style=" font-weight:600;">Euclidean distance</span>: The square root of the sum of squared differences between tie/distance profiles.</p><p><span style=" font-weight:600;">Jaccard distance</span>: The Jaccard index J is the ratio of same ties/distances reported by each pair of actors to the total number of their ties. Does not count absent ties. The Jaccard distance is 1 - J</p><p><span style=" font-weight:600;">Hamming distance</span>: The number of ties/distances to other actors which differ between each pair of actors.</p><p><span style=" font-weight:600;">Manhattan distance</span>: The sum of absolute differences between tie/distance profiles.<br/></p></body></html> Include input matrix diagonal DialogDataSetSelect Famous SNA data sets <html><head/><body><p>Automatically recreate and visualize known data sets of Social Network Analysis, such as Padgett's Florentine families, Zachary's Karate Club, Knoke's Bureaucracies, etc.</p><p>Select the data set you want to re-create from the list.</p></body></html> <html><head/><body><p>Click to select a data set</p></body></html> DialogDissimilarities Tie profile dissimilarities Distance metric: <html><head/><body><p>Select a matching method. </p><p><span style=" font-weight:600;">Exact Matches</span>: Examines pairs of actors for exact tie or distance matches (present or absent) to other actors and returns a proportion to their overall ties. </p><p><span style=" font-weight:600;">Positive Matches (Jaccard/Co-citation)</span>: Looks for same ties/distances reported by both actors. Returns the ratio to the total number of ties reported.</p><p><span style=" font-weight:600;">Hamming distance</span>: Reports the number of ties/distances to other actors which differ between each pair of actors.</p><p><span style=" font-weight:600;">Cosine similarity</span>: Computes the pair-wise similarity of actors as the dot product of their tie/distance vectors divided by the magnitude of these vectors. <br/></p></body></html> <html><head/><body><p>Compute a <span style=" font-weight:600;">dissimilarities matrix</span>, where each element (i,j) is the pair-wise distance / dissimilarity of actors i and j tie profiles to all other actors, according to a selected metric. </p><p>Select a distance metric. For example, the &quot;Euclidean distance&quot; is the square root of the sum of the squared differences of tie values that actors i and j have to other actors. Hover over &quot;Distance Metric&quot; select box for more info on each metric.</p><p>Also, specify where the &quot;variables&quot; are. For instance, select Rows to measure the outbound ties between all pairs of actors. Select Both to measure both inbound and outbound ties. </p></body></html> Variables in: <html><head/><body><p><span style=" font-weight:600;">Rows: </span>Correlate outbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Columns: </span>Correlate inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Both: </span> Correlate both outbound and inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><br/></p></body></html> Enable to include matrix diagonal in calculations Include input matrix diagonal Euclidean distance Manhattan distance Hamming distance Jaccard distance Chebyshev distance DialogEdgeDichotomization Dichotomize Edges <html><head/><body><p>Enter a threshold value to dichotomize the edges of a valued network, in order to create a new binary relation. All ties with equal or higher values will be set to 1, and all lower will be removed. </p></body></html> Weight Threshold DialogExportImage Export to Image Save to file: <html><head/><body><p><span style=" font-weight:600;">Image filename</span></p><p>The path and the filename of the resulting image. </p><p>Click the button on the right to select a new filename.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-size:10pt; font-weight:600;">Image filename</span></p><p><span style=" font-size:10pt;">Click this button to select the path and the filename of the resulting image.<br/></span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">PDF filename</span></p><p>Click this button to select the path and the filename of the PDF. </p><p><br/></p><p><br/></p></body></html> ... Format <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Image format </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Select the format of the resulting image. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">SocNetV automatically supports all image formats supported currently by Qt in your computer platform. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Click on this drop down to see all supported image formats and select a format for your image.</span></p></body></html> Quality Compression Save to image DialogExportPDF Export to PDF Page Orientation <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">Page Orientation</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Select the page orientation in the PDF: Portrait or landscape. </span></p></body></html> Save to file: <html><head/><body><p><span style=" font-weight:600;">PDF filename</span></p><p>The path and the filename of the PDF. </p><p>Click the button on the right to select a new filename.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-size:10pt; font-weight:600;">PDF filename</span></p><p><span style=" font-size:10pt;">Click this button to select the path and the filename of the PDF. <br/></span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">PDF filename</span></p><p>Click this button to select the path and the filename of the PDF. </p><p><br/></p><p><br/></p></body></html> ... Quality <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">PDF Quality</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Select the quality of the PDF. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">The default value is Screen, which results in a PDF exactly like what you see on the application canvas.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; text-decoration: underline;">Screen</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt; font-weight:600;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Sets the resolution of the print device to the screen resolution. This has the big advantage that the results obtained when painting on the printer will match more or less exactly the visible output on the screen. It is the easiest to use, as font metrics on the screen and on the printer are the same. This is the default value.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; text-decoration: underline;">Print</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">On Windows, sets the printer resolution to that defined for the printer in use. For PDF printing, sets the resolution of the PDF driver to 1200 dpi.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p></body></html> Resolution (DPI) <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; font-weight:600;">DPI</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">Select the PDF resolution in DPI.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">The default value is 75, to match screen resolution.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:10pt;"><br /></p></body></html> Save to pdf PDF (*.pdf) DialogFilterEdgesByWeight Filter edges From this dialog, you can filter edges according to their weight. Select your desired weight threshold, then click on one of the radio buttons further below to control what to do. By default, it will filter edges with weight equal or over the weight threshold. The rest edges will be disabled (and invisible). Weight Threshold: <html><head/><body><p>Enter the weight threshold to use while filtering.</p><p>By default, it filters edges with weights equal or above the selected threshold. </p><p>You can control the behaviour with the radio boxes below. </p></body></html> Enter the weight threshold to use while filtering. By default, it filters all edges with weights equal or above your selected threshold here. You can control the behaviour with the radio boxes below. Select behaviour: Filter edges with weight equal or OVER the above threshold Filter edges with weight equal or BELOW the above threshold This action is not destructive, until you close the app/network. You can undo the filtering by running this action again and selecting a threshold lower than the lowest edge weight (i.e. 0). Obviously, when you save a network with filtering applied, only the filtered edges will be saved. DialogFilterNodesByCentrality Filter nodes Score Threshold: <html><head/><body><p>Enter the score threshold used for hiding nodes.<br/>Use the options below to hide nodes with scores <span style=" font-weight:700;">β‰₯</span> or <span style=" font-weight:700;">≀</span> the threshold.</p></body></html> Enter the threshold to use while filtering. Index: This does not delete nodes; it only hides them in the current view. To undo, run this action again and choose a threshold that hides no nodes (for example, a very large threshold when hiding scores β‰₯ threshold, or a very small threshold when hiding scores ≀ threshold). If you save the network while nodes are hidden, only the visible nodes and their edges will be saved. Select behaviour: Hide nodes with scores β‰₯ threshold Hide nodes with scores ≀ threshold <html><head/><body><p>Hide nodes based on a centrality/prestige score. </p><p>Choose an index and a score threshold, then choose whether to hide nodes with scores β‰₯ the threshold or ≀ the threshold. </p><p><span style=" font-style:italic;">Note: Indices must be computed before they can be used for filtering. Edges connected to hidden nodes will also be hidden. </span></p></body></html> Not computed yet. Run the analysis first. DialogNodeEdit Node Properties <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click the button on the right to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> Custom Icon <html><head/><body><p><span style=" font-weight:600;">Custom Icon </span></p><p>This box only shows the path of the selected custom icon file for all network nodes. If it is empty, it means you have not selected any image yet.</p><p>Click the button on the right to select a new image to be used as custom icon in all the nodes of the network.</p><p>Valid icons are images: JPG, PNG, JPEG, SVG etc.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>This is the path of the default background image. </p><p>Click the button on the right to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Custom Icon </span></p><p>Click this button to select a new custom icon for all nodes of the network. </p><p>Valid icons are images: JPG, PNG, JPEG, SVG etc.</p><p>If you don't select an image file, you must select one of the default shapes from the Node Shape menu above. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click this button to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> ... Custom attributes <html><head/><body><p>Click to remove the selected key-value attribute (you should have already clicked one!).</p></body></html> Remove selected <html><head/><body><p>Enter the key of the attribute, i.e. age or birthdate.</p></body></html> Enter key New Attribute: <html><head/><body><p>Enter the value of this attribute. </p><p>For example, if the key is 'age', then the value might be a number like 19. </p><p>Note: The value supports ISO 8601 dates, i.e. 2011-10-05T14:48:00.000Z</p><p><br/></p></body></html> Enter value Add <html><head/><body><p>Shows the current custom attributes (metadata) of the node. Custom attributes are key-value pairs. </p><p>For example. you could add demographics, work years, age, time events, etc. </p></body></html> Key Value Node shape <html><head/><body><p>Select a node shape. </p><p>If you select &quot;Icon&quot; then you must click on the custom icon button (below) to select the desired icon file from your filesystem. </p></body></html> Node color (click the button to select) Node size <html><head/><body><p>Set the size of the node. </p><p><br/></p><p>Default node size: 8</p></body></html> Node label <html><head/><body><p>Enter a node label. </p><p>If multiple nodes are selected, the label you define here will be set to all of them, along with a numerical suffix, i.e. if you enter &quot;Jim&quot;, then the selected actors will be labeled &quot;Jim1&quot;, &quot;Jim2&quot;, &quot;Jim3&quot; and so on. </p></body></html> Select a new icon Images (*.png *.jpg *.jpeg *.svg);;All (*.*) Select node color DialogNodeFind Find nodes (and select them) <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Find and select nodes (by numbers, labels or index score)</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To find nodes by their number, enter numbers either comma-separated or line by line or array <br /><span style=" font-style:italic;">i.e. 1,2,3 or 1-10) </span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To find nodes by their label enter words either comma separated or line by line <br /><span style=" font-style:italic;">i.e. jim,julia</span> </p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To find and select nodes by their index score, enter the desired score in the form: <br /><span style=" font-style:italic;">&gt; threshold</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">or </p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-style:italic;">&lt; threshold</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">and select the index in the menu below</p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> By Number(s) By Label(s) By Index Score Search for these numbers (enter line by line or csv): <html><head/><body><p>Select how to search nodes (by their number, by their label, or by index score) and enter below what you want to find. Matched nodes will be highlighted.</p></body></html> Index DialogPreviewFile &Encoding: <p>In this area you can preview your text file before actually loading it.</p> <p>SocNetV uses UTF-8 for saving and loading network files, by default. </p><p>If your file is encoded in another encoding, select the correct encoding from the menu and see if strings appear correctly.</p> Preview file & Choose Encoding DialogRandErdosRenyi ErdΕ‘s–RΓ©nyi network generator <html><head/><body><p>Generate random network according to ErdΕ‘s–RΓ©nyi (ER) model. </p><p>In fact, there are two models: in <span style=" font-style:italic;">G(n,p)</span> edges are created with Bernoulli trials, while in <span style=" font-style:italic;">G(n,M) </span>a graph is randomly selected from all graphs with <span style=" font-style:italic;">n</span> nodes and <span style=" font-style:italic;">M</span> edges. Read more in the manual.</p></body></html> <html><head/><body><p>Nodes <span style=" font-style:italic;">n</span></p></body></html> Model <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This will create a new random network using <span style=" font-weight:600;">G(n,p)</span> model, where</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">n</span> is the number of nodes in the final graph</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">p</span> is the probability with which an edge is included in the graph</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If you select this model, you must enter the number of nodes n and the edge probability p. </p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">You may also select if the final network will be undirected or directed and if you want to allow nodes to link to themselves (diagonal non zero).</p></body></html> G(n, p) <html><head/><body><p>This will create a new random network using <span style=" font-weight:600;">G(n,M)</span> model, where</p><p><span style=" font-weight:600;"> n</span> is the number of nodes in the final graph</p><p><span style=" font-weight:600;"> M</span> is the number of edges in the final graph</p><p>If you select this model, you must enter both the number of nodes n and the number of edges M</p><p>You may also select if the final network will be undirected or directed and if you want to allow nodes to link to themselves (diagonal non zero).</p></body></html> G(n, M) <html><head/><body><p>Edges <span style=" font-style:italic;">M </span><span style=" color:#7c7c7c;">for G(n,M) model only</span></p></body></html> <html><head/><body><p>Edge Probability <span style=" color:#7c7c7c;">applicable only in G(n,p) model</span></p></body></html> Graph Mode Undirected Directed Allow diagonals (loops) or set to zero? Yes, allow DialogRandLattice Lattice network generator <html><head/><body><p>Generate a lattice network. You can select how many dimensions the lattice will have (i.e. d=2 for a two-dimensional lattice) and the length of each dimension (i.e. l=3 for a 3x3 lattice of 9 nodes). Read more in the manual.</p></body></html> Nodes <html><head/><body><p><span style=" font-weight:600;">Lattice Nodes </span></p><p>The resulting lattice will have this amount of nodes . </p><p>This value changes automatically as you modify the <span style=" font-style:italic;">length l </span>of each dimension (below).</p></body></html> <html><head/><body><p>Length <span style=" font-style:italic;">l </span>in each dimension </p></body></html> <html><head/><body><p><span style=" font-weight:600;">Length</span></p><p>The size of the lattice in each dimension.</p></body></html> <html><head/><body><p>Dimension <span style=" font-style:italic;">d</span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Dimension </span><span style=" font-weight:600; font-style:italic;">d</span></p><p>The dimension of the lattice.</p><p>I.e. enter 2 for a two-dimensional lattice.</p><p><br/></p></body></html> <html><head/><body><p>Neighborhood <span style=" font-style:italic;">n</span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Neighborhood </span><span style=" font-weight:600; font-style:italic;">n</span></p><p>The distance within which the neighbors on the lattice will be connected</p></body></html> Graph Mode Undirected Directed Circular <html><head/><body><p>If checked, the lattice will be circular</p></body></html> false DialogRandRegular d-Regular network generator <html><head/><body><p>Generate a <span style=" font-style:italic;">d-regular</span> random network of <span style=" font-style:italic;">n</span> nodes. This is a graph where each vertex has the same number of neighbors <span style=" font-style:italic;">d</span>.</p><p>This model produces undirect and directed provided that n &gt; 5, d/<span style=" font-style:italic;">n &lt; 0.5 </span>and <span style=" font-style:italic;">n*d </span>is even. Read more in the manual.</p></body></html> <html><head/><body><p>The number n of nodes in the new Regular Network.</p><p>Note: For a <span style=" font-style:italic;">d</span>-regular graph of <span style=" font-style:italic;">n</span> nodes, it is necessary that <span style=" font-style:italic;">n &gt;= d + 1 </span>and <span style=" font-style:italic;">n*d </span>is even</p></body></html> Nodes <html><head/><body><p>Enter number n of nodes in the new Regular Network.</p><p>Constraints: </p><p><span style=" font-style:italic;">n &gt; 6 </span>and <span style=" font-style:italic;"/></p><p><span style=" font-style:italic;">d/n &gt; 0.5 </span>and</p><p><span style=" font-style:italic;">n*d </span>is even</p></body></html> <html><head/><body><p>Degree <span style=" font-style:italic;">d</span></p></body></html> <html><head/><body><p>Enter the degree <span style=" font-style:italic;">d</span> each new node will have.</p><p>Constraints: </p><p><span style=" font-style:italic;">d/n &gt; 0.5 </span>and</p><p><span style=" font-style:italic;">n*d </span>is even</p><p>In directed Graph Mode, this Degree <span style=" font-style:italic;">d</span> option corresponds to the outDegree and the inDegree, which will be both equal to <span style=" font-style:italic;">d</span> in the generated directed regular network. </p><p><br/></p></body></html> Graph Mode <html><head/><body><p>Click &quot;undirected&quot; to generate an undirected <span style=" font-style:italic;">d</span>-regular network </p></body></html> Undirected <html><head/><body><p>Click &quot;directed&quot; to generate a directed <span style=" font-style:italic;">d</span>-regular network. </p><p>In this case, the Degree <span style=" font-style:italic;">d</span> option above corresponds to the outDegree and the inDegree which will be both equal to <span style=" font-style:italic;">d</span> in the generated regular network. </p><p>For instance, if you select <span style=" font-style:italic;">d</span>=4 and <span style=" font-style:italic;">Graph Mode</span>=directed, then each new node will have outDegree=4 (four outbound edges) and inDegree=4 (four inbound edges). The inbound and outbound edges of each node will not necessarily be from / to the same nodes.</p></body></html> Directed Allow diagonals (loops) or set to zero? Check to allow loops (nodes linking to themselves) in the new network No loops DialogRandScaleFree Scale-free random network generator <html><head/><body><p>Generate a random scale-free network of <span style=" font-style:italic;">n</span> nodes according to the BarabΓ‘si–Albert (BA) model which uses a preferential attachment mechanism. </p><p>The model starts with <span style=" font-style:italic;">m</span><span style=" font-style:italic; vertical-align:sub;">0</span> connected nodes. In each step a new node is added, along with <span style=" font-style:italic;">m</span> edges to existing nodes. Read more in the manual.</p></body></html> <html><head/><body><p>Nodes <span style=" font-style:italic;">n</span></p></body></html> <html><head/><body><p>The amount of nodes in the resulting scale-free graph</p></body></html> <html><head/><body><p>Power of preferential attachment <span style=" font-style:italic;">p</span></p></body></html> <html><head/><body><p>The power p of preferential attachment </p><p>Leave 1 for linear preferential attachment (BA model).</p></body></html> <html><head/><body><p>Initial connected nodes <span style=" font-style:italic;">m</span><span style=" font-style:italic; vertical-align:sub;">0</span></p></body></html> <html><head/><body><p>The number m<span style=" vertical-align:sub;">0</span> of nodes in the initial connected network.</p><p>Leave 1 to start with just one node.</p></body></html> <html><head/><body><p>Edges to add in each step <span style=" font-style:italic;">m</span></p></body></html> <html><head/><body><p>The number of edges to add in each step</p></body></html> <html><head/><body><p>Zero appeal <span style=" font-style:italic;">Ξ±</span></p></body></html> <html><head/><body><p>The initial attractiveness of a node - useful for isolate nodes with d =0</p></body></html> Graph Mode Undirected Directed Allow diagonals (loops) or set to zero? Check to allow loops (nodes linking to themselves) in the new network No loops DialogRandSmallWorld Small-World network generator <html><head/><body><p>Generate random network according to the <span style=" font-style:italic;">Watts &amp; Strogatz</span> model.</p><p>This model produces graphs with small-world properties, including short average path lengths and high clustering. Read more in the manual.</p></body></html> Nodes <html><head/><body><p>The resulting graph will have N nodes and N*d/2 edges</p></body></html> <html><head/><body><p>Mean Degree <span style=" font-style:italic;">d</span></p></body></html> <html><head/><body><p>This is the mean edge degree each new node will have</p></body></html> <html><head/><body><p>Rewiring Probability <span style=" font-style:italic;">Ξ²</span></p></body></html> Graph Mode Undirected Directed Allow diagonals (loops) or set to zero? Check to allow loops (nodes linking to themselves) in the new network No loops DialogSettings Settings & Preferences General Data Exporting <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>Click the small button on the far right corner to select a folder, where all SocNetV files and reports will be saved by default. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> Save folder <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>This is the path of the folder where all SocNetV files and reports will be saved. </p><p>Click the button on the right to select a new folder. </p><p>If the folder does not exist, it wil be created.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>This is the path of the folder where all SocNetV files and reports will be saved. </p><p>Click the button on the right to select a new folder. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the folder does not exist, it wil be created.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>Click this button to select a folder, where all SocNetV files and reports will be saved by default. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default Save / Export folder </span></p><p>Click this button to select a folder, where all SocNetV files and reports will be saved by default. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the folder does not exist, it wil be created.</p></body></html> ... Reports <html><head/><body><p><span style=" font-weight:600;">Label Length</span></p><p>Change the length of labels <span style=" font-style:italic;">in reports</span>. Use the spin box to the right to change the number of digits SocNetV should write when generating reports with labels, i.e. node labels in centrality reports.</p><p>The length cannot be a negative value. The default value is 10.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> Labels length <html><head/><body><p><span style=" font-weight:600;">Label Length</span></p><p>Sets the length of labels <span style=" font-style:italic;">in reports</span>. This value describes the number of digits SocNetV should write when generating reports with labels, i.e. node labels in centrality reports.</p><p>The length cannot be a negative value. The default value is 8.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Real Number Precision</span></p><p>Change the precision of real numbers <span style=" font-style:italic;">in reports</span>. Use the spin box on the right to select a new precision namely the number of fraction digits SocNetV should write when generating reports with real numbers, i.e. centrality reports..</p><p>The precision cannot be a negative value. The default value is 6.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> Real number precision <html><head/><body><p><span style=" font-weight:600;">Real Number Precision</span></p><p>Sets the precision of real numbers <span style=" font-style:italic;">in reports</span>. This value describes the number of fraction digits SocNetV should write when generating reports with real numbers, i.e. centrality reports..</p><p>The precision cannot be a negative value. The default value is 6.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> <html><head/><body><p><span style=" font-family:'Sans Serif'; font-size:9pt; font-weight:600;">Chart Type</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">This is the chart type that is used in reports, i.e. to plot the distribution of a prominence index, such as Degree Centrality.</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">Use the select box on the right to select a chart type.</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">SocNetV supports the following chart types:</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Spline charts, which present data as a series of data points connected by lines. </span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Area charts, which present data as an area bound by two lines.</span></p><p><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Bar charts, which present data as vertical bars. </span></p></body></html> Chart type <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; font-weight:600;">Chart Type</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">Select which kind of chart type will be used in reports, i.e. to plot the </span><span style=" font-family:'DejaVu Sans'; font-size:10pt;">distribution of a </span><span style=" font-family:'DejaVu Sans'; font-size:10pt;">prominence index, such as Degree Centrality.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'DejaVu Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">SocNetV supports the following chart types:</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Spline charts, which present data as a series of data points connected by lines. </span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Area charts, which present data as an area bound by two lines.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">- Bar charts, which present data as vertical bars. </span></p></body></html> Image Exporting <html><head/><body><p><span style=" font-weight:600;">SocNetV logo on exported images</span></p><p>Enable or disable the printing of a small SocNetV logo on exported PNG and BMP network images </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Print SocNetV logo on exported Images Debugging and Progressing <html><head/><body><p><span style=" font-weight:600;">Debug Messages</span></p><p>Enable or disable debug messages to strerr. </p><p>Once you enable this and press OK, you must close and run SocNetV again from the command line / terminal, in order to see the debug messages.</p><p>Enabling has a significant cpu cost but lets you know what SocNetV is actually doing.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Debug Messages</span></p><p>Enables or disable debug messages to strerr. </p><p>Enabling has a significant cpu cost but lets you know what SocNetV is actually doing.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Print debug messages to command line <html><head/><body><p><span style=" font-weight:600;">Progress Bars</span></p><p>Enable or disable the application Progress Bars.</p><p>Progress Bars may appear during time-cost operations. </p><p>Enabling Progress Bars has a significant cpu cost but lets you know about the progress of a given operation.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Progress Bars</span></p><p>Enable or disable the application Progress Bars.</p><p>Progress Bars may appear during time-cost operations. </p><p>Enabling Progress Bars has a significant cpu cost but lets you know about the progress of a given operation.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Show progress dialog Application Style Use SocNetV Stylesheet Window options <html><head/><body><p><span style=" font-weight:600;">Control panel</span></p><p>Enable or disable the Control panel (left panel)</p><p>The Control Panel is the widget at the left of the application window, where you can find essential functions and options for Editing, Analyzing and Visualizing your network data.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Show Control panel (left panel) <html><head/><body><p><span style=" font-weight:600;">Toolbar</span></p><p>Enable or disable the application toolbar.</p><p>The toolbar is the widget right below the menu, and carries useful icons. You can disable it if you like from here...</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Show toolbar <html><head/><body><p><span style=" font-weight:600;">Statistics panel</span></p><p>Enable or disable the statistics panel (right panel)</p><p>The Statistics panel is the widget at the right of the application window, where you can see statistics about the whole network such as node and edge/arc count, density etc as well as statistics about the last clicked node (in-degree, out-degree, clustering coefficient etc).</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Show Statistics panel (right panel) <html><head/><body><p><span style=" font-weight:600;">Statusbar</span></p><p>Enable or disable the application statusbar.</p><p>The statusbar is the widget at the bottom of the window, where messages appear. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Show statusbar Nodes <html><head/><body><p><span style=" font-weight:600;">Default node settings</span></p><p>Any change to these settings will apply to all existing nodes immediately.</p><p>Once you press OK, these settings will be saved and they will be used in all future SocNetV sessions.</p></body></html> Node settings <html><head/><body><p><span style=" font-weight:600;">Node color</span></p><p>Click the colored button to select a new color for all nodes.</p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node color when creating new nodes (except when loading network files which declare specific node settings).</p></body></html> Node color <html><head/><body><p><span style=" font-weight:600;">Node color</span></p><p>Click this button to select a new node color for all nodes.</p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node color when creating new nodes (except when loading network files which declare specific node settings).</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node color</span></p><p>Click to select a new default node color.</p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node color.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node shape</span></p><p>Click on any of the following shapes to select a new node shape for all nodes. </p><p>This will apply to all existing nodes immediately.</p><p>Once you press OK, the default node shape will be saved and it will be used in all future SocNetV sessions when creating new nodes (except when loading network files which declare specific shapes for nodes).</p></body></html> Node shape <html><head/><body><p>Select a node shape. </p><p>If you select &quot;Icon&quot; then you must click on the custom icon button (below) to select the desired icon file from your filesystem. </p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click the button on the right to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> Custom Icon <html><head/><body><p><span style=" font-weight:600;">Custom Icon </span></p><p>This box only shows the path of the selected custom icon file for all network nodes. If it is empty, it means you have not selected any image yet.</p><p>Click the button on the right to select a new image to be used as custom icon in all the nodes of the network.</p><p>Valid icons are images: JPG, PNG, JPEG, SVG etc.</p><p><br/></p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>This is the path of the default background image. </p><p>Click the button on the right to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Custom Icon </span></p><p>Click this button to select a new custom icon for all nodes of the network. </p><p>Valid icons are images: JPG, PNG, JPEG, SVG etc.</p><p>If you don't select an image file, you must select one of the default shapes from the Node Shape menu above. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click this button to select a new background image.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. If the file does not exist, the default background color will be used.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node size</span></p><p>Use the spin box to select a new size (in pixels) for all nodes. </p><p>Actual node size will vary according to selected shape. For instance, if the selected shape is circle and the selected size is 8, then the circle will have a diameter of 16 pixels. </p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node size when creating new nodes (except when loading network files which declare specific node settings).</p></body></html> Node size <html><head/><body><p><span style=" font-weight:600;">Node size</span></p><p>Select a new size for all nodes. Actual node size will vary according to selected shape. For instance, if the selected shape is circle and the size is 8, then the circle will have a diameter of 16 pixels. </p><p>Any change to this setting will apply to all existing nodes immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node size.</p></body></html> Node Number settings <html><head/><body><p><span style=" font-weight:600;">Node number color</span></p><p>Change the color for all node numbers. Click the colored button on the right corner to select a new color.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number color when creating new nodes.</p></body></html> Number color <html><head/><body><p><span style=" font-weight:600;">Node number color</span></p><p>Click to select a new color for all node numbers.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number color when creating new nodes.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Number distance from node</span></p><p>Change the distance of each number from the respective node. Use the spin box on the right to select a new distance (in pixels).</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default distance from node.</p></body></html> Number distance <html><head/><body><p><span style=" font-weight:600;">Number distance from nodes</span></p><p>Select a new distance (in pixels) of numbers from the respective nodes.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default distance from node.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node numbers</span></p><p>Enable or disable displaying node numbers.</p><p>Any change will apply to all existing nodes immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> Display node numbers <html><head/><body><p><span style=" font-weight:600;">Node number font size</span></p><p>Change the font size (in pixels) of all node numbers. Use the spin box on the right to select a new font size (in pixels).</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> Number font size <html><head/><body><p><span style=" font-weight:600;">Node number size</span></p><p>Select a new font size (in pixels) for node numbers. Set it to 0 to let the program automagically select a different font size according to the node size.</p><p>Any change to this setting will apply to all existing node numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node number size.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Node numbers inside shapes</span></p><p>Enable or disable displaying node numbers inside the node shapes</p><p>Any change will apply to all existing nodes immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> Display node numbers inside node shapes Node Label settings <html><head/><body><p><span style=" font-weight:600;">Node labels</span></p><p>Enable or disable displaying labels below the nodes.</p><p>Any change will apply to all existing nodes immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> Display node labels Label color <html><head/><body><p><span style=" font-weight:600;">Node label color</span></p><p>Click to select a new color for node labels.</p><p>Any change to this setting will apply to all existing node labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node label color.</p></body></html> Label font size <html><head/><body><p><span style=" font-weight:600;">Node label size</span></p><p>Select a new font size (in pixels) for all node labels.</p><p>Any change to this setting will apply to all existing node labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default node label size.</p></body></html> Label distance <html><head/><body><p><span style=" font-weight:600;">Label distance from node</span></p><p>Change the distance of labels from the respective nodes.</p><p>Any change to this setting will apply to all existing node labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default label distance from node.</p></body></html> Edges Edge settings <html><head/><body><p><span style=" font-weight:600;">Edges </span></p><p>Enable or disable displaying edges.</p><p>Any change will apply to all existing edges immediately.</p><p><br/></p></body></html> Display edges <html><head/><body><p><span style=" font-weight:600;">Edges </span></p><p>Enable or disable displaying arrows in directed edges. Useful If you work with directional social network data. You can disable it if your network is undirected.</p><p>Any change will apply to all existing edges immediately and saved for all future sessions (until you change it again).</p><p><br/></p></body></html> Display edge arrows <html><head/><body><p><span style=" font-weight:600;">Default edge shape</span></p><p>Select the default edge shape for all edges. </p><p>Edges can be either straight lines (default) or bezier curves.</p><p>This will apply to all existing edgesimmediately.</p><p>Once you press OK, the default edge shape will be saved and it will be used in all future SocNetV sessions.</p></body></html> Edge shape Straight Lines Be&zier Curves <html><head/><body><p><span style=" font-weight:600;">Edge color</span></p><p>Click the colored button on the right to select a new color for all edges.</p><p>Any change to this setting will apply to all existing edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color. Until you change it again.</p></body></html> Default edge color <html><head/><body><p><span style=" font-weight:600;">Edge color</span></p><p>Click to select a new color for all edges.</p><p>Any change to this setting will apply to all existing edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color. Until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Negative edge color</span></p><p>Click the colored button at the right corner to select a new color for negative weighted edges.</p><p>Any change to this setting will apply to all existing &quot;negative&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;negative&quot; edges. Until you change it again.</p></body></html> Negative valued edge color <html><head/><body><p><span style=" font-weight:600;">Negative edge color</span></p><p>Click this button to select a new default edge color for all negative weighted edges.</p><p>Any change to this setting will apply to all existing &quot;negative&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default color for &quot;negative&quot; edges. Until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Zero edge color</span></p><p>Click the colored button at the right corner to select a new color for edges with zero weights.</p><p>ATTTENTION: Zero-valued edges are being omittted during network analysis computations. This setting is available for those that have data (i.e. weighted edge lists) with zero weights on some edges and they still need to draw those edges, despite the fact that they will not be considered in any SNA computation by SocNetV.</p><p>Any change to this setting will apply to all existing &quot;zero&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;zero&quot; edges. Until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Zero edge color</span></p><p>Click the colored button at the right corner to select a new color for edges with zero weights.</p><p>ATTTENTION: Zero-valued edges are being omittted during network analysis computations. This setting is available for those that have data (i.e. weighted edge lists) with zero weights on some edges and they still need to draw those edges, despite the fact that they will not be considered in any SNA computation by SocNetV.</p><p>Any change to this setting will apply to all existing &quot;zero&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;zero&quot; edges. Until you change it again.</p></body></html> Zero valued edge color <html><head/><body><p><span style=" font-weight:600;">Zero edge color</span></p><p>Click this button to select a new color for edges with zero weights.</p><p>ATTTENTION: Zero-valued edges are being omittted during network analysis computations. This setting is available for those that have data (i.e. weighted edge lists) with zero weights on some edges and they still need to draw those edges, despite the fact that they will not be considered in any SNA computation by SocNetV.</p><p>Any change to this setting will apply to all existing &quot;zero&quot; edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge color for &quot;zero&quot; edges. Until you change it again.</p></body></html> Edge offset from node <html><head/><body><p><span style=" font-weight:600;">Edge offset from node </span></p><p>Changes the offset of each edge from each source and target nodes. rs.</p><p>Any change to this setting will apply to all existing edges immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions when creating new edges.</p></body></html> Weight number settings <html><head/><body><p><span style=" font-weight:600;">Edge weight numbers</span></p><p>Enable or disable edge weight numbers. When enabled, a number will be displayed along every edge indicating the edge weight.</p><p>Any change to this setting will apply to all existing weight numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default when creating new edges.</p></body></html> Display edge weight numbers Weight number color <html><head/><body><p><span style=" font-weight:600;">Edge weight number color</span></p><p>Click to select a new color for all edge weight numbers.</p><p>Any change to this setting will apply to all existing weight numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge weight number color when creating new edges.</p></body></html> Weight number font size <html><head/><body><p><span style=" font-weight:600;">Edge weight number text size</span></p><p>Click to select a new text size (in pixels) for all edge weight numbers.</p><p>Any change to this setting will apply to all existing weight numbers immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge weight number size when creating new edges.</p></body></html> Edge label settings <html><head/><body><p><span style=" font-weight:600;">Edge labels</span></p><p>Enable or disable displaying edge labels.</p><p>Any change will apply to all existing edges immediately.</p><p>Once you press OK, this setting will be saved and it will be used in all future SocNetV sessions.</p></body></html> Display edge labels <html><head/><body><p><span style=" font-weight:600;">Default edge label color</span></p><p>Click to select a new default color for edge labels.</p><p>Any change to this setting will apply to all existing edge labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge label color.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Default edge label size</span></p><p>Select the default font size for all edge labels.</p><p>Any change to this setting will apply to all existing edge labels immediately.</p><p>This is a permanent setting. Once you press OK, it will be saved and used in all future SocNetV sessions as default edge label size.</p></body></html> Canvas <html><head/><body><p><span style=" font-weight:600;">Canvas Background</span></p><p>In this section, there are general settings for the canvas, such as the background color or image.</p><p><br/></p><p><br/></p><p><br/></p></body></html> Canvas Background <html><head/><body><p><span style=" font-weight:600;">Background color</span></p><p>Click the button on the right to select a new background color. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> Background color <html><head/><body><p><span style=" font-weight:600;">Background color</span></p><p>Click this button to select a new background color. </p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background color</span></p><p>Click this button to select a new background color. </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p></body></html> Background Image <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>This is the path of the default background image. </p><p>Click the button on the right to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Background image</span></p><p>Click this button to select a new background image.</p><p>If the file does not exist, the default background color will be used.</p><p>This is a permanent setting. Once you press OK, it will be saved and will be the default of the application every time you run SocNetV - until you change it again.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Performance Settings</span></p><p>The following options influence the performance of the SocNetV canvas, inside which nodes and edges are being drawn. They affect the look of nodes and edges as well the precision and speed of the graphics engine.</p><p>Most of these settings have noticeable effects only in large networks. For instance, if you have a network with over 3000 actors and 10000 edges you might want to disable Antialiasing, Antialiasing Auto-adjustment and Smooth Pixmap Transformation while enabling Cache Background. </p><p>Also, if you need to visually interact with edges in a large network, you can experiment with the Update Mode setting to find which is suitable for your workload. The default setting &quot;Full&quot; means that the canvas is fully redrawn every time you make a small change.</p></body></html> Hardware Acceleration <html><head/><body><p><span style=" font-weight:600;">Edge highlighting</span></p><p>Enable or disable highlighting of selected edges.</p><p>By default, SocNetV hughlights the edges you select with the mouse, as well as all edges connected to the selected node. Although a useful feature, this can slow down the application responsiveness when the network consists of thousand nodes and edges. </p><p>If disabled, selected edges will not be highlighted.</p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Cache Background</span></p><p>Enable or disable caching of the canvas background. </p><p>The graphics engine can cache pre-rendered content in a pixmap, which is then drawn onto the viewport. The purpose of such caching is to speed up the total rendering time for areas that are slow to render (i.e. texture, gradient and alpha blended backgrounds). </p><p>If enabled, the canvas background is cached by allocating one pixmap with the full size of the viewport. The cache is invalidated every time the view is transformed. However, when scrolling, only partial invalidation is required. </p><p>If not enabled, nothing is cached and all painting is done directly onto the viewport.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. <br/></p></body></html> Use OpenGL Performance settings <html><head/><body><p><span style=" font-weight:600;">Anti-Aliasing</span></p><p>Enable or disable Anti-Aliasing. If enabled, the graphics engine will antialias edges of primitives if possible. </p><p>Anti-aliasing is a technique which makes nodes, lines and text, smoother and fancier. But it comes at the cost of speed... </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Anti-Aliasing</span></p><p>Enable or disable Anti-Aliasing.</p><p>Anti-aliasing is a technique which makes nodes, lines and text, smoother and fancier. But it comes at the cost of speed... </p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> Antialiasing <html><head/><body><p><span style=" font-weight:600;">Antialiasing Auto-Adjustment</span></p><p>Enable or disable antialiasing auto-adjustment of exposed areas. Items that render antialiased lines on the boundaries of their bounding rectangle can end up rendering parts of the line outside. To prevent rendering artifacts, the graphics engine expands all exposed regions by 2 pixels in all directions. </p><p>If you disable this, SocNetV will no longer perform these adjustments, minimizing the areas that require redrawing, which improves performance. A common side effect is that items that do draw with antialiasing can leave painting traces behind on the scene as they are moved.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> Antialising Auto-Adjustment <html><head/><body><p><span style=" font-weight:600;">Smooth pixmap transformations</span></p><p>Enable or disable smooth pixmap transformations. </p><p>If enabled, the engine will use a smooth pixmap transformation algorithm (such as bilinear) rather than nearest neighbor.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> Smooth Pixmap Transformation <html><head/><body><p><span style=" font-weight:600;">Save Painter State</span></p><p>When rendering, the graphics engine can protect (&quot;save&quot;) the painter state when rendering the background or foreground, and when rendering each item. This allows us to leave the painter in an altered state. </p><p>However, if the items consistently do restore the state, you should disable this option to prevent the application from doing the same.</p><p>This is a permanent setting, it will be the default of the application every time you run SocNetV. </p><p><br/></p></body></html> Save Painter State Edge Highlighting Cache Background <html><head/><body><p><span style=" font-family:'Sans Serif'; font-size:9pt; font-weight:600;">Update Mode</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">Use the drop-down menu on the right to select the update mode when the canvas changes. Usually you do not need to modify this property, but there are some cases where doing so can improve rendering performance. </span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">The default value is Full, where the graphics engine will update the entire canvas when some of its contents change.</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Full</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">When any visible part of the canvas changes or is reexposed, the engine will update the entire canvas. This approach is fastest when the engine spends more time figuring out what to draw than it would spend drawing (e.g., when very many small items are repeatedly updated). This is the default setting as it is the fastest with network of thousands of edges.</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Minimal</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">The engine will determine the minimal region that requires a redraw, minimizing the time spent drawing by avoiding a redraw of areas that have not changed. Although this approach provides the best performance in general, if there are many small visible changes on the scene, the engine might end up spending more time finding the minimal approach than it will spend drawing.</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Bounding Rectangle </span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">The bounding rectangle of all changes in the canvas will be redrawn. This mode has the advantage that the engine searches only one region for changes, minimizing time spent determining what needs redrawing. The disadvantage is that areas that have not changed also need to be redrawn.</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">None</span></p><p><span style=" font-family:'Sans Serif'; font-size:9pt;">The engine will never update the canvas when something changes; This mode disables all item changes. Normally, you don't want to use this!</span></p></body></html> Update mode <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; font-weight:600;">Update Mode</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">Select how to update areas of the canvas that have been reexposed or changed. Usually you do not need to modify this property, but there are some cases where doing so can improve rendering performance. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">The default value is Full, where the graphics engine will update the entire canvas when some of its contents change.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Full</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt; font-weight:600;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">When any visible part of the canvas changes or is reexposed, the engine will update the entire canvas. This approach is fastest when the engine spends more time figuring out what to draw than it would spend drawing (e.g., when very many small items are repeatedly updated). This is the default setting as it is the fastest with network of thousands of edges.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Minimal</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">The engine will determine the minimal region that requires a redraw, minimizing the time spent drawing by avoiding a redraw of areas that have not changed. Although this approach provides the best performance in general, if there are many small visible changes on the scene, the engine might end up spending more time finding the minimal approach than it will spend drawing.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">Bounding Rectangle </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">The bounding rectangle of all changes in the canvas will be redrawn. This mode has the advantage that the engine searches only one region for changes, minimizing time spent determining what needs redrawing. The disadvantage is that areas that have not changed also need to be redrawn.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline;">None</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:9pt;">The engine will never update the canvas when something changes; This mode disables all item changes. Normally, you don't want to use this!</span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Index Method</span></p><p>Use the drop-down menu on the right to select the indexing algorithm of the graphics engine for managing positional information about items on the canvas.</p><p><span style=" font-weight:600; font-style:italic;">BspTreeIndex</span><span style=" font-style:italic;">: </span>A Binary Space Partitioning tree is applied. All item location algorithms are of an order close to logarithmic complexity, by making use of binary search. Adding, moving and removing items is logarithmic. This approach is best for static scenes (i.e., scenes where most items do not move).</p><p><span style=" font-weight:600; font-style:italic;">NoIndex</span><span style=" font-style:italic;">: </span>No index is applied. Item location is of linear complexity, as all items on the scene are searched. Adding, moving and removing items, however, is done in constant time. This approach is ideal for dynamic scenes, where many items are added, moved or removed continuously.</p></body></html> Index Method <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:600;">Index Method</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'DejaVu Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;">Select one of the indexing algorithms the graphics engine provides for managing positional information about items on the canvas.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'DejaVu Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:600; font-style:italic;">BspTreeIndex</span><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-style:italic;">: </span><span style=" font-family:'DejaVu Sans'; font-size:10pt;">A Binary Space Partitioning tree is applied. All item location algorithms are of an order close to logarithmic complexity, by making use of binary search. Adding, moving and removing items is logarithmic. This approach is best for static scenes (i.e., scenes where most items do not move).</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt;"> </span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:600; font-style:italic;">NoIndex</span><span style=" font-family:'DejaVu Sans'; font-size:10pt; font-style:italic;">: </span><span style=" font-family:'DejaVu Sans'; font-size:10pt;">No index is applied. Item location is of linear complexity, as all items on the scene are searched. Adding, moving and removing items, however, is done in constant time. This approach is ideal for dynamic scenes, where many items are added, moved or removed continuously.</span></p></body></html> Options Saving options Save zero-weight edges (GraphML only) Select a new data dir Select a background color Select a background image All (*);;PNG (*.png);;JPG (*.jpg) Select a color for Nodes Select a new icon Images (*.png *.jpg *.jpeg *.svg);;All (*.*) Select color for Node Numbers Select color for Node Labels Select color for Edges Select color for negative Edges DialogSimilarityMatches Similarity: Matches Variables in: <html><head/><body><p><span style=" font-weight:600;">Rows: </span>Correlate outbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Columns: </span>Correlate inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Both: </span>Correlate both outbound and inbound ties (or distances, depending on the selected matrix above) between pairs of actors. In this case, the input matrix is expanded by listing row vectors followed by column vectors. This is useful only when you have directed data.</p><p><br/></p></body></html> Enable to include matrix diagonal in calculations Include input matrix diagonal Matching measure: <html><head/><body><p>Select a matching method. </p><p><span style=" font-weight:600;">Exact Matches</span>: Examines pairs of actors for exact tie or distance matches (present or absent) to other actors and returns a proportion to their overall ties. </p><p><span style=" font-weight:600;">Positive Matches (Jaccard/Co-citation)</span>: Looks for same ties/distances reported by both actors. Returns the ratio to the total number of ties reported.</p><p><span style=" font-weight:600;">Hamming distance</span>: Reports the number of ties/distances to other actors which differ between each pair of actors.</p><p><span style=" font-weight:600;">Cosine similarity</span>: Computes the pair-wise similarity of actors as the dot product of their tie/distance vectors divided by the magnitude of these vectors. <br/></p></body></html> <html><head/><body><p>Compute a <span style=" font-weight:600;">similarity matrix</span>, where each element (i,j) is the pair-wise similarity score of actors i and j according to the selected &quot;matching&quot; method. For example, the &quot;Simple Matching&quot; method counts the number of times that actors i and j have the same tie / distance (present or absent) to other actors. </p><p>Select input matrix and where the &quot;variables&quot; are. For instance, select Adjacency matrix and Rows to correlate the outbound ties between all pairs of actors. Select Both to correlate the both inbound and outbound ties. Hover over &quot;Matching measure&quot; select box for more info on each method.</p></body></html> Input matrix: <html><head/><body><p><span style=" font-weight:600;">Adjacency:</span> Use the active network's adjacency matrix as input to correlate ties between all pairs of actors. </p><p><span style=" font-weight:600;">Distances:</span> Use the active network's geodesic distances matrix to correlate distances between all pairs of actors.</p></body></html> DialogSimilarityPearson Pearson Correlations Input matrix: <html><head/><body><p><span style=" font-weight:600;">Adjacency:</span> Use the active network's adjacency matrix as input to correlate ties between all pairs of actors. </p><p><span style=" font-weight:600;">Distances:</span> Use the active network's geodesic distances matrix to correlate distances between all pairs of actors.</p></body></html> Variables in: <html><head/><body><p><span style=" font-weight:600;">Rows: </span>Correlate outbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Columns: </span>Correlate inbound ties (or distances, depending on the selected matrix above) between pairs of actors.</p><p><span style=" font-weight:600;">Both: </span>Correlate both outbound and inbound ties (or distances, depending on the selected matrix above) between pairs of actors. In this case, the input matrix is expanded by listing row vectors followed by column vectors. This is useful only when you have directed data.</p><p><br/></p></body></html> <html><head/><body><p>Compute <span style=" font-weight:600;">Pearson Product Moment Correlation Coefficients</span> (PPMCC) between the rows, the columns or both of a social network matrix (adjacency or distance matrix). The result is a <span style=" font-weight:600;">correlation matrix </span>containing the correlation coefficients between each variable (i.e. row) and the others. This might be useful if you want to check the pair-wise similarity of the actors, in terms of their ties. </p><p>Select input matrix and what &quot;variables&quot; to correlate. For instance, select Adjacency matrix and Rows to correlate the outbound ties between all pairs of actors. Select Both to correlate the both inbound and outbound ties.</p></body></html> Enable to include matrix diagonal in calculations Include input matrix diagonal DialogSystemInfo System Information <html><head/><body><p>Use the following to provide more meaningful information in your bug reports:</p></body></html> SocNetV has OpenGL support, but you have disabled it. <br>Please enable OpenGL from Settings -> Canvas to enjoy faster drawing on the canvas.<br> DialogWebCrawler Generate network from web links URL patterns to include <html><head/><body><p><span style=" font-weight:600;">Allowed URL Patterns</span></p><p>Enter, in separate lines, one or more URL patterns to <span style=" font-weight:600;">include</span> while crawling. For example:</p><p>example.com/pattern/*</p><p>Do not enter spaces. Leave * to crawl all urls.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Crawl Internal Links </span></p><p>If enabled, the crawler will include and map <span style=" font-weight:600;">internal links, </span> that is pages from the same domain (or, in other words, from the same host ) as the URL being parsed each time.</p><p>If you do not want to crawl internal links, disable this option.</p><p> Please note that you MUST enable either this option or the &quot;Include external links&quot; option, for the crawler to work.</p><p>Default is to crawl internal links only. </p><p>You can further refine what kind of internal links to follow with the two options below: Child links and Parent links. </p></body></html> Crawl internal links <html><head/><body><p><span style=" font-weight:600;">Crawl child links</span></p><p>If enabled, the crawler will map <span style=" font-weight:600;">child URLs</span></p><p>A URL is a <span style=" font-style:italic;">childUrl </span>of another URL if the two URLs share the same scheme and authority, and the latter URL's path is a parent of the path of <span style=" font-style:italic;">childUrl</span>. This applies only to internal URLs.</p><p>For instance, www.socnetv.org/docs/manual.html is a child URL of www.socnetv.org/docs/ </p><p>If you don't want to crawl child URLs, disable this option. </p></body></html> Child links <html><head/><body><p><span style=" font-weight:600;">Crawl parent links</span></p><p>If enabled, the crawler will map <span style=" font-weight:600;">parent URLs</span>. </p><p>A URL is a <span style=" font-style:italic;">parent </span>of another URL if the two URLs share the same scheme and authority, and the former URL's path is a parent of the path of the latter URL. This applies to internal URLs.</p><p>For instance, the URL www.socnetv.org/docs/ is a parent URL of www.socnetv.org/docs/manual.html</p><p>If you don't want to crawl parent links, disable this option. </p></body></html> Parent links URL patterns to exclude <html><head/><body><p><span style=" font-weight:600;">Excluded URL Patterns</span></p><p>Enter, in separate lines, one or more URL patterns to <span style=" font-weight:600;">exclude</span> while crawling. For example:</p><p>example.com/pattern/*</p><p>Do not enter spaces. Leave blank to crawl all urls.</p></body></html> <html><head/><body><p>Set the max links inside a page to be followed and crawled by SocNetV.</p><p>Set this to zero if you don't want to have this limit. In this case SocNetV will follow and crawl every link found in a page.</p></body></html> Max links in each page to follow <html><head/><body><p><span style=" font-weight:600;">Links to social media domains</span></p><p>If enabled, the crawler will map (and possibly crawl) <span style=" font-weight:600;">links to social media websites</span>, such as twitter.com.</p><p>If disabled, the crawler will diregard any link to URLs in the following domains:</p><p>facebook.com<br/>twitter.com<br/>linkedin.com<br/>instagram.com<br/>pinterest.com<br/>telegram.org<br/>telegram.me<br/>youtube.com<br/>reddit.com<br/>plus.google.com</p><p><span style=" font-weight:600;">Note</span>: You can exclude more social media or define your custom social media exclusion list by typing domains in the &quot;URL patterns to exclude&quot; text edit above.</p></body></html> Links to social media <html><head/><body><p>If enabled the application will draw a <span style=" font-weight:600;">self-link</span> when a page contains a link to itself. </p><p>Default is not to allow self-links.</p></body></html> Allow Self-Links <html><head/><body><p><span style=" font-weight:600;">Show external links</span></p><p>If enabled, the crawler will map <span style=" font-weight:600;">links to external domains</span>. </p><p>For instance, if this option is enabled and you start crawling www.supersyntages.gr where there is a link to a page of a different domain, i.e. www.aggelies247.com/news, then a node &quot;www.aggelies247.com/news&quot; will be added to the network. </p><p>If you don't want to show external links at all, just disable this option. </p><p>Please note that you <span style=" font-weight:600;">MUST </span>enable either this option or the &quot;Include internal links&quot; option, for the crawler to work.</p></body></html> Show external links <html><head/><body><p><span style=" font-weight:600;">Crawl external links</span></p><p>If enabled, the crawler will <span style=" font-weight:600;">map external links AND crawl them for new links as well.</span></p><p>For instance, if you enable this option and start crawling the page at https://www.supersyntages.gr where there is a link to another domain, i.e. www.linuxinsider.gr, then the crawler will visit linuxinsider.gr too to find more links. </p><p>If you don't want to crawl external links, disable this option. </p></body></html> Crawl external links <html><head/><body><p>Wait for a random number of milliseconds (<span style=" font-weight:600;">0-1000</span>) between network requests. </p><p>Use of this option is recommended, as it lightens the server load by making the requests less frequent.</p><p>By default this option is enabled.</p></body></html> Delay between requests <html><head/><body><p>Use the built-in web crawler to scan the HTML code of a given initial URL (i.e. a website) and map all internal or external links to other pages found there. </p><p>As new URLs are discovered, the crawler follows them to scan their HTML code for links as well. For more details, see the Manual. </p><p>Enter the initial URL below and change crawling parameters if you like.</p></body></html> Initial URL <html><head/><body><p>Enter the initial url/domain to start crawling from, i.e. https://socnetv.org</p><p>You may omit https:// if you want. </p></body></html> <html><head/><body><p>Set the total urls to be crawled. </p><p>This is the total nodes the result network will have. </p><p>Set value to 0, if you don't want any limits...</p></body></html> Max URLs to crawl <html><head/><body><p>Set the total URLs to be crawled. </p><p>This is the <span style=" font-weight:600;">maximum nodes</span> the result network will have. </p><p>Set value to 0, if you don't want any limits...</p></body></html> EdgeEditDialog Node Properties Enter node label Enter node value (disabled) Select line shape Select link color (click the button) ... Select the link weight (default 1) Graph Computing Information Centralities. Please wait... Computing inverse adjacency matrix. Please wait... Computing IC scores. Please wait... Calculating EVC scores... Computing Eigenvector Centrality scores. Please wait... Computing outDegrees. Please wait... Leading eigenvector computed. Analysing centralities. Please wait... Computing out-Degree Centralities for %1 nodes. Please wait... Computing Influence Range Centrality scores. Please wait Computing Degree Prestige (in-Degree). Please wait ... Computing Proximity Prestige scores. Please wait ... Computing PageRank Prestige scores. Please wait ... Computing Clustering Coefficient. Please wait... Computing Hierarchical Clustering. Please wait... Computing Triad Census. Please wait... Finding cliques: Recursive backtracking for actor Checking if the graph edges are valued. Please wait... Calculating the Arc Reciprocity of the graph... web Creating shortest paths matrix. Please wait Creating geodesic distances matrix. Please wait Edges with weight %1 %2 have been filtered. Unilateral edges have been temporarily disabled. Please compute the selected centrality/prestige index first, then apply the filter. Filter applied: vertices with score %1 %2 are now hidden. Creating Erdos-Renyi Random Network. Please wait... erdos-renyi Creating Scale-Free Random Network. Please wait... scale-free Creating Small-World Random Network. Please wait ... small-world Creating pseudo-random d-regular network. Please wait... d-regular Creating ring-lattice network. Please wait... ring-lattice Creating lattice network. Please wait... lattice Error. Could not write to File %1 saved Adjacency matrix-formatted network saved into file %1 Embedding Random Layout. Please wait... Embedding Random Radial layout. Please wait .... Applying circular layout. Please wait... Computing centrality/prestige scores. Please wait... Embedding Radial layout by Prominence Score. Please wait... Embedding Level layout by Prominence Score. Please wait... Embedding Node Size by Prominence Score layout. Please wait... Embedding Node Color by Prominence Score layout. Please wait... Embedding Eades Spring-Gravitational model. Please wait .... Embedding Fruchterman & Reingold forces model. Please wait ... Embedding Kamada & Kawai spring model. Please wait... Creating Adjacency Matrix. Please wait... Computing Centrality Distribution. Please wait... Creating prominence index distribution line chart... Creating prominence index distribution area chart... Creating prominence index distribution bar chart... Creating reachability matrix. Please wait Computing walks of length %1. Please wait... Computing sociomatrix powers up to %1. Please wait... Computing all sociomatrix powers up to %1. Now computing A^%2. Please wait... Creating Influence Range List. Please wait Creating Influence Domain List. Please wait Added a new relation named: %1. Writing Reciprocity to file. Please wait... RECIPROCITY (r) REPORT Network name: Actors: Reciprocity, <b>r</b>, is a measure of the likelihood of vertices in a directed network to be mutually linked. <br />SocNetV supports two different methods to index the degree of reciprocity in a social network: <br />- The arc reciprocity, which is the fraction of reciprocated ties over all actual ties in the network. <br />- The dyad reciprocity which is the fraction of actor pairs that have reciprocated ties over all pairs of actors that have any connection. <br />In a directed network, the arc reciprocity measures the proportion of directed edges that are bidirectional. If the reciprocity is 1, then the adjacency matrix is structurally symmetric. <br />Likewise, in a directed network, the dyad reciprocity measures the proportion of connected actor dyads that have bidirectional ties between them. <br />In an undirected graph, all edges are reciprocal. Thus the reciprocity of the graph is always 1. <br />Reciprocity can be computed on undirected, directed, and weighted graphs. r range: 0 &le; r &le; 1 Arc reciprocity: %1 / %2 = %3 Of all actual ties in the network, %1% are reciprocated. Dyad reciprocity: Of all pairs of actors that have any ties, %1% have a reciprocated connection. Reciprocity proportions per actor: Actor Label Symmetric nonSymmetric nsym out/nsym nsym in/nsym nsym out/out nsym in/in Symmetric Proportion of reciprocated ties involving the actor to the total incoming and outgoing ties. nonSymmetric One minus symmetric nonSym Out/NonSym Proportion of non-symmetric outgoing ties to the total non-symmetric ties. nonSym In/NonSym Proportion of non-symmetric incoming ties to the total non-symmetric ties. nonSym Out/Out Proportion of non-symmetric outgoing ties to the total outgoing ties. nonSym In/In Proportion of non-symmetric incoming ties to the total incoming ties Reciprocity Report, <br /> Created by <a href="https://socnetv.org" target="_blank">Social Network Visualizer</a> v%1: %2 Computation time: %1 msecs Computation canceled. Writing Eccentricity scores to file. Please wait... ECCENTRICITY (e) REPORT The eccentricity <em>e</em> measures how far, at most, is each node from every other node. <br />In a connected graph, the eccentricity <em>e</em> of a vertex is the maximum geodesic distance between that vertex and all other vertices. <br />In a disconnected graph, the eccentricity <em>e</em> of all vertices is considered to be infinite. e range: 1 &le; e &le; ∞ e All nodes have the same eccentricity. Max e (Graph Diameter) = Min e (Graph Radius) = e classes = e = 1 when the node is connected to all others (star node). e > 1 when the node is not directly connected to all others. Larger eccentricity means the actor is farther from others. e = ∞ there is no path from that node to one or more other nodes. Eccentricity Report, <br /> Writing Information Centralities to file. Please wait... INFORMATION CENTRALITY (IC) The IC index, introduced by Stephenson and Zelen (1991), measures the information flow through all paths between actors weighted by strength of tie and distance. IC' is the standardized index (IC divided by the sumIC). Warning: To compute this index, SocNetV drops all isolated nodes and symmetrizes (if needed) the adjacency matrix. <br />Read the Manual for more. IC range: 0 &le; IC &le; ∞ IC' range: 0 &le; IC' &le; 1 Node IC IC' %IC All nodes have the same IC score. Max IC' = Min IC' = IC classes = IC' Sum = IC' Mean = IC' Variance = IC' DISTRIBUTION GROUP INFORMATION CENTRALIZATION (GIC) Since there is no way to compute Group Information Centralization, <br />you can use Variance as a general centralization index. <br /><br /> Variance = Variance = 0, when all nodes have the same IC value, i.e. a complete or a circle graph). <br /> Larger values of variance suggest larger variability between the IC' values. <br /> Information Centrality report, <br /> Writing Eigenvector Centrality scores to file. Please wait... EIGENVECTOR CENTRALITY (EVC) The Eigenvector Centrality of each node is the i<sub>th</sub> element of the leading eigenvector of the adjacency matrix, that is the eigenvector corresponding to the largest positive eigenvalue. <br />Proposed by Bonacich (1972), the Eigenvector Centrality is an extension of the simpler Degree Centrality because it gives each actor a score proportional to the scores of its neighbors. Thus, a node may have high EVC score if it has lots of ties or it has ties to other nodes with high EVC. <br />The eigenvector centralities are also known as Gould indices. EVC' is the scaled EVC (EVC divided by max EVC). EVC'' is the standardized index (EVC divided by the sum of all EVCs). EVC range: 0 &le; EVC &lt; 1 (The eigenvector has unit euclidean length) EVC' range: 0 &le; EVC' &le; 1 EVC EVC' EVC'' %EVC' All nodes have the same EVC score. Max EVC = Min EVC = EVC classes = EVC Sum = EVC Mean = EVC Variance = EVC' DISTRIBUTION GROUP EIGENVECTOR CENTRALIZATION (GEC) Since there is no way to compute Group Eigenvector Centralization, <br />you can use Variance as a general centralization index. <br /><br /> Variance = 0, when all nodes have the same EVC value, i.e. a complete or a circle graph). <br /> Larger values of variance suggest larger variability between the EVC' values. <br /> Eigenvector Centrality report, <br /> Writing out-Degree Centralities. Please wait... DEGREE CENTRALITY (DC) REPORT In undirected networks, the DC index is the sum of edges attached to a node u. <br />In directed networks, the index is the sum of outbound arcs from node u to all adjacent nodes (also called "outDegree Centrality"). <br />If the network is weighted, the DC score is the sum of weights of outbound edges from node u to all adjacent nodes.<br />Note: To compute inDegree Centrality, use the Degree Prestige measure. DC' is the standardized index (DC divided by N-1 (non-valued nets) or by sumDC (valued nets). DC range: 0 &le; DC &le; DC' range: 0 &le; DC' &le; 1 DC DC' %DC' All nodes have the same DC score. DC Sum = Max DC' = Min DC' = DC' classes = DC' Sum = DC' Mean = DC' Variance = DC' DISTRIBUTION GROUP DEGREE CENTRALIZATION (GDC) GDC = GDC range: GDC = 0, when all out-degrees are equal (i.e. regular lattice). GDC = 1, when one node completely dominates or overshadows the other nodes. Because this graph is weighted, we cannot compute Group Centralization You can use variance as a group-level centralization measure. Degree Centrality report, <br /> Writing Closeness Centrality scores to file. Please wait ... CLOSENESS CENTRALITY (CC) REPORT The CC index is the inverted sum of geodesic distances from each node u to all other nodes. Note: The CC index considers outbound arcs only and isolate nodes are dropped by default. Read the Manual for more. CC' is the standardized index (CC multiplied by (N-1 minus isolates)). CC range: 0 &le; CC &le; ( 1 / Number of node pairs excluding u) CC' range: 0 &le; CC' &le; 1 (CC'=1 when a node is the center of a star graph) CC CC' %CC' All nodes have the same CC score. CC Sum = Max CC' = Min CC' = CC' classes = CC' Sum = CC' Mean = CC' Variance = CC' DISTRIBUTION GROUP CLOSENESS CENTRALIZATION (GCC) GCC = GCC range: GCC = 0, when the lengths of the geodesics are all equal, i.e. a complete or a circle graph. GCC = 1, when one node has geodesics of length 1 to all the other nodes, and the other nodes have geodesics of length 2. to the remaining (N-2) nodes. This is exactly the situation realised by a star graph. Closeness Centrality report, <br /> Writing Influence Range Centrality scores. Please wait INFLUENCE RANGE CLOSENESS CENTRALITY (IRCC) The IRCC index of a node u is the ratio of the fraction of nodes reachable by node u to the average distance of these nodes from u (Wasserman & Faust, formula 5.22, p. 201)<br />Thus, this measure is similar to Closeness Centrality but it counts only outbound distances from each actor to other reachable nodes. <br />This measure is useful for directed networks which are not strongly connected (thus the ordinary CC index cannot be computed).<br />In undirected networks, the IRCC has the same properties and yields the same results as the ordinary Closeness Centrality.<br />Read the Manual for more. IRCC is standardized. IRCC range: 0 &le; IRCC &le; 1 (IRCC is a ratio) IRCC %IRCC' All nodes have the same IRCC score. Max IRCC = Min IRCC = IRCC classes = IRCC Sum = IRCC Mean = IRCC Variance = IRCC DISTRIBUTION Influence Range Closeness Centrality report, <br /> Writing Betweenness Centrality scores to file. Please wait... BETWEENNESS CENTRALITY (BC) The BC index of a node u is the sum of &delta;<sub>(s,t,u)</sub> for all s,t &isin; V where &delta;<sub>(s,t,u)</sub> is the ratio of all geodesics between s and t which run through u. BC' is the standardized index (BC divided by (N-1)(N-2)/2 in symmetric nets or (N-1)(N-2) otherwise. BC range: 0 &le; BC &le; (Number of pairs of nodes excluding u) BC' range: 0 &le; BC' &le; 1 (BC'=1 when the node falls on all geodesics) BC BC' %BC' All nodes have the same BC score. BC Sum = Max BC' = Min BC' = BC' classes = BC' Sum = BC' Mean = BC' Variance = BC' DISTRIBUTION GROUP BETWEENNESS CENTRALIZATION (GBC) GBC = GBC range: GBC = 0, when all the nodes have exactly the same betweenness index. GBC = 1, when one node falls on all other geodesics between all the remaining (N-1) nodes. Betweenness Centrality report, <br /> Writing Stress Centralities. Please wait... STRESS CENTRALITY (SC) The SC index of each node u is the sum of &sigma;<sub>(s,t,u)</sub>): <br />the number of geodesics from s to t through u. SC' is the standardized index (SC divided by sumSC). SC range: 0 &le; SC &le; SC' range: 0 &le; SC' &le; 1 (SC'=1 when the node falls on all geodesics) SC SC' %SC' All nodes have the same SC score. SC Sum = Max SC' = Min SC' = BC classes = SC' Sum = SC' Mean = SC' Variance = SC' DISTRIBUTION Stress Centrality report, <br /> Writing Eccentricity Centralities to file. Please wait... ECCENTRICITY CENTRALITY (EC) The EC score of a node u is the inverse maximum geodesic distance from u to all other nodes in the network. This index is also known as <em>Harary Graph Centrality</em>. EC is standardized. EC range: 0 &le; EC &le; 1 (EC=1 when the actor has ties to all other nodes) EC=EC' %EC' All nodes have the same EC score. Max EC = Min EC = EC classes = EC Sum = EC Mean = EC Variance = EC DISTRIBUTION Eccentricity Centrality report, <br /> Writing Gil-Schmidt Power Centralities to file. Please wait... POWER CENTRALITY (PC) The PC index, introduced by Gil and Schmidt, of a node u is the sum of the sizes of all Nth-order neighbourhoods with weight 1/n. PC' is the standardized index: The PC score divided by the total number of nodes in the same component minus 1 PC range: 0 &le; PC &le; PC' range: 0 &le; PC' &le; 1 (PC'=1 when the node is connected to all (star).) PC PC' %PC' All nodes have the same PC score. PC Sum = Max PC' = Min PC' = PC classes = PC' Sum = PC' Mean = PC' Variance = PC' DISTRIBUTION GROUP POWER CENTRALIZATION (GPC) GPC = GPC range: GPC = 0, when all in-degrees are equal (i.e. regular lattice). GPC = 1, when one node is linked to all other nodes (i.e. star). Use mean or variance instead. Power Centrality report, <br /> Writing Degree Prestige (in-Degree) scores to file. Please wait ... DEGREE PRESTIGE (DP) The DP index, also known as InDegree Centrality, of a node u is the sum of inbound edges to that node from all adjacent nodes. <br />If the network is weighted, DP is the sum of inbound arc weights (Indegree) to node u from all adjacent nodes. DP' is the standardized index (DP divided by N-1). DP range: 0 &le; DP &le; DP' range: 0 &le; DP' &le; 1 DP DP' %DP' All nodes have the same DP score. DP Sum = Max DP' = Min DP' = DP' classes = DP' Sum = DP' Mean = DP' Variance = DP' DISTRIBUTION GROUP DEGREE PRESTIGE (GDP) GDP = GDP range: GDP = 0, when all in-degrees are equal (i.e. regular lattice). GDP = 1, when one node is chosen by all other nodes (i.e. star). Degree Prestige report, <br /> Writing Proximity Prestige scores to file. Please wait ... PROXIMITY PRESTIGE (PP) The PP index of a node u is the ratio of the proportion of nodes who can reach u to the average distance these nodes are from u (Wasserman & Faust, formula 5.25, p. 204)<br />Thus, it is similar to Closeness Centrality but it counts only inbound distances to each actor, thus it is a measure of actor prestige. <br />This metric is useful for directed networks which are not strongly connected (thus the ordinary CC index cannot be computed).<br />In undirected networks, the PP has the same properties and yields the same results as Closeness Centrality.<br />Read the Manual for more. <br /> PP range: 0 &le; PP &le; 1 (PP is a ratio) PP=PP' %PP All nodes have the same PP score. Max PP = Min PP = PP classes = PP Sum = PP Mean = PP Variance = PP DISTRIBUTION Proximity Prestige report, <br /> Writing PageRank scores to file. Please wait ... PAGERANK PRESTIGE (PRP) The PRP is an importance ranking index for each node based on the structure of its incoming links/edges and the rank of the nodes linking to it. <br />For each node u the algorithm counts all inbound links (edges) to it, but it normalizes each inbound link from a node v by the outDegree of v. <br />The PR values correspond to the principal eigenvector of the normalized link matrix.<br />Note: In weighted relations, each backlink to a node u from another node v is considered to have weight=1 but it is normalized by the sum of outbound edge weights of v. Therefore, nodes with high outLink weights give smaller percentage of their PR to node u. PRP' is the scaled PRP (PRP divided by max PRP). PRP range: (1-d)/N = &le; PRP PRP' range: 0 &le; PRP' &le; 1 PRP PRP' %PRP' All nodes have the same PRP score. Max PRP = Min PRP = PRP classes = PRP Sum = PRP Mean = PRP Variance = PRP' DISTRIBUTION PageRank Prestige report, <br /> Writing Walks matrix to file: Computing Walks... WALKS OF LENGTH %1 MATRIX TOTAL WALKS MATRIX The Walks of length %1 matrix is a NxN matrix where each element (i,j) is the number of walks of length %1 between actor i and actor j, or 0 if no walk exists. <br />A walk is a sequence of edges and vertices, where each edge's endpoints are the two vertices adjacent to it. In a walk, vertices and edges may repeat. <br />Warning: Walks count unordered pairs of nodes. The Total Walks matrix of a social network is a NxN matrix where each element (i,j) is the total number of walks of any length (less than or equal to %1) between actor i and actor j, or 0 if no walk exists. <br />A walk is a sequence of edges and vertices, where each edge's endpoints are the two vertices adjacent to it. In a walk, vertices and edges may repeat. <br />Warning: Walks count unordered pairs of nodes. Walks report, <br /> Reachability Matrix (XR) Two nodes are reachable if there is a walk between them (their geodesic distance is non-zero). If nodes i and j are reachable then XR(i,j)=1 otherwise XR(i,j)=0. Writing Clustering Coefficients to file. Please wait... CLUSTERING COEFFICIENT (CLC) REPORT The local Clustering Coefficient, introduced by Watts and Strogatz (1998) quantifies how close each node and its neighbors are to being a complete subgraph (clique). For each node <em>u</em>, the local CLC score is the proportion of actual links between its neighbors divided by the number of links that could possibly exist between them. <br />The CLC index is used to characterize the transitivity of a network. A value close to one indicates that the node is involved in many transitive relations. CLC' is the normalized CLC, divided by maximum CLC found in this network. CLC range: 0 &le; CLC &le; 1 0 &le; CLC' &le; 1 CLC CLC' %CLC' All nodes have the same local CLC score. Max CLC = Min CLC = CLC Mean = CLC Variance = GROUP / NETWORK AVERAGE CLUSTERING COEFFICIENT (GCLC) GCLC = Range: 0 < GCLC < 1 <br/ > GCLC = 0, when there are no cliques (i.e. acyclic tree). <br /> GCLC = 1, when every node and its neighborhood are complete cliques. Clustering Coefficient report, <br /> Computing triad census. Please wait.... Writing Triad Census to file. Please wait... TRIAD CENSUS (TRC) REPORT A Triad Census counts all the different types (classes) of observed triads within a network. <br />The triad types are coded and labeled according to their number of mutual, asymmetric and non-existent (null) dyads. <br />SocNetV follows the M-A-N labeling scheme, as described by Holland, Leinhardt and Davis in their studies. <br />In the M-A-N scheme, each triad type has a label with four characters: <br /> - The first character is the number of mutual (M) dyads in the triad. Possible values: 0, 1, 2, 3.<br />- The second character is the number of asymmetric (A) dyads in the triad. Possible values: 0, 1, 2, 3.<br />- The third character is the number of null (N) dyads in the triad. Possible values: 0, 1, 2, 3.<br />- The fourth character is inferred from features or the nature of the triad, i.e. presence of cycle or transitivity. Possible values: none, D ("Down"), U ("Up"), C ("Cyclic"), T ("Transitive") Type Census Triad Census report, <br /> Computing Clique Census and writing it to a file. Please wait... Computing Clique Census. Please wait.. Writing Clique Census to file. Please wait.. CLIQUE CENSUS (CLQs) REPORT A clique is the largest subgroup of actors in the social network who are all directly connected to each other (maximal complete subgraph). <br />SocNetV applies the Bron–Kerbosch algorithm to produce a census of all maximal cliques in the network and reports some useful statistics such as disaggregation by vertex and co-membership information. <br /> Maximal Cliques found: Clique No Clique members Actor by clique analysis: Proportion of clique members adjacent <sub>Actor</sub>/<sup>Clique</sup> Actor by actor analysis: Co-membership matrix <sub>Actor</sub>/<sup>Actor</sup> Hierarchical clustering of overlap matrix: Actors Computing HCA for Cliques. Please wait.. Writing HCA for Cliques. Please wait.. Clique by clique analysis: Co-membership matrix Clique Clique Census Report, <br /> Computing hierarchical clustering. Please wait... Error: unsupported matrix type. Writing Hierarchical Cluster Analysis to file. Please wait... HIERARCHICAL CLUSTERING (HCA) Input matrix: Distance/dissimilarity metric: Clustering method/criterion: Analysis results Structural Equivalence Matrix: Hierarchical Clustering of Equivalence Matrix: Hierarchical Cluster Analysis report, <br /> Clustering Dendrogram (SVG) Examining pair-wise similarity of actors... SIMILARITY MATRIX: MATCHING COEFFICIENTS (SMMC) Variables in: Matching measure: Examining pair-wise tie profile dissimilarities of actors... Writing tie profile dissimilarities to file: DISSIMILARITIES MATRIX Metric: Diagonal: Range: 0 &lt; C &lt; 1 0 &lt; C Analysis results DSM = 0 when two actors have no tie profile dissimilarities. The actors have the same ties to all others. DSM &gt; 0 when the two actors have differences in their ties to other actors. Dissimilarity Matrix Report, <br /> Writing Similarity coefficients to file. Please wait... SMMC range: 0 &lt; C SMMC = 0 when two actors are absolutely similar (no tie/distance differences). SMMC &gt; 0 when two actors have some differences in their ties/distances, i.e. SMMC = 3 means the two actors have 3 differences in their tie/distance profiles to other actors. when there is no tie profile similarity at all. when two actors have some matches in their ties/distances, i.e. SMMC = 1 means the two actors have their ties to other actors exactly the same all the time. Similarity Matrix by Matching Measure Report, <br /> Calculating Pearson Correlations... Writing Pearson coefficients to file: PEARSON CORRELATION COEFFICIENTS (PCC) MATRIX PCC range: PCC = 0 when there is no correlation at all. PCC &gt; 0 when there is positive correlation, i.e. +1 means actors with same patterns of ties/distances. PCC &lt; 0 when there is negative correlation, i.e. -1 for actors with exactly opposite patterns of ties. Pearson Correlation Coefficients Report, <br /> Campnet dataset The dataset is the interactions among 18 people, including 4 instructors, participating in a 3-week workshop. Each person was asked to rank everyone else in terms of how much time they spent with them. This dataset shows only top 3 choices for each respondent(week 2 and week 3). Thus, there is a 1 for xij if person i listed person j as one of their top 3 interactors. The Camp data were collected by Steve Borgatti, Russ Bernard and Bert Pelto in 1992 at the NSF Summer Institute for Ethnographic Research Methods. During the 3-week workshop, all the participants and instructors were housed at the same motel and spent a great deal of time together. The participants were all faculty in Anthropology except Holly, who was a PhD student. Herschel graph The Herschel graph is the smallest nonhamiltonian polyhedral graph. It is the unique such graph on 11 nodes, and has 18 edges. High-tech Managers Krackhardt's High-tech Managers is a famous social network of 21 managers of a high-tech US company. The company manufactured high-tech equipment and had just over 100 employees with 21 managers. David Krackhardt collected the data to assess the effects of a recent management intervention program. The network consists of 3 relations: - Advice - Friendship - Reports To Each manager was asked to whom do you go to for advice and who is your friend. Data for the "whom do you report" relation was taken from company documents. This data is used by Wasserman and Faust in their seminal network analysis book. Krackhardt D. (1987). Cognitive social structures. Social Networks, 9, 104-134. Padgett's Florentine_Families This famous data set includes 16 families who were fighting each other to gain political control of the city of Florence circa 1430. Among the 16 families, the Medicis and the Strozzis were the two most prominent with factions formed around them. The data set is actually a subset of the original data on social relations among 116 Renaissance Florentine Families collected by John Padgett. This subset was used by Breiger & Pattison (1986) in their paper about local role analysis. Padgett researched historical documents to code two relations: Business ties (loans, credits, partnerships) Marrital ties (marriage alliances). Breiger R. and Pattison P. (1986). Cumulated social roles: The duality of persons and their algebras. Social Networks, 8, 215-256. Zachary Karate Club The Zachary Karate Club is a well-known social network of 34 members of a university karate club studied by Wayne W. Zachary from 1970 to 1972. During the study, disputes among two members led to club splitting into two groups. Zachary documented 78 ties between members who interacted outside the club and used the collected data and an information flow model to explain the split-up. There are two relations (matrices) in this network:The ZACHE relation represents the presence or absence of ties among the actors. The ZACHC relation indicates the relative strength of their associations (number of situations in and outside the club in which interactions occurred). Zachary W. (1977). An information flow model for conflict and fission in small groups. Journal of Anthropological Research, 33, 452-473. Galaskiewicz's CEOs and Clubs The affiliation network of the chief executive officers and their spouses from 26 corporations and banks in 15 clubs, corporate and cultural boards. Membership was during the period 1978-1981 This is a 26x15 affiliation matrix, where the rows correspond to the 26 CEOs and the columns to the 15 clubs. This data was originally collected by Galaskiewicz (1985) and is used by Wasserman and Faust in Social Network Analysis: Methods and Applications (1994). Galaskiewicz, J. (1985). Social Organization of an Urban Grants Economy. New York: Academic Press. Thurman's Office Networks and Coalitions In the late 70s, B. Thurman spent 16 months observing the interactions among employees in the overseas office of a large international corporation. During this time, two major disputes erupted in a subgroup of fifteen people. Thurman analyzed the outcome of these disputes in terms of the network of formal and informal associations among those involved. This labeled dataset contains two relations (15x15 matrices): THURA is a 15x15 non-symmetric, binary matrix showing the formal organizational chart of the employees. THURM is a 15x15 symmetric binary matrix which shows the actors linked by multiplex ties. Thurman B. (1979). In the office: Networks and coalitions. Social Networks, 2, 47-63 Corporate Interlocks in Netherlands A 16x16 symmetric, binary matrix.This data represent corporate interlocks among the major business entities in the Netherlands. The data were gathered during a 6-year research project which was concluded in 1976 in nine European countries and the USA Stokman F., Wasseur F. and Elsas D. (1985). The Dutch network: Types of interlocks and network structure. In F. Stokman, R. Ziegler & J. Scott (eds), Networks of corporate power. Cambridge: Polity Press, 1985 Corporate Interlocks in West Germany A 15x15 symmetric, binary matrix.This data represent corporate interlocks among the major business entities in the West Germany. The data were gathered during a 6-year research project which was concluded in 1976 in nine European countries and the USA Ziegler R., Bender R. and Biehler H. (1985). Industry and banking in the German corporate network. In F. Stokman, R. Ziegler & J. Scott (eds), Networks of corporate power. Cambridge: Polity Press, 1985. Bernard and Killworth Fraternity Bernard & Killworth recorded the interactions among students living in a fraternity at a West Virginia college. Subjects had been residents in the fraternity from 3 months to 3 years. This network dataset contains two relations: The BKFRAB relation is symmetric and valued. It counts the number of times a pair of subjects were seen in conversation by an unobtrusive observer (observation time: 21 hours a day, for five days). The BKFRAC relation is non-symmetric and valued. Contains rankings made by the subjects themselves of how frequently they interacted with other subjects in the observation week. Knoke D. and Wood J. (1981). Organized for action: Commitment in voluntary associations. New Brunswick, NJ: Rutgers University Press. Knoke D. and Kuklinski J. (1982). Network analysis, Beverly Hills, CA: Sage Freeman's EIES Networks This data comes from an early experiment on computer mediated communication. Fifty academics were allowed to contact each other via an Electronic Information Exchange System (EIES). The data collected consisted of all messages sent plus acquaintance relationships at two time periods. The data includes the 32 actors who completed the study and the following three 32x32 relations: TIME_1 non-symmetric, valued TIME_2 non-symmetric, valued NUMBER_OF_MESSAGES non-symmetric, valued TIME_1 and TIME_2 give the acquaintance information at the beginning and end of the study. This is coded as follows: 4 = close personal fiend, 3 = friend, 2= person I've met, 1 = person I've heard of but not met, and 0 = person unknown to me (or no reply). NUMBER_OF MESSAGES is the total number of messages person i sent to j over the entire period of the study. Freeman's EIES network (Acquaintanceship) at time 1 Freeman's EIES network (Acquaintanceship) at time 2 Freeman's EIES network (Messages) Freeman's 34 possible graphs of N=5 This data comes from Freeman's (1979) seminal paper "Centrality in social networks". It illustrates all 34 possible graphs of five nodes. Freeman used them to calculate and compare the three measures of Centrality: Degree, Betweenness and Closeness. Use Relation buttons on the toolbar to move between the graphs. Mexican Power Network in the 1940s Knoke Bureaucracies In 1978, Knoke & Wood collected data from workers at 95 organizations in Indianapolis. Respondents indicated with which other organizations their own organization had any of 13 different types of relationships. Knoke and Kuklinski (1982) selected a subset of 10 organizations and two relationships: information exchange and money exchange. This dataset is directed and not symmetric. Information exchange is recorded in KNOKI relation while money exchange in KNOKM . Stephenson & Zelen's AIDS patients network (sex contact) The data described by Auerbach et al. (1984) and Klovdahl (1985) consists of information on 40 homosexual men diagnosed with AIDS. Initially, 19 men residing in the Los Angeles and Orange County area were interviewed about their previous sexual contacts. This information led to the subsequent identification of an additional 21 sexual partners in San Francisco, New York and other parts of the United States. All 40 homosexual men were linked to each other through sexual contact. Galada baboon colony network (H22a) A network of the Galada baboon colony, as described by Dunbar and Dunbar (1975). This is the first set of observations (H22a) and was made on 12 baboons. The lines connecting two points (baboons) represent nonagonistic interactions (generally grooming behavior) and the frequency of such interactions is recorded by the edge weight. Data derived from Stephenson & Zelen seminal 1989 paper where they introduced Information Centrality. Wasserman & Faust's 7 actors graphs Wasserman & Faust's Countries Trade Data (manufactured goods) This data set is just a famous non-planar mathematical graph, named after Julius Petersen, who constructed it in 1898. The Petersen graph is undirected with 10 vertices and 15 edges and the smallest bridgeless cubic graph with no three-edge-coloring. This small graph serves as a useful example and counterexample for many problems in graph theory. Adjacency recomputed. Writing Adjacency Matrix... Need to recompute Adjacency Matrix. Please wait... Adjacency recomputed. Writing Laplacian Matrix... Adjacency recomputed. Writing Degree Matrix... Distances recomputed. Writing Distances Matrix... Distances recomputed. Writing Shortest Paths Matrix... Computing Inverse Adjacency Matrix. Please wait... Inverse Adjacency Matrix computed. Writing Matrix... Writing Reachability Matrix... Need to recompute tie profile distances. Please wait... Tie profile distances recomputed. Writing matrix... ADJACENCY MATRIX REPORT LAPLACIAN MATRIX REPORT DEGREE MATRIX REPORT DISTANCES MATRIX REPORT SHORTEST PATHS (GEODESICS) MATRIX REPORT INVERSE ADJACENCY MATRIX REPORT REACHABILITY MATRIX REPORT TRANSPOSE OF ADJACENCY MATRIX REPORT COCITATION MATRIX REPORT EUCLIDEAN DISTANCE MATRIX REPORT HAMMING DISTANCE MATRIX REPORT JACCARD DISTANCE MATRIX REPORT MANHATTAN DISTANCE MATRIX REPORT The adjacency matrix, AM, of a social network is a NxN matrix where each element (i,j) is the value of the edge from actor i to actor j, or 0 if no edge exists. The laplacian matrix L of a social network is a NxN matrix with L = D - A, where D the degree matrix and A the adjacency matrix. The elements of L are: <br />- L<sub>i,j</sub> = d<sub>i</sub>, if i = j, <br />- L<sub>i,j</sub> = -1, if i &ne; j and there is an edge (i,j)<br />- and all other elements zero.<br /> The degree matrix D of a social network is a NxN matrix where each element (i,i) is the degree of actor i and all other elements are zero. The distance matrix of a social network is a NxN matrix where each element (i,j) is the geodesic distance (length of shortest path) from actor i to actor j, or infinity if no shortest path exists. The geodesics matrix of a social network is a NxN matrix where each element (i,j) is the number of shortest paths(geodesics) from actor i to actor j, or infinity if no shortest path exists. The adjacency matrix is singular. The reachability matrix R of a social network is a NxN matrix where each element R(i,j) is 1 if actors j is reachable from i otherwise 0. <br />Two nodes are reachable if there is a walk between them (their geodesic distance is non-zero). <br />Essentially the reachability matrix is a dichotomized geodesics matrix. The adjacency matrix AM of a social network is a NxN matrix where each element (i,j) is the value of the edge from actor i to actor j, or 0 if no edge exists. This is the transpose of the adjacency matrix, AM<sup>T</sup>, a matrix whose (i,j) element is the (j,i) element of AM. The Cocitation matrix, C = A<sup>T</sup> * A, is a NxN matrix where each element (i,j) is the number of actors that have outbound ties/links to both actors i and j. The diagonal elements, C<sub>ii</sub>, of the Cocitation matrix are equal to the number of inbound edges of i (inDegree). C is a symmetric matrix. The Euclidean distances matrix is a NxN matrix where each element (i,j) is the Euclidean distanceof the tie profiles between actors i and j, namely the square root of the sum of their squared differences. The Hamming distances matrix is a NxN matrix where each element (i,j) is the Hamming distanceof the tie profiles between actors i and j, namely the number of different ties to other actors. The Jaccard distances matrix is a NxN matrix where each element (i,j) is the Jaccard distanceof the tie profiles between actors i and j. The Manhattan distances matrix is a NxN matrix where each element (i,j) is the Manhattan distanceof the tie profiles between actors i and j, namely the sum of their absolute differences. The Chebyshev distances matrix is a NxN matrix where each element (i,j) is the Chebyshev distanceof the tie profiles between actors i and j, namely the greatest of their differences. Matrix report, <br /> Writing matrix to file. Please wait... <sub>Actor</sup>/<sup>Actor</sup> Writing Adjacency Matrix to file. Please wait... ADJACENCY MATRIX The adjacency matrix of a social network is a NxN matrix Adjacency matrix report, <br /> Plotting Adjacency Matrix. Please wait... ADJACENCY MATRIX PLOT This a plot of the network's adjacency matrix, a NxN matrix where each element (i,j) is filled if there is an edge from actor i to actor j, or not filled if no edge exists. Adjacency matrix plot, <br /> Computing Similarity coefficients matrix. Please wait... New node (numbered %1) added at position (%2,%3). Double-click on it to start a new edge from it. -clique Creating subgraph. Please wait... Found %1 matching nodes. Could not find any nodes matching your choices. MainWindow &New &Open &Save &Adjacency Matrix &Pajek &List &DL... &GW... &Close Close Closes the actual network &Print E&xit Exit Quits the application Ring Lattice Gaussian Gaussian Creates a random network of Gaussian distribution Small World Add Node Remove Node Change Background Color Strong Structural Nodes are assigned the same color if they have identical in and out neighborhoods Click this to colorize nodes; Nodes are assigned the same color if they have identical in and out neighborhoods Regular Nodes are assigned the same color if they have neighborhoods of the same set of colors Click this to colorize nodes; Nodes are assigned the same color if they have neighborhoods of the same set of colors Random Eccentricity Fruchterman-Reingold Repelling forces between all nodes, and attracting forces between adjacent nodes. Fruchterman-Reingold Layout Embeds a layout all nodes according to a model in which repelling forces are used between every pair of nodes, while attracting forces are used only between adjacent nodes. The algorithm continues until the system retains its equilibrium state where all forces cancel each other. Bezier Curves Manual Read the manual... Manual Displays the documentation of SocNetV Tip of the Day Read useful tips Quick Tips Displays some useful and quick tips About SocNetV About Basic information about SocNetV About Qt About About Qt &Network &Edit Filter... &Layout &Options &Help Network Layout Nothing to save. There are no vertices. Graph already saved. Save to GraphML? Default File Format: GraphML This network will be saved in GraphML format which is the default file format of SocNetV. Is this OK? If not, press Cancel, then go to Network > Export menu to see other supported formats to export your data to. Save aborted... Enter or select a filename to save the network... Save Network to GraphML File Named... Appending .graphml extension. Missing file extension. Appended the standard .graphml extension to the given filename. Final Filename: Using .graphml extension. Wrong file extension. Appended the standard .graphml extension to the given filename. Error! Could not save this file: %1 Network saved under filename: %1 Closing network file... Closing Network... Ready. Ready Yes No Choose a network file... Error loading requested file. Aborted. Opening aborted Welcome to %1, version %2 Closing SocNetV. Bye! Save changes Modified network has not been saved! Do you want to save the changes to the network file? Error loading settings file Error loading settings Error! I cannot read the settings file in %1 You can continue using SocNetV with default settings but any changes to them will not be saved for future sessions Please, check permissions in your home folder and contact the developer team. Error writing settings file Error writing settings I cannot write the settings file in %1 You can continue using SocNetV with default settings but any changes to them will not be saved for future sessions Please, check permissions in your home folder and contact the developer team. <p><b>The canvas of SocNetV</b></p><p>Inside this area you create and edit networks, load networks from files and visualize them according to the selected metrics. </p><p>To create a new node, <em>double-click</em> anywhere.</p><p>To add an edge between two nodes, <em>double-click</em> on the first node (source) then double-click on the second (target) .</p><p>To move around the canvas, use the keyboard arrows.</p><p>To change network appearance, <em>right click on empty space</em>. </p><p>To edit the properties of a node, <em>right-click</em> on it. </p><p>To edit the properties of an edge, <em>right-click</em> on it.</p> Create a new network New network New Creates a new social network. First, checks if current network needs to be saved. Open network Open a GraphML formatted file of social network data. Open Opens a file of a social network in GraphML format &GML Import GML-formatted file Import GML Imports a social network from a GML-formatted file Import Pajek-formatted file Import Pajek Imports a social network from a Pajek-formatted file Import Adjacency matrix Import Sociomatrix Imports a social network from an Adjacency matrix-formatted file Graph&Viz (.dot) Import dot file Import GraphViz Imports a social network from a GraphViz formatted file &UCINET (.dl)... ImportDL-formatted file (UCINET) Import UCINET Imports social network data from a DL-formatted file &Edge list Import an edge list file. Import edge list Import a network from an edgelist file. SocNetV supports EdgeList files with edge weights as well as simple EdgeList files where the edges are non-value (see manual) &Two Mode Sociomatrix Import two-mode sociomatrix (affiliation network) file Import Two-Mode Sociomatrix Imports a two-mode network from a sociomatrix file. Two-mode networks are described by affiliation network matrices, where A(i,j) codes the events/organizations each actor is affiliated with. Save social network to a file Save. Saves the social network to file Save As... Save network under a new filename Save As Saves the social network under a new filename Export to I&mage... Export the visible part of the network to image Export to Image Exports the visible part of the current social network to an image E&xport to PDF... Export the visible part of the network to a PDF file Export to PDF Exports the visible part of the current social network to a PDF document. Export social network to an adjacency/sociomatrix file Export network to Adjacency format Exports the social network to an adjacency matrix-formatted file Export social network to a Pajek-formatted file Export Pajek Exports the social network to a Pajek-formatted file Export to List-formatted file. Export List Exports the network to a List-formatted file Export network to UCINET-formatted file Export UCINET Exports the active network to a DL-formatted Export to GW-formatted file Export Exports the active network to a GW formatted file Close the actual network Send the currrent social network to the printer Print Sends whatever is viewable on the canvas to your printer. To print the whole social network, you might want to zoom-out. Quit SocNetV. Are you sure? Open &Text Editor Open a text editor to take notes, copy/paste network data, etc <p><b>Text Editor</b></p><p>Opens a simple text editor where you can copy paste network data, of any supported format, and save to a file. Then you can import that file to SocNetV. </p> &View Loaded File Display the loaded social network file. View Loaded File Displays the loaded social network file View &Adjacency Matrix Display the adjacency matrix of the network. <p><b>View Adjacency Matrix</b></p><p>Displays the adjacency matrix of the active network. </p><p>The adjacency matrix of a social network is a matrix where each element a(i,j) is equal to the weight of the arc from actor (node) i to actor j. <p>If the actors are not connected, then a(i,j)=0. </p> P&lot Adjacency Matrix (text) Plots the adjacency matrix in a text file using unicode characters. <p><b>Plot Adjacency Matrix (text)</b></p><p>Plots the adjacency matrix in a text file using unicode characters. </p><p>In every element (i,j) of the "image", a black square means actors i and j are connectedwhereas a white square means they are disconnected.</p> Create From &Known Data Sets Load one of the 'famous' social network data sets included in SocNetV. <p><b>Famous Data Sets</b></p><p>SocNetV includes a number of known (also called famous) data sets in Social Network Analysis, such as Krackhardt's high-tech managers, etc. Click this menu item or press F7 to load a data set.</p> Scale-free Create a random network with a power-law degree distribution. <p><b>Scale-free (power-law)</b></p><p>A scale-free network is a network whose degree distribution follows a power law. SocNetV generates random scale-free networks according to the BarabΓ‘si–Albert (BA) model using a preferential attachment mechanism.</p> Create a small-world random network, according to the Watts & Strogatz model. <p><b>Small World </b></p><p>Creates a random small-world network, according to the Watts & Strogatz model. </p><p>A small-world network has short average path lengths and high clustering coefficient.</p> ErdΕ‘s–RΓ©nyi Create a random network according to the ErdΕ‘s–RΓ©nyi model <p><b>ErdΕ‘s–RΓ©nyi </b></p><p>Creates a random network either of G(n, p) model or G(n,M) model. </p><p>The former model creates edges with Bernoulli trials (probability p).</p><p>The latter creates a graph of exactly M edges.</p> Lattice Create a lattice network. <p><b>Lattice </b></p><p>Creates a random lattice network</p><p>A lattice is a network whose drawing forms a regular tiling. Lattices are also known as meshes or grids.</p> d-Regular Create a d-regular random network, where every actor has the same degree d. <p><b>d-Regular</b></p><p>Creates a random network where each actor has the same number <em>d</em> of neighbours, aka the same degree d.</p> Create a ring lattice random network. <p><b>Ring Lattice </b></p><p>Creates a ring lattice random network. </p><p>A ring lattice is a graph with N vertices each connected to d neighbors, d / 2 on each side.</p> Create a Gaussian distributed random network. &Web Crawler Use the web crawler to create a network from all links found in a given website <p><b>Web Crawler </b></p><p>Creates a network of linked webpages, starting from an initial webpage using the built-in Web Crawler. </p><p>The web crawler visits the given URL (website or webpage) and parses its contents to find links to other pages (internal or external). If there are such links, it adds them to a queue of URLs. Then, all the URLs in the queue list are visited in a FIFO order and parsed to find more links which are also added to the url queue. The process repeats until it reaches user-defined limits: </p><p>Maximum urls to visit (max nodes in the resulting network)</p> <p>Maximum links per page</p><p>Except the initial url and the limits, you can also specify patterns of urls to include or exclude, types of links to follow (internal, external or both) as well as if you want delay between requests (strongly advised)</p>. Select/Move <p><b>Mouse mode: Interactive</b></p> <p>In this interactive mode, you can click on nodes/edges and move them around with your mouse. </p><p>Also, you can select multiple items with a rubber band selection area. To move the canvas, use the keyboard arrows.</p> Enable the interactive mouse mode to be able to click and move items and select them with a rubber band. <p><b>Mouse Mode: Interactive</b></p><p>In this mode, you can interact with the items on the canvas using the mouse: </p><p>a) double-click to create new nodes, <p>b) left-click or right-click on items (i.e. nodes, edges) to edit their properties</p><p>c) move nodes by dragging them with your mouse. </p><p>d) select multiple items with a rubber band.</p><p>To move the canvas (up/down, left/right), use the keyboard arrows. Scroll/Pan <p><b>Mouse mode: Scrolling</b></p> <p>In this non-interactive mode, you can easily scroll the canvas by dragging the mouse around. All mouse actions are disabled.</p> Enable this non-interactive mode to easily scroll the canvas by dragging the mouse around. <p><b>Mouse mode: Scrolling</b></p><p>In this mode, you cannot interact with the canvas using the mouse.</p><p>The cursor changes into a pointing hand, and dragging the mouse around will only scroll the scrolbars.</p> <p>You will not be able to select any items or move them around with the mouse.</p><p>Note: You will still be able to edit the network using the menu or the toolbar actions and icons.</p> Next Relation Goto the next relation of the network (if any). Next Relation Loads the next relation of the network (if any) Previous Relation Goto the previous relation of the network (if any). Previous Relation Loads the previous relation of the network (if any) Add New Relation Add a new relation to the network. Nodes will be preserved, edges will be removed. Add New Relation Adds a new relation to the active network. Nodes will be preserved, edges will be removed. Rename Relation Rename current relation Rename the current relation of the network. Rename Relation Renames the current relation of the network. Zoom In Zoom In. Zooms in the network Zoom in the network. Alternatives: use the canvas button, or press Ctrl++, or use mouse wheel while pressing Ctrl. Zoom Out Zoom Out. Zooms out of the actual network Zoom out the network. Alternatives: use the canvas button, or press Ctrl+-, or use mouse wheel while pressing Ctrl. Rotate counterclockwise Rotate counterclockwise. You can also use the button underneath the canvas. Rotates the network counterclockwise. You can also use the far left button below the canvas. Rotate clockwise Rotate clockwise. You can also use the button underneath the canvas. Rotates the network clockwise. You can also use the far right button below the canvas. Reset Zoom and Rotation Reset zoom and rotation to zero. Resets any zoom and rotation transformations to zero. Select All Select all nodes Select All Selects all nodes and edges in the network Select None Ctrl+Alt+A Deselect all nodes and edges Deselect all Clears the node selection Find Nodes Find and select one or more nodes by their number or label. Find Node Finds one or more nodes by their number or label and highlights them by doubling its size. Ctrl+. Add a new node to the network in a random position. Alternately, double-click on a specific position the canvas. Add a new node to the network in a random position. Alternately, create a new node by double-clicking on a specific position the canvas. Add new node Add a new node to the network in a random position. Alternately, you can create a new node by double-clicking on a specific position the canvas. Remove selected node(s). If no nodes are selected, you will be prompted for a node number. Remove selected node(s). If no nodes are selected, you will be prompted for a node number. Remove node Removes selected node(s) from the network. Alternately, you can remove a node by right-clicking on it. If no nodes are selected, you will be prompted for a node number. Selected Node Properties Change the properties of the selected node(s) There must be some nodes on the canvas! Change the basic properties of the selected node(s). There must be some nodes on the canvas! Selected Node Properties If there are one or more nodes selected, it opens a properties dialog to edit their label, size, color, shape etc. You must have some node selected. Create a clique from selected nodes Connect all selected nodes with edges to create a clique -- There must be some nodes selected! Clique from Selected Nodes Adds all possible edges between selected nodes, so that they become a complete subgraph (clique) You must have some nodes selected. Create a star from selected nodes Connect selected nodes with edges/arcs to create a star -- There must be some nodes selected! Star from Selected Nodes Adds edges between selected nodes, so that they become a star subgraph. You must have some nodes selected. Create a cycle from selected nodes Cycle from Selected Nodes Connect selected nodes so that they become a cycle subgraph. A cycle graph or circular graph is a graph that consists of a single cycle, or in other words, the vertices are connected in a closed chain. The cycle graph with n vertices is called Cβ‚™ You must have some nodes selected. Create a line from selected nodes Connect selected nodes with edges/arcs to create a line-- There must be some nodes selected! Line from Selected Nodes Adds edges between selected nodes, so that they become a line subgraph. You must have some nodes selected. Change All Nodes Color (this session) Choose a new color for all nodes (in this session only). Nodes Color Changes all nodes color at once. This setting will apply to this session only. To permanently change it, go to Settings. Change All Nodes Size (this session) Change the size of all nodes (in this session only) Change All Nodes Size Click to select and apply a new size for all nodes at once. This setting will apply to this session only. To permanently change it, go to Settings. Change All Nodes Shape (this session) Change the shape of all nodes (this session only) Change All Nodes Shape Click to select and apply a new shape for all nodes at once.This setting will apply to this session only. To permanently change it, go to Settings. Change All Node Numbers Size (this session) Change the font size of the numbers of all nodes(in this session only) Change Node Numbers Size Click to select and apply a new font size for all node numbersThis setting will apply to this session only. To permanently change it, go to Settings. Change All Node Numbers Color (this session) Change the color of the numbers of all nodes.(in this session only) Node Numbers Color Click to select and apply a new color to all node numbers.This setting will apply to this session only. To permanently change it, go to Settings. Change All Node Labels Size (this session) Change the font size of the labels of all nodes(this session only) Node Labels Size Click to select and apply a new font-size to all node labelsThis setting will apply to this session only. To permanently change it, go to Settings. Change All Node Labels Color (this session) Change the color of the labels of all nodes (for this session only) Labels Color Click to select and apply a new color to all node labels.This setting will apply to this session only. To permanently change it, go to Settings. Add Edge (arc) Add a directed edge (arc) from a node to another. Add a new edge from a node to another. You can also create an edge between two nodes by double-clicking on them consecutively. Add edge Adds a new edge from a node to another. Alternately, you can create a new edge between two nodes by double-clicking on them consecutively. Remove Edge Remove selected edges from the network. If no edge has been clicked or selected, you will be prompted to enter edge source and target nodes for the edge to remove. Remove selected Edge(s) Remove Edge Removes edges from the network. If one or more edges has been clicked or selected, they are removed. Otherwise, you will be prompted to enter edge source and target nodes for the edge to remove. Change Edge Label Change the Label of an Edge Change Edge Label Changes the label of an Edge Change Edge Color Change the Color of an Edge Change Edge Color Changes the Color of an Edge Change Edge Weight Change the weight of an Edge Edge Weight Changes the Weight of an Edge Change All Edges Color Change the color of all Edges. All Edges Color Changes the color of all Edges Symmetrize All Edges Make all directed ties to be reciprocated (thus, a symmetric graph). <p><b>Symmetrize All Edges</b></p><p>Forces all edges in this relation to be reciprocated: <p>If there is a directed edge from node A to node B then a new directed edge from node B to node A will be created, with the same weight. </p><p>The result is a symmetric network.</p> Symmetrize by Strong Ties Create a new symmetric relation by counting reciprocated ties only (strong ties). <p><b>Symmetrize Edges by Strong Ties:</b></p><p>Creates a new symmetric relation by keeping strong ties only. </p><p>A tie between actors A and B is considered strong if both A -> B and B -> A exist. Therefore, in the new relation, a reciprocated edge will be created between actors A and B only if both arcs A->B and B->A were present in the current or all relations. </p><p>If the network is multi-relational, it will ask you whether ties in the current relation or all relations are to be considered.</p> Undirected Edges Enable to transform all arcs to undirected edges and hereafter work with undirected edges . Undirected Edges Transforms all directed arcs to undirected edges. The result is a undirected and symmetric network.After that, every new edge you add, will be undirected too.If you disable this, then all edges become directed again. Cocitation Network Create a new symmetric relation by connecting actors that are cocitated by others. <p><b>Symmetrize Edges by examining Cocitation:</b></p><p>Creates a new symmetric relation by connecting actors that are cocitated by others. In the new relation, an edge will be created between actor i and actor j only if C(i,j) > 0, where C the Cocitation Matrix. </p><p>Thus the actor pairs cited by more common neighbors will appear with a stronger tie between them than pairs those cited by fewer common neighbors. The resulting relation is symmetric.</p> Dichotomize Valued Edges Create a new binary relation/graph in a valued network using edge dichotomization. Dichotomize Edges Creates a new binary relation in a valued network using edge dichotomization according to a given threshold value. In the new dichotomized relation, an edge will exist between actor i and actor j only if e(i,j) > threshold, where threshold is a user-defined value.Thus the dichotomization procedure is as follows: Choose a threshold value, set all ties with equal or higher values to equal one, and all lower to equal zero.The result is a binary (dichotomized) graph. The process is also known as compression and slicing Transform Nodes to Edges Transforms the network so that nodes become Edges and vice versa Transform Nodes EdgesAct Transforms network so that nodes become Edges and vice versa Filter Nodes By Centrality Temporarily filter out nodes according to their centrality score. Filter Nodes By Centrality Filters out nodes according to their score in a user-selected centrality index. Disable Isolate Nodes Temporarily filter out nodes with no edges Filter Isolate Nodes Enables or disables displaying of isolate nodes. Isolate nodes are those with no edges... Filter Edges by Weight Temporarily filter edges of some weight out of the network Filter Edges Filters edges according to their weight. Disable unilateral edges Temporarily disable all unilateral (non-reciprocal) edges in this relation. Keeps only "strong" ties. Unilateral edges In directed networks, a tie between two actors is unilateral when only one actor identifies the other as connected (i.e. friend, vote, etc). A unilateral tie is depicted as a single arc. These ties are considered weak, as opposed to reciprocal ties where both actors identify each other as connected. Strong ties are depicted as either a single undirected edge or as two reciprocated arcs between two nodes. By selecting this option, all unilateral edges in this relation will be disabled. Layout the network actors in random positions. Random Layout This layout algorithm repositions all network actors in random positions. Random Circles Layout the network in random concentric circles Random Circles Layout Repositions the nodes randomly on circles Degree Centrality Place all nodes on concentric circles of radius inversely proportional to their Degree Centrality. Degree Centrality (DC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Degree Centrality score. Nodes with higher DC are closer to the centre. Closeness Centrality Place all nodes on concentric circles of radius inversely proportional to their Closeness Centrality. Closeness Centrality (CC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Closeness Centrality. Nodes having higher CC are closer to the centre. Influence Range Closeness Centrality Place all nodes on concentric circles of radius inversely proportional to their Influence Range Closeness Centrality. Influence Range Closeness Centrality (IRCC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their IRCC score. Nodes having higher IRCC are closer to the centre. Betweenness Centrality Place all nodes on concentric circles of radius inversely proportional to their Betweenness Centrality. Betweenness Centrality (BC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Betweenness Centrality. Nodes having higher BC are closer to the centre. Stress Centrality Place all nodes on concentric circles of radius inversely proportional to their Stress Centrality. Stress Centrality (SC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Stress Centrality score. Nodes having higher SC are closer to the centre. Eccentricity Centrality Place all nodes on concentric circles of radius inversely proportional to their Eccentricity Centrality (aka Harary Graph Centrality). Eccentricity Centrality (EC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Eccentricity Centrality (aka Harary Graph Centrality) score. Nodes having higher EC are closer to the centre. Power Centrality Place all nodes on concentric circles of radius inversely proportional to their Power Centrality. Power Centrality (PC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Power Centrality score. Nodes having higher PC are closer to the centre. Information Centrality Place all nodes on concentric circles of radius inversely proportional to their Information Centrality. Information Centrality (IC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Information Centrality score. Nodes of higher IC are closer to the centre. Eigenvector Centrality Place all nodes on concentric circles of radius inversely proportional to their Eigenvector Centrality. Eigenvector Centrality (EVC) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their Eigenvector Centrality score. Nodes of higher EVC are closer to the centre. Degree Prestige Place all nodes on concentric circles of radius inversely proportional to their Degree Prestige (inDegree). Degree Prestige (DP) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their inDegree score. Nodes having higher DP are closer to the centre. PageRank Prestige Place all nodes on concentric circles of radius inversely proportional to their PRP index. PageRank Prestige (PRP) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their PageRank score. Nodes having higher PRP are closer to the centre. Proximity Prestige Place all nodes on concentric circles of radius inversely proportional to their Proximity Prestige. Proximity Prestige (PP) Radial Layout Repositions all nodes on concentric circles of radius inversely proportional to their PP index. Nodes having higher PP score are closer to the centre. Place all nodes on horizontal levels of height proportional to their Degree Centrality. Degree Centrality (DC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their DC score. Nodes having higher DC are closer to the top. Place all nodes on horizontal levels of height proportional to their Closeness Centrality. Closeness Centrality (CC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Closeness Centrality score. Nodes of higher CC are closer to the top. This layout can be computed only for connected graphs. Place all nodes on horizontal levels of height proportional to their Influence Range Closeness Centrality. Influence Range Closeness Centrality (IRCC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their IRCC score. Nodes having higher IRCC are closer to the top. This layout can be computed for not connected graphs. Place all nodes on horizontal levels of height proportional to their Betweenness Centrality. Betweenness Centrality (BC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Betweenness Centrality score. Nodes having higher BC are closer to the top. Place nodes on horizontal levels of height proportional to their Stress Centrality. Stress Centrality (SC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Stress Centrality score. Nodes having higher SC are closer to the top. Place nodes on horizontal levels of height proportional to their Eccentricity Centrality (aka Harary Graph Centrality). Eccentricity Centrality (EC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Eccentricity Centrality (aka Harary Graph Centrality) score. Nodes having higher EC are closer to the top. Place nodes on horizontal levels of height proportional to their Power Centrality. Power Centrality (PC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Power Centrality score. Nodes having higher PC are closer to the top. Place nodes on horizontal levels of height proportional to their Information Centrality. Information Centrality (IC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Information Centrality score. Nodes having higher IC are closer to the top. Place nodes on horizontal levels of height proportional to their Eigenvector Centrality. Eigenvector Centrality (EVC) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Eigenvector Centrality score. Nodes having higher EVC are closer to the top. Place nodes on horizontal levels of height proportional to their Degree Prestige. Degree Prestige (DP) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Degree Prestige score. Nodes having higher DP are closer to the top. Place nodes on horizontal levels of height proportional to their PageRank Prestige. PageRank Prestige (PRP) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their PageRank Prestige score. Nodes having higher PRP are closer to the top. Place nodes on horizontal levels of height proportional to their Proximity Prestige. Proximity Prestige (PP) Levels Layout Repositions all nodes on horizontal levels of heightproportional to their Proximity Prestige score. Nodes having higher PP are closer to the top. Resize all nodes to be proportional to their Degree Centrality. Degree Centrality (DC) Node Size Layout Changes the size of all nodes to be proportional to their DC (inDegree) score. Nodes having higher DC will appear bigger. Resize all nodes to be proportional to their Closeness Centrality. Closeness Centrality (CC) Node Size Layout Changes the size of all nodes to be proportional to their CC score. Nodes of higher CC will appear bigger. This layout can be computed only for connected graphs. Resize all nodes to be proportional to their Influence Range Closeness Centrality. Influence Range Closeness Centrality (IRCC) Node Size Layout Changes the size of all nodes to be proportional to their IRCC score. Nodes having higher IRCC will appear bigger. This layout can be computed for not connected graphs. Resize all nodes to be proportional to their Betweenness Centrality. Betweenness Centrality (BC) Node Size Layout Changes the size of all nodes to be proportional to their Betweenness Centrality score. Nodes having higher BC will appear bigger. Resize all nodes to be proportional to their Stress Centrality. Stress Centrality (SC) Node Size Layout Changes the size of all nodes to be proportional to their Stress Centrality score. Nodes having higher SC will appear bigger. Resize all nodes to be proportional to their Eccentricity Centrality (aka Harary Graph Centrality). Eccentricity Centrality (EC) NodeSizes Layout Changes the size of all nodes to be proportional to their Eccentricity Centrality (aka Harary Graph Centrality) score. Nodes having higher EC will appear bigger. Resize all nodes to be proportional to their Power Centrality. Power Centrality (PC) Node Size Layout Changes the size of all nodes to be proportional to their Power Centrality score. Nodes having higher PC will appear bigger. Resize all nodes to be proportional to their Information Centrality. Information Centrality (IC) Node Size Layout Changes the size of all nodes to be proportional to their Information Centrality score. Nodes having higher IC will appear bigger. Resize all nodes to be proportional to their Eigenvector Centrality. Eigenvector Centrality (EVC) Node Size Layout Changes the size of all nodes to be proportional to their Eigenvector Centrality score. Nodes having higher EVC will appear bigger. Resize all nodes to be proportional to their Degree Prestige. Degree Prestige (DP) Node Size Layout Changes the size of all nodes to be proportional to their Degree Prestige score. Nodes having higher DP will appear bigger. Resize all nodes to be proportional to their PageRank Prestige. PageRank Prestige (PRP) Node Size Layout Changes the size of all nodes to be proportional to their PageRank Prestige score. Nodes having higher PRP will appear bigger. Resize all nodes to be proportional to their Proximity Prestige. Proximity Prestige (PP) Node Size Layout Changes the size of all nodes to be proportional to their Proximity Prestige score. Nodes having higher PP will appear bigger. Change the color of all nodes to reflect their Degree Centrality. Degree Centrality (DC) Node Color Layout Changes the color of all nodes to reflect their DC (inDegree) score. Nodes having higher DC will have warmer color (i.e. red). Change the color of all nodes to reflect their Closeness Centrality. Closeness Centrality (CC) Node Color Layout Changes the color of all nodes to reflect their CC score. Nodes of higher CC will have warmer color (i.e. red). This layout can be computed only for connected graphs. Change the color of all nodes to proportional to their Influence Range Closeness Centrality. Influence Range Closeness Centrality (IRCC) Node Color Layout Changes the color of all nodes to reflect their IRCC score. Nodes having higher IRCC will have warmer color (i.e. red). This layout can be computed for not connected graphs. Change the color of all nodes to reflect their Betweenness Centrality. Betweenness Centrality (BC) Node Color Layout Changes the color of all nodes to reflect their Betweenness Centrality score. Nodes having higher BC will have warmer color (i.e. red). Change the color of all nodes to reflect their Stress Centrality. Stress Centrality (SC) Node Color Layout Changes the color of all nodes to reflect their Stress Centrality score. Nodes having higher SC will have warmer color (i.e. red). Change the color of all nodes to reflect their Eccentricity Centrality (aka Harary Graph Centrality). Eccentricity Centrality (EC) NodeColors Layout Changes the color of all nodes to reflect their Eccentricity Centrality (aka Harary Graph Centrality) score. Nodes having higher EC will have warmer color (i.e. red). Change the color of all nodes to reflect their Power Centrality. Power Centrality (PC) Node Color Layout Changes the color of all nodes to reflect their Power Centrality score. Nodes having higher PC will have warmer color (i.e. red). Change the color of all nodes to reflect their Information Centrality. Information Centrality (IC) Node Color Layout Changes the color of all nodes to reflect their Information Centrality score. Nodes having higher IC will have warmer color (i.e. red). Change the color of all nodes to reflect their Eigenvector Centrality. Eigenvector Centrality (EVC) Node Color Layout Changes the color of all nodes to reflect their Eigenvector Centrality score. Nodes having higher EVC will have warmer color (i.e. red). Change the color of all nodes to reflect their Degree Prestige. Degree Prestige (DP) Node Color Layout Changes the color of all nodes to reflect their Degree Prestige score. Nodes having higher DP will have warmer color (i.e. red). Change the color of all nodes to reflect their PageRank Prestige. PageRank Prestige (PRP) Node Color Layout Changes the color of all nodes to reflect their PageRank Prestige score. Nodes having higher PRP will have warmer color (i.e. red). Change the color of all nodes to reflect their Proximity Prestige. Proximity Prestige (PP) Node Color Layout Changes the color of all nodes to reflect their PageRank Prestige score. Nodes of higher PP will have warmer color (i.e. red). Spring Embedder (Eades) Layout Eades Spring-Gravitational model. Spring Embedder Layout The Spring Embedder model (Eades, 1984), part of the Force Directed Placement (FDP) family, embeds a mechanical system in the graph by replacing nodes with rings and edges with springs. In our implementation, nodes are replaced by physical bodies (i.e. electrons) which exert repelling forces to each other, while edges are replaced by springs which exert attractive forces to the adjacent nodes. The nodes are placed in some initial layout and let go so that the spring forces move the system to a minimal energy state. The algorithm continues until the system retains an equilibrium state in which all forces cancel each other. Kamada-Kawai Embeds the Kamada-Kawai FDP layout model, the best variant of the Spring Embedder family of models. <p><em>Kamada-Kawai</em></p><p>The best variant of the Spring Embedder family of models. <p>In this the graph is considered to be a dynamic system where every edge is between two actors is a 'spring' of a desirable length, which corresponds to their graph theoretic distance. </p><p>In this way, the optimal layout of the graph is the state with the minimum imbalance. The degree of imbalance is formulated as the total spring energy: the square summation of the differences between desirable distances and real ones for all pairs of vertices.</p> Layout GuideLines Toggles layout guidelines on or off. Layout Guidelines Layout Guidelines are circular or horizontal lines usually created when embedding prominence-based visualization models on the network. Disable this checkbox to hide guidelines Invert Adjacency Matrix Invert the adjacency matrix, if possible Invert Adjacency Matrix Inverts the adjacency matrix using linear algebra methods. Transpose Adjacency Matrix View the transpose of adjacency matrix Transpose Adjacency Matrix Computes and displays the adjacency matrix tranpose. Cocitation Matrix Compute the Cocitation matrix of this network. Cocitation Matrix Computes and displays the cocitation matrix of the network. The Cocitation matrix, C=A*A^T, is a NxN matrix where each element (i,j) is the number of actors that have outbound ties/links to both actors i and j. Degree Matrix Compute the Degree matrix of the network Degree Matrix Compute the Degree matrix of the network. Laplacian Matrix Compute the Laplacian matrix of the network Laplacian Matrix Compute the Laplacian matrix of the network. Reciprocity Compute the arc and dyad reciprocity of the network. Arc and Dyad Reciprocity The arc reciprocity of a network/graph is the fraction of reciprocated ties over all present ties of the graph. The dyad reciprocity of a network/graph is the fraction of actor pairs that have reciprocated ties over all connected pairs of actors. In a directed network, the arc reciprocity measures the proportion of directed edges that are bidirectional. If the reciprocity is 1, then the adjacency matrix is structurally symmetric. Likewise, in a directed network, the dyad reciprocity measures the proportion of connected actor dyads that have bidirectional ties between them. In an undirected graph, all edges are reciprocal. Thus the reciprocity of the graph is always 1. Reciprocity can be computed on undirected, directed, and weighted graphs. Symmetry Test Check whether the network is symmetric or not Symmetry Checks whether the network is symmetric or not. A network is symmetric when all edges are reciprocal, or, in mathematical language, when the adjacency matrix is symmetric. Geodesic Distance between 2 nodes Compute the length of the shortest path (geodesic distance) between 2 nodes. Distance Computes the geodesic distance between two nodes.In graph theory, the geodesic distance of two nodes is the length (number of edges) of the shortest path between them. Geodesic Distances Matrix Compute the matrix of geodesic distances between all pair of nodes. Distances Matrix Computes the matrix of distances between all pairs of actors/nodes in the social network.A distances matrix is a n x n matrix, in which the (i,j) element is the distance from node i to node jThe distance of two nodes is the length of the shortest path between them. Geodesics Matrix Compute the number of shortest paths (geodesics) between each pair of nodes Geodesics Matrix Displays a n x n matrix, where the (i,j) element is the number of shortest paths (geodesics) between node i and node j. Graph Diameter Compute the diameter of the network, the maximum geodesic distance between any actors. Diameter The diameter of a network is the maximum geodesic distance (maximum shortest path length) between any two nodes of the network. Average Distance Compute the average graph distance for all possible pairs of nodes. Average Graph Distance This is the average length of shortest paths (geodesics) for all possible pairs of nodes. It is a measure of the efficiency or compactness of the network. Compute the Eccentricity of each actor and group Eccentricity Eccentricity The eccentricity of each node i in a network or graph is the largest geodesic distance between node i and any other node j. Therefore, it reflects how far, at most, is each node from every other node. The maximum eccentricity is the graph diameter while the minimum is the graph radius. This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1. Connectedness Check whether the network is a connected graph, a connected digraph or a disconnected graph/digraph... Connectedness In graph theory, a graph is <b>connected</b> if there is a path between every pair of nodes. A digraph is <b>strongly connected</b> if there the a path from i to j and from j to i for all pairs (i,j). A digraph is weakly connected if at least a pair of nodes are joined by a semipath. A digraph or a graph is disconnected if at least one node is isolate. Walks of a given length Compute the number of walks of a given length between any nodes. Walks of a given length A walk is a sequence of alternating vertices and edges such as v<sub>0</sub>e<sub>1</sub>, v<sub>1</sub>e<sub>2</sub>, v<sub>2</sub>e<sub>3</sub>, …, e<sub>k</sub>v<sub>k</sub>, where each edge, e<sub>i</sub> is defined as e<sub>i</sub> = {v<sub>i-1</sub>, v<sub>i</sub>}. This function counts the number of walks of a given length between each pair of nodes, by studying the powers of the sociomatrix. Total Walks Calculate the total number of walks of every possible length between all nodes Total Walks A walk is a sequence of alternating vertices and edges such as v<sub>0</sub>e<sub>1</sub>, v<sub>1</sub>e<sub>2</sub>, v<sub>2</sub>e<sub>3</sub>, …, e<sub>k</sub>v<sub>k</sub>, where each edge, e<sub>i</sub> is defined as e<sub>i</sub> = {v<sub>i-1</sub>, v<sub>i</sub>}. This function counts the number of walks of any length between each pair of nodes, by studying the powers of the sociomatrix. Reachability Matrix Compute the Reachability Matrix of the network. Reachability Matrix Calculates the reachability matrix X<sup>R</sup> of the graph where the {i,j} element is 1 if the vertices i and j are reachable. Actually, this just checks whether the corresponding element of Distances matrix is not zero. Local and Network Clustering Coefficient Compute the Watts & Strogatz Clustering Coefficient for every actor and the network average. Local and Network Clustering Coefficient The local Clustering Coefficient (Watts & Strogatz, 1998) of an actor quantifies how close the actor and her neighbors are to being a clique and can be used as an indication of network transitivity. Clique Census Compute the clique census: find all maximal connected subgraphs. Clique Census Produces the census of network cliques (maximal connected subgraphs), along with disaggregation by actor and co-membership information. Triad Census (M-A-N labeling) Calculate the triad census for all actors. Triad Census A triad census counts all the different kinds of observed triads within a network and codes them according to their number of mutual, asymmetric and non-existent dyads using the M-A-N labeling scheme. Pearson correlation coefficients Compute Pearson Correlation Coefficients between pairs of actors. Most useful with valued/weighted ties (non-binary). Pearson correlation coefficients Computes a correlation matrix, where the elements are the Pearson correlation coefficients between pairs of actors in terms of their tie profiles or distances (in, out or both). The Pearson product-moment correlation coefficient (PPMCC or PCC or Pearson's r)is a measure of the linear dependence/association between two variables X and Y. This correlation measure of similarity is particularly useful when ties are valued/weighted denoting strength, cost or probability. Note that in very sparse networks (very low density), measures such as"exact matches", "correlation" and "distance" will show little variation among the actors, causing difficulty in classifying the actors in structural equivalence classes. Similarity by measure (Exact, Jaccard, Hamming, Cosine, Euclidean) Compute a pair-wise actor similarity matrix based on a measure of their ties (or distances) "matches" . Actor Similarity by measure Computes a pair-wise actor similarity matrix, where each element (i,j) is the ratio of tie (or distance) matches of actors i and j to all other actors. SocNetV supports the following matching measures: Simple Matching (Exact Matches)Jaccard Index (Positive Matches or Co-citation)Hamming distanceCosine similarityEuclidean distanceFor instance, if you select Exact Matches, a matrix element (i,j) = 0.5, means that actors i and j have the same ties present or absent to other actors 50% of the time. These measures of similarity are particularly useful when ties are binary (not valued). Note that in very sparse networks (very low density), measures such as"exact matches", "correlation" and "distance" will show little variation among the actors, causing difficulty in classifying the actors in structural equivalence classes. Tie Profile Dissimilarities/Distances Compute tie profile dissimilarities/distances (Euclidean, Manhattan, Jaccard, Hamming) between all pair of nodes. Tie Profile Dissimilarities/Distances Computes a matrix of tie profile distances/dissimilarities between all pairs of actors/nodes in the social network using an ordinary metric such as Euclidean distance, Manhattan distance, Jaccard distance or Hamming distance).The resulted distance matrix is a n x n matrix, in which the (i,j) element is the distance or dissimilarity between the tie profiles of node i and node j. Hierarchical clustering Perform agglomerative cluster analysis of the actors in the social network Hierarchical clustering Hierarchical clustering (or hierarchical cluster analysis, HCA) is a method of cluster analysis which builds a hierarchy of clusters, based on their elements dissimilarity. In SNA context these clusters usually consist of network actors. This method takes the social network distance matrix as input and uses the Agglomerative "bottom up" approach where each actor starts in its own cluster (Level 0). In each subsequent Level, as we move up the clustering hierarchy, a pair of clusters are merged into a larger cluster, until all actors end up in the same cluster. To decide which clusters should be combined at each level, a measure of dissimilarity between sets of observations is required. This measure consists of a metric for the distance between actors (i.e. manhattan distance) and a linkage criterion (i.e. single-linkage clustering). This linkage criterion (essentially a definition of distance between clusters), differentiates between the different HCA methods.Note that the complexity of agglomerative clustering is O( n^2 log(n) ), therefore is too slow for large data sets. Degree Centrality (DC) Compute Degree Centrality indices for every actor and group Degree Centralization. Degree Centrality (DC) For each node v, the DC index is the number of edges attached to it (in undirected graphs) or the total number of arcs (outLinks) starting from it (in digraphs). This is often considered a measure of actor activity. This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs. In weighted relations, DC is the sum of weights of all edges/outLinks attached to v. Closeness Centrality (CC) Compute Closeness Centrality indices for every actor and group Closeness Centralization. Closeness Centrality (CC) For each node v, CC the inverse sum of the shortest distances between v and every other node. CC is interpreted as the ability to access information through the "grapevine" of network members. Nodes with high closeness centrality are those who can reach many other nodes in few steps. This index can be calculated in both graphs and digraphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1. Influence Range Closeness Centrality (IRCC) Compute Influence Range Closeness Centrality indices for every actor focusing on how proximate each one isto others in its influence range Influence Range Closeness Centrality (IRCC) For each node v, IRCC is the standardized inverse average distance between v and every reachable node. This improved CC index is optimized for graphs and directed graphs which are not strongly connected. Unlike the ordinary CC, which is the inverted sum of distances from node v to all others (thus undefined if a node is isolated or the digraph is not strongly connected), IRCC considers only distances from node v to nodes in its influence range J (nodes reachable from v). The IRCC formula used is the ratio of the fraction of nodes reachable by v (|J|/(n-1)) to the average distance of these nodes from v (sum(d(v,j))/|J| Betweenness Centrality (BC) Betweenness Centrality (BC) For each node v, BC is the ratio of all geodesics between pairs of nodes which run through v. It reflects how often an node lies on the geodesics between the other nodes of the network. It can be interpreted as a measure of control. A node which lies between many others is assumed to have a higher likelihood of being able to control information flow in the network. Note that betweenness centrality assumes that all geodesics have equal weight or are equally likely to be chosen for the flow of information between any two nodes. This is reasonable only on "regular" networks where all nodes have similar degrees. On networks with significant degree variance you might want to try informational centrality instead. This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1. Compute Betweenness Centrality indices and group Betweenness Centralization. Stress Centrality (SC) Compute Stress Centrality indices for every actor and group Stress Centralization. Stress Centrality (SC) For each node v, SC is the total number of geodesics between all other nodes which run through v. A node with high SC is considered 'stressed', since it is traversed by a high number of geodesics. When one node falls on all other geodesics between all the remaining (N-1) nodes, then we have a star graph with maximum Stress Centrality. This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1. Eccentricity Centrality (EC) Compute Eccentricity Centrality (aka Harary Graph Centrality) scores for each node. Eccentricity Centrality (EC) This index is also known as Harary Graph Centrality. For each node i, the EC is the inverse of the maximum geodesic distance of that v to all other nodes in the network. Nodes with high EC have short distances to all other nodes This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1. Gil and Schmidt Power Centrality (PC) Compute Power Centrality indices (aka Gil-Schmidt Power Centrality) for every actor and group Power Centralization Power Centrality (PC) For each node v, this index sums its degree (with weight 1), with the size of the 2nd-order neighbourhood (with weight 2), and in general, with the size of the kth order neighbourhood (with weight k). Thus, for each node in the network the most important other nodes are its immediate neighbours and then in decreasing importance the nodes of the 2nd-order neighbourhood, 3rd-order neighbourhood etc. For each node, the sum obtained is normalised by the total numbers of nodes in the same component minus 1. Power centrality has been devised by Gil-Schmidt. This index can be calculated in both graphs and digraphs but is usually best suited for undirected graphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1 (therefore not considered). Information Centrality (IC) Compute Information Centrality indices and group Information Centralization Information Centrality (IC) Information centrality counts all paths between nodes weighted by strength of tie and distance. This centrality measure developed by Stephenson and Zelen (1989) focuses on how information might flow through many different paths. This index should be calculated only for graphs. Note: To compute this index, SocNetV drops all isolated nodes. Eigenvector Centrality (EVC) Compute Eigenvector Centrality indices and group Eigenvector Centralization Eigenvector Centrality (EVC) Computes the Eigenvector centrality of each node in a social network which is defined as the ith element of the leading eigenvector of the adjacency matrix. The leading eigenvector is the eigenvector corresponding to the largest positive eigenvalue.The Eigenvector Centrality, proposed by Bonacich (1989), is an extension of the simpler Degree Centrality because it gives each actor a score proportional to the scores of its neighbors. Thus, a node may be important, in terms of its EC, because it has lots of ties or it has fewer ties to important other nodes. Degree Prestige (DP) Compute Degree Prestige (InDegree) indices InDegree (Degree Prestige) For each node k, this the number of arcs ending at k. Nodes with higher in-degree are considered more prominent among others. In directed graphs, this index measures the prestige of each node/actor. Thus it is called Degree Prestige. Nodes who are prestigious tend to receive many nominations or choices (in-links). The largest the index is, the more prestigious is the node. This index can be calculated only for digraphs. In weighted relations, DP is the sum of weights of all arcs/inLinks ending at node v. PageRank Prestige (PRP) Compute PageRank Prestige indices for every actor PageRank Prestige An importance ranking for each node based on the link structure of the network. PageRank, developed by Page and Brin (1997), focuses on how nodes are connected to each other, treating each edge from a node as a citation/backlink/vote to another. In essence, for each node PageRank counts all backlinks to it, but it does so by not counting all edges equally while it normalizes each edge from a node by the total number of edges from it. PageRank is calculated iteratively and it corresponds to the principal eigenvector of the normalized link matrix. This index can be calculated in both graphs and digraphs but is usually best suited for directed graphs since it is a prestige measure. It can also be calculated in weighted graphs. In weighted relations, each backlink to a node v from another node u is considered to have weight=1 but it is normalized by the sum of outLinks weights (outDegree) of u. Therefore, nodes with high outLink weights give smaller percentage of their PR to node v. Proximity Prestige (PP) Calculate and display Proximity Prestige (digraphs only) Proximity Prestige (PP) This index measures how proximate a node v is to the nodes in its influence domain I (the influence domain I of a node is the number of other nodes that can reach it). In PP calculation, proximity is based on distances to rather than distances from node v. To put it simply, in PP what matters is how close are all the other nodes to node v. The algorithm takes the average distance to node v of all nodes in its influence domain, standardizes it by multiplying with (N-1)/I and takes its reciprocal. In essence, the formula SocNetV uses to calculate PP is the ratio of the fraction of nodes that can reach node v, to the average distance of that nodes to v: PP = (I/(N-1))/(sum{d(u,v)}/I) where the sum is over all nodes in I. Display Node Numbers Toggle displaying of node numbers (this session only) Display Node Numbers Enables or disables displaying of node numbers This setting will apply to this session only. To permanently change it, go to Settings. Display Numbers Inside Nodes Toggle displaying of numbers inside nodes (this session only) Display Numbers Inside Nodes Enables or disables displaying node numbers inside nodes. This setting will apply to this session only. To permanently change it, go to Settings. Display Node Labels Toggle displaying of node labels (this session only) Display Node Labels Enables or disables node labels. This setting will apply to this session only. To permanently change it, go to Settings. Display Edges Toggle displaying edges (this session only) Display Edges Enables or disables displaying of edgesThis setting will apply to this session only. To permanently change it, go to Settings. Display Edge Weights Toggle displaying of numbers of edge weights (this session only) Display Edge Weights Enables or disables displaying edge weight numbers. This setting will apply to this session only. To permanently change it, go to Settings. Consider Edge Weights in Calculations Toggle considering edge weights during calculations (i.e. distances, centrality, etc) (this session only) Consider Edge Weights in Calculations Enables or disables considering edge weights during calculations (i.e. distances, centrality, etc). This setting will apply to this session only. To permanently change it, go to Settings. Display Edge Labels Toggle displaying of Edge labels, if any (this session only) Display Edge Labes Enables or disables displaying edge labels. This setting will apply to this session only. To permanently change it, go to Settings. Display Edge Arrows Toggle displaying directional Arrows on edges (this session only) Display edge Arrows Enables or disables displaying of arrows on edges. Useful if all links are reciprocal (undirected graph). This setting will apply to this session only. To permanently change it, go to Settings. Edge Thickness reflects Weight Draw edges as thick as their weights (if specified) Edge thickness reflects weight Click to toggle having all edges as thick as their weight (if specified) Draw Edges as Bezier curves Edges Bezier Enable or disables drawing Edges as Bezier curves.This setting will apply to this session only. To permanently change it, go to Settings. Change the canvasbackground color Background Color Changes the background color of the canvas Background Image (this session) Select and display a custom image in the background(for this session only) Background image Enable to select an image file from your computer, which will be displayed in the background instead of plain color.This setting will apply to this session only. To permanently change it, go to Settings. Full screen (this session) Toggle full screen mode (for this session only) Full Screen Mode Enable to show application window in full screen mode. This setting will apply to this session only. To permanently change it, go to Settings. Settings Open the Settings dialog where you can save your preferences for all future sessions Open the Settings dialog to save your preferences for all future sessions Settings Opens the Settings dialog where you can edit and save settings permanently for all subsequent sessions. Check for Updates Open a browser to SocNetV website to check for a new version... Check Updates Open a browser to SocNetV website so that you can check yourself for updates System Information Show information about your system <p><b>System Information</b></p><p>Shows useful information about your system, which you can include in your bug reports. </p> Recent &files... &Import ... Create &Random Network... Export to other... Nodes... Edges... &Analyze Adjacency Matrix and Matrices... Cohesion... Centrality and Prestige indices... Communities and Subgroups... Structural Equivalence... Random... Radial by prominence index... On Levels by prominence index... Node Size by prominence index... Node Color by prominence index... Force-Directed Placement... &Canvas... <p><b>Current relation<b></p><p>To rename the current relation, click here, enter new name and press Enter.</p> Name of the current relation. To rename it, enter a new name and press Enter. To select another relation, click the Down arrow (on the right). <p><b>Relations combo</b></p><p>This displays the currently selected relation of the network. </p><p>To rename the current relation, click on the name, enter a new name and press Enter. </p><p>To select another relation (if any), click the Down arrow (on the right).</p> Auto Create: Create a network automatically (famous, random, or by using the web crawler). <p><b>Auto network creation</b></p> <p>Create a new network automatically.</p><p>You may create a random network, recreate famous data-sets or use the built-in web crawler to create a network of webpages. </p> Subgraph: Create a basic subgraph with selected nodes. <p><b>Subgraph creation</b></p> <p>Create a basic subgraph from selected nodes.</p><p>Select some nodes with your mouse and then click on one of theseoptions to create a basic subgraph with them. </p><p>You can create a star, clique, line, etc subgraph.</p><p>There must be some nodes selected!</p> Edge Mode: Select the edge mode: directed or undirected. <p><b>Edge mode</b></p><p>In social networks and graphs, edges can be directed or undirected (and the corresponding network is called directed or undirected as well).</p><p>This option lets you choose what the kind of edges you want in your network.<p><p>By selecting an option here, all edges of the network will change automatically. <p><p>For instance, if the network is directed and and you select "undirected" then all the directed edges will become undirected <p> Transform: Select a method to transform the network, i.e. transform all directed edges to undirected. <p><b>Transform Network Edges </b></p><p>Select a method to transform network edges. Available methods: </p><p><em>Symmetrize All Edges</em></p><p>Forces all edges in this relation to be reciprocated: <p>If there is a directed edge from node A to node B then a new directed edge from node B to node A will be created, with the same weight. </p><p>The result is a symmetric network.</p><p><em>Symmetrize Edges by Strong Ties:</em></p><p>Creates a new symmetric relation by keeping strong ties only. </p><p>A tie between actors A and B is considered strong if both A -> B and B -> A exist. Therefore, in the new relation, a reciprocated edge will be created between actors A and B only if both arcs A->B and B->A were present in the current or all relations. </p><p>If the network is multi-relational, it will ask you whether ties in the current relation or all relations are to be considered.</p><p><em>Symmetrize Edges by examining Cocitation:</em></p><p>Creates a new symmetric relation by connecting actors that are cocitated by others. In the new relation, an edge will be created between actor i and actor j only if C(i,j) > 0, where C the Cocitation Matrix. </p><p>Thus the actor pairs cited by more common neighbors will appear with a stronger tie between them than pairs those cited by fewer common neighbors. The resulting relation is symmetric.</p><p><em>Dichotomize Edges</em></p><p>Creates a new binary relation in a valued network using edge dichotomization according to a given threshold value. In the new dichotomized relation, an edge will exist between actor i and actor j only if e(i,j) > threshold, where threshold is a user-defined value.The process is also known as compression and slicing.</p> Matrix: Select which matrix to compute and display, based on the adjacency matrix of the current network. <p><b>Matrix Analysis</b></p><p>Compute and display the adjacency matrix and other matrices based on the adjacency matrix of the current network. Available options:<p><em>Adjacency Matrix</em></p><p><em>Adjacency Matrix Plot</em></p><p><em>Inverse of Adjacency Matrix</em></p><p><em>Transpose of Adjacency Matrix</em></p><p><em>Cocitation Matrix </em></p><p><em>Degree Matrix </em></p><p><em>Laplacian Matrix </em></p> Cohesion: Select a graph-theoretic measure, i.e. distances, walks, graph diameter, eccentricity. <p><b>Analyze Cohesion</b></p><p><Compute basic graph-theoretic measures. <p><em>Reciprocity:</em><p><p>Measures the likelihood that pairs of nodes in a directed network are mutually linked.</p><p><em>Symmetry:</em><p><p>Checks if the directed network is symmetric or not.<p><p><em>Distances:</em></p><p>Computes the matrix of geodesic distances between all pairs of nodes.<p><p><em>Average Distance:</em></p><p>Computes the average distance between all nodes.<p><p><em>Graph Diameter:</em></p><p>The maximum distance between any two nodes in the network.</p><p><em>Walks:</em></p><p>A walk is a sequence of edges and vertices (nodes), where each edge's endpoints are the two vertices adjacent to it. In a walk, vertices and edges may repeat.<p><em>Eccentricity:</em></p><p>The Eccentricity of each node is how far, at most, is from every other actor in the network.</p><p><em>Reachability:</em></p><p>Creates a matrix where an element (i,j) = 1 only if the actors i and j are reachable.</p><p><em>Clustering Coefficient (CLC):</em></p><p>The CLC score of each node is the proportion of actual links between its neighbors divided by the number of links that could possibly exist between them. Quantifies how close each actor and its neighbors are to form a complete subgraph (clique)</p> Prominence: Select a prominence metric to compute for each actor and the whole network. <p><b>Prominence Analysis</b></p><p>Compute Centrality and Prestige indices, to measure how <em>prominent</em> (important) each actor (node) is inside the network. </p><p>Centrality measures quantify how central is each node by examining its ties and its geodesic distances (shortest path lengths) to other nodes. Most Centrality indices were designed for undirected graphs. </p><p>Prestige indices focus on "choices received" to a node. These indices measure the nominations or ties to each node from all others (or inLinks). Prestige indices are suitable (and can be calculated only) on directed graphs.</p><p>Available measures:</p><p><em>Degree Centrality (DC) </em></p><p>The sum of outbound edges or the sum of weights of outbound edges from each node <em>i</em> to all adjacent nodes. Note: This is the outDegree Centrality. To compute inDegree Centrality, use the Degree Prestige measure.</p><p><em>Closeness Centrality (CC):</em></p>The inverted sum of geodesic distances from each node <em>u</em> to all other nodes. <p><em>IR Closeness Centrality (IRCC):</em></p><p>The ratio of the fraction of nodes reachable by each node <em>u</em> to the average distance of these nodes from <em>u</em>.</p><p><em>Betweenness Centrality (BC):</em></p><p>The sum of delta<sub>(s,t,u)</sub> for all s,t ∈ V where delta<sub>(s,t,u)</sub> is the ratio of all geodesics between nodes <em>s</em> and <em>t</em> which run through node <em>u</em>.</p> <p><em>Stress Centrality (SC):</em></p><p>The sum of sigma<sub>(s,t,u)</sub> for all s,t ∈ V where sigma<sub>(s,t,u)</sub> is the number of geodesics between nodes <em>s</em> and <em>t</em> which run through node <em>u</em>.</p> <p><em>Eccentricity Centrality (EC):</em></p><p>Also known as Harary Graph Centrality. The inverse maximum geodesic distance from node <em>u</em> to all other nodes in the network.<p><em>Power Centrality (PC):</em></p><p>The sum of the sizes of all N<sub>th</sub>-order neighbourhoods of node <em>u</em> with weight 1/n.</p><p><em>Information Centrality (IC):</em></p><p>Measures the information flow through all paths between actors weighted by strength of tie and distance.</p><p><em>Eigenvector Centrality (EVC):</em></p><p>The EVC score of each node <em>i</em> is the i<sub>th</sub> element of the leading eigenvector of the adjacency matrix, that is the eigenvector corresponding to the largest positive eigenvalue. <p><em>Degree Prestige (DP):</em></p><p>Also known as InDegree Centrality, it is the sum of inbound edges to a node <em>u</em> from all adjacent nodes. </p><p><em>PageRank Prestige (PRP):</em></p><p>For each node <em>u</em> counts all inbound links (edges) to it, but it normalizes each inbound link from another node <em>v</em> by the outDegree of <em>v</em>. </p><p><em>Proximity Prestige (PP):</em></p><p>The ratio of the proportion of nodes who can reach each node <em>u</em> to the average distance these nodes are from it. Similar to Closeness Centrality but it counts only inbound distances to each actor, thus it is a measure of actor prestige.</p> Communities: Select a community detection measure / cohesive subgroup algorithm, i.e. cliques, triad census etc. <p><b>Community Analysis</b></p><p>Community detection measures and cohesive subgroup algorithms, to identify meaningful subgraphs in the graph.</p><p><b>Available measures</b></p><p><em>Clique Census:</em><p><p>Computes aggregate counts of all maximal cliques of actors by size, actor by clique analysis, clique co-memberships</p><p><em>Triad Census:</em><p><p>Computes the Holland, Leinhardt and Davis triad census, which counts all different classes of triads coded according to theirnumber of Mutual, Asymmetric and Non-existest dyads (M-A-N scheme)</p> Equivalence: Select a method to measure structural equivalence, i.e. Pearson Coefficients, tie profile similarities, hierarchical clustering, etc. <p><b>Structural Equivalence Analysis</b></p><p>Select one of the available structural equivalence measures and visualization algorithms. <p><p>Available options</p><p><em>Pearson Coefficients<.em></p><p><em>Tie profile similarities</em></p><p><em>Dissimilarities</em></p><p><em>Hierarchical Clustering Analysis</em></p> Analyze Index: Select a prominence-based layout model <p><b>Visualize by prominence index</b></p><p>Apply a prominence-based layout model to the network.</p><p>For instance, you can apply a degree centrality layout. </p><p>Note: For each prominence index, you must select a layout type (below).</p><p>Available measures:</p><p><em>Degree Centrality (DC) </em></p><p>The sum of outbound edges or the sum of weights of outbound edges from each node <em>i</em> to all adjacent nodes. Note: This is the outDegree Centrality. To compute inDegree Centrality, use the Degree Prestige measure.</p><p><em>Closeness Centrality (CC):</em></p>The inverted sum of geodesic distances from each node <em>u</em> to all other nodes. <p><em>IR Closeness Centrality (IRCC):</em></p><p>The ratio of the fraction of nodes reachable by each node <em>u</em> to the average distance of these nodes from <em>u</em>.</p><p><em>Betweenness Centrality (BC):</em></p><p>The sum of delta<sub>(s,t,u)</sub> for all s,t ∈ V where delta<sub>(s,t,u)</sub> is the ratio of all geodesics between nodes <em>s</em> and <em>t</em> which run through node <em>u</em>.</p> <p><em>Stress Centrality (SC):</em></p><p>The sum of sigma<sub>(s,t,u)</sub> for all s,t ∈ V where sigma<sub>(s,t,u)</sub> is the number of geodesics between nodes <em>s</em> and <em>t</em> which run through node <em>u</em>.</p> <p><em>Eccentricity Centrality (EC):</em></p><p>Also known as Harary Graph Centrality. The inverse maximum geodesic distance from node <em>u</em> to all other nodes in the network.<p><em>Power Centrality (PC):</em></p><p>The sum of the sizes of all N<sub>th</sub>-order neighbourhoods of node <em>u</em> with weight 1/n.</p><p><em>Information Centrality (IC):</em></p><p>Measures the information flow through all paths between actors weighted by strength of tie and distance.</p><p><em>Eigenvector Centrality (EVC):</em></p><p>The EVC score of each node <em>i</em> is the i<sub>th</sub> element of the leading eigenvector of the adjacency matrix, that is the eigenvector corresponding to the largest positive eigenvalue. <p><em>Degree Prestige (DP):</em></p><p>Also known as InDegree Centrality, it is the sum of inbound edges to a node <em>u</em> from all adjacent nodes. </p><p><em>PageRank Prestige (PRP):</em></p><p>For each node <em>u</em> counts all inbound links (edges) to it, but it normalizes each inbound link from another node <em>v</em> by the outDegree of <em>v</em>. </p><p><em>Proximity Prestige (PP):</em></p><p>The ratio of the proportion of nodes who can reach each node <em>u</em> to the average distance these nodes are from it. Similar to Closeness Centrality but it counts only inbound distances to each actor, thus it is a measure of actor prestige.</p> Type: Select layout type for the selected model <p><b>Layout Type</b></p></p>Select a layout type (radial, level, node size or node color) for the selected prominence-based model you want to apply to the network. Please note that node coloring works only for basic shapes (box, circle, etc) not for image icons.</p> Apply By Prominence Index <p><b>Visualize by prominence index</b/></p><p>Apply a prominence-based layout model to the network. </p><p>For instance, you can apply a Degree Centrality layout. </p><p>For each prominence index, you must select a layout type:</p><p>Radial, Levels, NodeSize or NodeColor.</p><p>Please note that node coloring works only for basic shapes (box, circle, etc) not for image icons.</p> Model: None Eades Spring Embedder Select a Force-Directed layout model. <p><b>Visualize by a Force-Directed Placement layout model.</b></p> <p>Available models: </p><p><em>Kamada-Kawai</em></p><p>The best variant of the Spring Embedder family of models. <p>In this the graph is considered to be a dynamic system where every edge is between two actors is a 'spring' of a desirable length, which corresponds to their graph theoretic distance. </p><p>In this way, the optimal layout of the graph is the state with the minimum imbalance. The degree of imbalance is formulated as the total spring energy: the square summation of the differences between desirable distances and real ones for all pairs of vertices.</p><p><em>Fruchterman-Reingold:</em></p><p>In this model, the vertices behave as atomic particles or celestial bodies, exerting attractive and repulsive forces to each other. Again, only vertices that are neighbours attract each other but, unlike Eades Spring Embedder, all vertices repel each other.</p><p><em>Eades Spring Embedder:</em></p><p>A spring-gravitational model, where each node is regarded as physical object (ring) repelling all other non-adjacent nodes, while springs between connected nodes attract them.</p> By Force-Directed Model Control Panel The type of the network: directed or undirected. Toggle the menu option Edit->Edges->Undirected Edges to change it The loaded network, if any, is directed and any link you add between nodes will be a directed arc. If you want to work with undirected edges and/or transform the loaded network (if any) to undirected toggle the option Edit->Edges->Undirected or press CTRL+E+U Directed Directed data mode. Toggle the menu option Edit->Edges->Undirected Edges to change it The loaded network, if any, is directed and any link you add between nodes will be a directed arc. If you want to work with undirected edges and/or transform the loaded network (if any) to undirected toggle the option Edit->Edges->Undirected. The loaded network, if any, is directed and any link you add between nodes will be a directed arc. If you want to work with undirected edges and/or transform the loaded network (if any) to undirected toggle the option Edit->Edges->Undirected Nodes: Each actor in a social netwok is visualized as a node (aka vertex). <p><b>Nodes</b></p><p>Each actor in a social netwok is visualized as a node (aka vertex) in a graph. This is total number of actors (aka nodes or vertices) in this social network.</p> The total number of actors (aka nodes or vertices) in the social network. This is the total number of actors (aka nodes or vertices) in the social network. Arcs: Each link between a pair of actors in a social network is visualized as an edge or arc. <p><b>Edges</b></p>Each link between a pair of actors in a social network is visualized as an undirected edge or a directed edge (aka arc). The total number of directed edges in the social network. This is the total number of directed edges (links between actors) in the social network. Density: The density d is the ratio of existing edges to all possible edges <p><b>Density</b></p><p>The density <em>d</em> of a social network is the ratio of existing edges to all possible edges ( n*(n-1) ) between the nodes of the network</p>. The network density, the ratio of existing edges to all possible edges ( n*(n-1) ) between nodes. <p>This is the density of the network. <p>The density of a network is the ratio of existing edges to all possible edges ( n*(n-1) ) between nodes.</p> Selection Selected nodes. The number of selected nodes (vertices). Selected edges. The number of selected edges. Clicked Node Number: The node number of the last clicked node. The node number of the last clicked node. Zero means no node clicked. This is the node number of the last clicked node. Becomes zero when you click on something other than a node. The node number of the last clicked node. Zero if you clicked something else. In-Degree: The inDegree of a node is the sum of all inbound edge weights. The sum of all inbound edge weights of the last clicked node. Zero if you clicked something else. This is the sum of all inbound edge weights of last clicked node. Becomes zero when you click on something other than a node. Out-Degree: The outDegree of a node is the sum of all outbound edge weights. The sum of all outbound edge weights of the last clicked node. Zero if you clicked something else. This is the sum of all outbound edge weights of the last clicked node. Becomes zero when you click on something other than a node. Clicked Edge Name: The name of the last clicked edge. This is the name of the last clicked edge. Becomes zero when you click on somethingto other than an edge The name of the last clicked edge.Zero when you click on something else. Weight: The weight of the clicked edge. This is the weight of the last clicked edge. Becomes zero when you click on something other than an edge The weight of the last clicked edge. Zero when you click on something else. The weight of the reciprocal edge. This is the reciprocal weight of the last clicked reciprocated edge. Becomes zero when you click on something other than an edge The reciprocal weight of the last clicked reciprocated edge. Becomes zero when you click on something other than an edge Statistics Panel Zoom in the network. Zoom in the network. Or press Cltr and use mouse wheel. Zoom In. Zooms in the network (Ctrl++).You can also press Cltr and use the mouse wheel. Zoom out. Zoom out of the actual network. Or press Cltr and use mouse wheel. Zoom out. Zooms out of the actual network. (Ctrl+-)You can also press Cltr and use the mouse wheel. Zoom slider: Drag up to zoom in. Drag down to zoom out. Rotates the canvas counterclockwise Rotates the canvas counterclockwise. Rotates the canvas clockwise. Rotate slider: Drag to left to rotate clockwise. Drag to right to rotate counterclockwise. Rotate slider: Drag to left to rotate clockwise. Drag to right to rotate counterclockwise. Reset Reset zoom and rotation to zero (or press Ctrl+0) Reset zoom and rotation to zero (Ctrl+0) Application initialization. Please wait... BackgroundImage on. &%1 %2 Useful information Error Nothing to do! Load or create a social network first No network! Load social network data or create a new social network first. Nothing to do! Load social network data or create edges first No edges! Load social network data or create some edges first. GraphML (*.graphml *.xml);;All (*) Pajek (*.net *.paj *.pajek);;All (*) Adjacency (*.csv *.sm *.adj *.txt);;All (*) GraphViz (*.dot);;All (*) UCINET (*.dl *.dat);;All (*) GML (*.gml);;All (*) Weighted Edge List (*.txt *.list *.edgelist *.lst *.wlst);;All (*) Simple Edge List (*.txt *.list *.edgelist *.lst);;All (*) Two-Mode Sociomatrix (*.2sm *.aff);;All (*) GraphML (*.graphml *.xml);;GML (*.gml *.xml);;Pajek (*.net *.pajek *.paj);;UCINET (*.dl *.dat);;Adjacency (*.csv *.adj *.sm *.txt);;GraphViz (*.dot);;Weighted Edge List (*.txt *.edgelist *.list *.lst *.wlst);;Simple Edge List (*.txt *.edgelist *.list *.lst);;Two-Mode Sociomatrix (*.2sm *.aff);;All (*) GraphML GML UCINET Adjacency GraphViz Edge List (weighted) Edge List (simple, non-weighted) Two-mode sociomatrix Selected file has ambiguous file extension! You selected: %1 The name of this file has either an unknown extension or an extension used by different network file formats. SocNetV supports the following social network file formats. In parentheses the expected extension. - GraphML (.graphml or .xml) - GML (.gml or .xml) - Pajek (.paj or .pajek or .net) - UCINET (.dl .dat) - GraphViz (.dot) - Adjacency Matrix (.csv, .txt, .sm or .adj) - Simple Edge List (.list or .lst) - Weighted Edge List (.wlist or .wlst) - Two-Mode / affiliation (.2sm or .aff) If you are sure the file is of a supported format, please select the right format from the list below. Opening network file aborted. Saving file... Select type... Select type of edge list format SocNetV can parse two kinds of edgelist formats: A. Edge lists with edge weights, where each line has exactly 3 columns: source target weight, i.e.: 1 2 1 2 3 1 3 4 2 4 5 1 B. Simple edge lists without weights, where each line has two or more columns in the form: source, target1, target2, ... , i.e.: 1 2 3 4 5 6 2 3 4 3 5 8 7 Please select the appropriate type of edge list format of the file you want to load: Weighted Simple non-weighted No file selected. Cannot read file %1: %2 Opt for labels Node labels? This file contains an adjacency matrix (sociomatrix). Please specify whether there are node labels defined on the first (comment) line. Column delimiter in file SocNetV supports edge list and adjacency files with arbitrary column delimiters. The default delimiter is one or more spaces. If the column delimiter in this file is other than simple space or TAB, please enter it below. For instance, if the delimiter is a comma or pipe enter "," or "|" respectively. Leave empty to use space or TAB as delimiter. Error loading file Error loading network file Sorry, the selected file is not in a supported format or encoding, or contains formatting errors. The error message was: %1 What now? Review the message above to see if it helps you to fix the data file. Try a different codec in the preview window or if the file is of a legacy format (i.e. Pajek, UCINET, GraphViz, etc), please use the options in the Import sub menu. Unrecognized format. Please specify the file-format using the Import Menu. %1 formatted network, named '%2', loaded. Nodes: %3, Edges: %4, Density: %5. Elapsed time: %6 ms Add new relation Enter a name for a new relation between the actors. A relation is a collection of ties of a specific kind between the network actors. For instance, enter "friendship" if the edges of this relation refer to the set of friendships between pairs of actors. Enter a name for the new relation (or press Cancel): Error. Relation name is used! The relation name is already used. Please use another relation name that is not already used. Error. No relation name entered! You did not type a name for this new relation New relation cancelled. New relation named %1, added. Added a new relation named: %1. Enter a new name for this relation. Not a valid name. You did not enter a valid name for this relation. Opening Image export dialog. No filename. Exporting to Image aborted. Network exported to image file. Image filename: %1 Error exporting to image file! Error while exporting network to image file: Opening PDF export dialog. No filename. Exporting to PDF aborted. Network exported to PDF file. PDF filename: %1 Exporting active network under new filename... Export Network to File Named... Pajek (*.paj *.net *.pajek);;All (*) Missing file extension. I will use .paj instead. Missing file extension. I will use the .paj extension. Appending an extension .paj to the given filename... Adjacency (*.csv *.txt *.adj *.sm *.net);;All (*) Missing file extension. I will use .csv instead. Missing file extension. I will use the .csv extension. Appending an extension .csv to the given filename... Weighted graph. Social network with valued/weighted edges Social network with valued/weighted edges This social network includes valued/weighted edges (the depicted graph is weighted). Do you want to save the edge weights in the adjacency file? Select Yes if you want to save edge values in the resulting file. Select No, if you don't want edge values to be saved. In the later case, all non-zero values will be truncated to 1. Displaying network data file %1 New network not saved yet. You might want to save it first. This new network you created has not been saved yet. Do you want to open a file dialog to save your work (then I will display the file)? Current network has been modified. Save to the original file? Current social network has been modified since last save. Do you want to save it to the original file? New Network File Enter your network data here Creating and writing adjacency matrix Adjacency matrix saved as Creating plot of adjacency matrix of %1 nodes. Very large network to plot! Warning: Really large network To plot a %1 x %1 matrix arranged in HTML table, I will need time to write a very large .html file , circa %2 MB in size. Instead, I can create a simpler / smaller HTML file without table. Press Yes to continue with simpler version, Press No to create large file with HTML table. Visual form of adjacency matrix saved as Generate a random Erdos-Renyi network. Creating new Erdos-Renyi random network. Please wait... ErdΕ‘s–RΓ©nyi network creation cancelled or did not finish. ErdΕ‘s–RΓ©nyi random network created. Random network created. A new random network has been created according to the ErdΕ‘s–RΓ©nyi model. On average, edges should be %1. This graph is almost surely connected because: probability > ln(n) that is: %2 < %3 On average, edges should be %1. This graph is almost surely not connected because: probability < ln(n) that is: %2 < %3 Generate a random Scale-Free network. Scale-free network creation cancelled or did not finish. Scale-free random network created. Random network created. A new scale-free random network with %1 nodes has been created according to the BarabΓ‘si–Albert model. A scale-free network is a network whose degree distribution follows a power law. Generate a random Small-World network. Small-world network creation cancelled or did not finish. Small-World random network created. Random network created. A new random network with %1 nodes has been created according to the Watts & Strogatz model. A small-world network has short average path lengths and high clustering coefficient. Generate a d-regular random network. d-regular network creation cancelled or did not finish. d-regular network created. Random network created. A new d-regular random network with %1 nodes has been created. Each node has the same number <em>%1</em> of neighbours, aka the same degree d. Create ring lattice This will create a ring lattice network, where each node has degree d: d/2 edges to the right and d/2 to the left. Please enter the number of nodes you want: Create ring lattice... Now, enter an even number d. This is the total number of edges each new node will have: Error. Cannot create such network. Error. Cannot create such network! The degree %1 is not an even number. A ring lattice is a graph with N vertices each connected to d neighbors, d / 2 on each side. Please try again entering an even number as degree. Ring lattice random network created. Random network created. A new ring-lattice random network with %1 nodes has been created. A ring lattice is a graph with N vertices each connected to d neighbors, d / 2 on each side. Generate a lattice network. Lattice network creation cancelled or did not finish. Lattice random network created. Random network created. A new lattice random network with %1 nodes has been created. A lattice is a network whose drawing forms a regular tiling. Lattices are also known as meshes or grids. No SSL support. I cannot verify that your computer Operating System has OpenSSL support. OpenSSL is an Open Source software library for the Transport Layer Security (TLS) protocol (aka SSL), for applications that secure communications over computer networks. It is widely used by Internet servers, including the majority of HTTPS websites. Without OpenSSL libraries installed in your computer, I cannot crawl webpages/URLs using https:// So, please download and install OpenSSL in your OS and try again. Hint: Go to Help > System Information to see which OpenSSL version you need to install. Shows the total number of undirected edges in the network. The total number of undirected edges in the network. Undirected data mode. Toggle the menu option Edit->Edges->Undirected Edges to change it The loaded network, if any, is undirected and any edge you add between nodes will be undirected. If you want to work with directed edges and/or transform the loaded network (if any) to directed disable the option Edit->Edges->Undirected or press CTRL+E+U The loaded network, if any, is undirected and any edge you add between nodes will be undirected. If you want to work with directed edges and/or transform the loaded network (if any) to directed disable the option Edit->Edges->Undirected Edges: Shows the total number of directed edges in the network. The total number of directed edges in the network. The loaded network, if any, is directed and any link you add between nodes will be a directed arc. If you want to work with undirected edges and/or transform the loaded network (if any) to undirected enable the option Edit->Edges->Undirected Remove nodes node Selected nodes: %1 Selection cleared New random positioned node (numbered %1) added. Node find dialog opened. Enter your choices. Error. Cannot remove node! Error. Cannot remove this node! This a network with more than 1 relations. If you remove a node from the active relation, and then ask me to go to the previous or the next relation, then I would crash because I would try to display edges from a deleted node.You cannot remove nodes in multirelational networks. Removed %1 nodes. Remove node Node removed completely. Choose a node between ( Updated the properties of node %1. Updated the properties of %1 nodes. Error. Not enough nodes selected. Cannot create new clique because you have not selected enough nodes. Select at least three nodes first. Clique created. A new clique has been created from Not enough nodes selected. Cannot create new star subgraph because you have not selected enough nodes. To create a star subgraph from selected nodes, enter the number of the central actor ( Star subgraph created. A new star subgraph has been created with nodes. Cannot create new cycle subgraph because you have not selected enough nodes. Cycle subgraph created. A new cycle subgraph has been created with select nodes. Cannot create new line subgraph because you have not selected enough nodes. Line subgraph created. A new line subgraph has been created with selected nodes. Change all nodes' color. Invalid color. Select new size for all nodes: Change node shapes aborted. Select an icon Images (*.png *.jpg *.jpeg *.svg);;All (*.*) All shapes have been changed. Node shape has been changed. Change all node numbers size to: (1-16) Changed node numbers size. Node number color changed. Change all node numbers distance from their nodes to: (1-16) Change node number distance aborted. Changed node number distance. Change all node labels text size to: (1-16) Changed node label size. Label colors changed. Change all node labels distance from their nodes to: (1-16) Change node label distance aborted. Changed node label distance. ## NODE (selected nodes: Create a clique from selected nodes Create a star from Create a cycle from Create a line from Create a clique from selected nodes Create a star from selected nodes Create a cycle from selected nodes Create a line from selected nodes Position (%1, %2): Node %3, label %4 - In-Degree: %5, Out-Degree: %6 Position (%1,%2): Double-click to create a new node. Undirected edge %1 <--> %2 of weight %3 has been selected. Click anywhere else to unselect it. Reciprocated edge %1 <--> %2 has been selected. Weight %1 --> %2 = %3, Weight %2 --> %1 = %4. Click anywhere else to unselect it. Directed edge %1 --> %2 of weight %3 has been selected. Click again to unselect it. This will draw a new edge between two nodes. Enter source node ( Error. That node does not exist! Are you sure you entered the correct node number? Source node: Now enter a target node [ Source and target nodes accepted. Please, enter the weight of new edge: Error. That edge already exists! Are you sure you entered the correct node numbers? New edge %1 -> %2 created, weight %3. Remove edge Error. Cannot find that edge! Select edge This is a reciprocated edge. Select direction to remove: Select edge source node: ( Select edge target node: ( Change edge label Enter label: Changed edge label. Change edge label aborted. Changed edges color. edges color change aborted. Select new color.... Edge color changed. Change edge color aborted. This is a reciprocated edge. Select direction: New edge weight: Change edge weight cancelled. All ties have been symmetrized. All ties between nodes have been symmetrized. The network is now symmetric. New cocitation relation added. Ready New cocitation relation has been added to the network. In the new relation, there are ties only between pairs of nodes who were cocited by others. New binary relation added. New dichotomized relation created A new relation called "%1" has been added to the network, using the given dichotomization threshold. Edge dichotomization finished. Select Symmetrize social network by examining strong ties This network has multiple relations. Symmetrize by examining reciprocated ties across all relations or just the current relation? all relations current relation New symmetric relation created from strong ties New relation created from strong ties A new relation "%1" has been added to the network. by counting reciprocated ties only. This relation is binary and symmetric. Undirected data mode. All existing directed edges transformed to undirected. Ready Undirected data mode. Any edge you add will be undirected. Ready Directed data mode. All existing undirected edges transformed to directed. Ready Directed data mode. Any new edge you add will be directed. Ready Directed data mode. All existing undirected edges transformed to directed. Directed data mode. Any new edge you add will be directed. Isolated nodes disabled. Isolated nodes enabled. Unilateral (weak) edges disabled. Unilateral (weak) edges enabled. Nodes in random positions. Nodes in random concentric circles. Eades Spring Embedder β€” Size Warning The Eades Spring Embedder was designed for graphs with fewer than 30 nodes. This network has %1 nodes. The layout may get stuck in local minima and the result may not be aesthetically pleasing. Consider using the Fruchterman-Reingold or Kamada-Kawai models instead, which handle larger graphs better. Spring-Gravitational (Eades) model embedded. Fruchterman & Reingold model embedded. Kamada & Kawai model embedded. Please note that this function is <b>SLOW</b> on large networks (n>200), since it will calculate a (n x n) matrix A with: <br>Aii=1+weighted_degree_ni <br>Aij=1 if (i,j)=0 <br>Aij=1-wij if (i,j)=wij <br>Next, it will compute the inverse matrix C of A. The computation of the inverse matrix is a CPU intensive function although it uses LU decomposition. <br>How slow is this? For instance, to compute IC scores of 600 nodes on a modern i7 4790K CPU you will need to wait for 2 minutes at least. <br>Are you sure you want to continue? Nodes in inner circles have higher %1 score. Nodes in upper levels have higher %1 score. Bigger nodes have greater %1 score. Nodes with warmer color have greater %1 score. Layout Guides are displayed Layout Guides removed Reciprocity report saved as: Symmetric network. The adjacency matrix is symmetric. Non symmetric network. The adjacency matrix is not symmetric. Inverting adjacency matrix. Computation canceled. Inverse matrix saved as: Transposing adjacency matrix. Transpose adjacency matrix saved as: Computing Cocitation matrix. Cocitation matrix saved as: Computing Degree matrix. Degree matrix saved as: Computing Laplacian matrix Laplacian matrix saved as: Non-Weighted Network You do not work on a weighted network at the moment. Therefore, I will not consider edge weights during computations. This option applies only when you load or create a weighted network Weighted Network This is a weighted network. Consider edge weights? The ties in this network have weights (non-unit values) assigned to them. Do you want me to take these edge weights into account (i.e. when computing distances) ? Inverse edge weights during calculations? If the edge weights denote cost or real distances (i.e. miles between cities), press No, since the distance between two nodes should be the quickest or cheaper one. If the weights denote value or strength (i.e. votes or interaction), press Yes to inverse the weights, since the distance between two nodes should be the most valuable one. Select source node (%1..%2): Select target node (%1..%2): Geodesic Distance: %1 Nodes %1 and %2 are connected through at least one path. The length of the shortest path is %3. Nodes %1 and %2 are not connected. In this case, their geodesic distance is considered to be infinite. Computing geodesic distances. Please wait... Geodesic Distances matrix saved as: Computing geodesics (number of shortest paths) for each pair. Please wait... Geodesics Matrix saved as: Computing graph diameter. Please wait... Network diameter computed. Network diameter computed. D = %1 The diameter of a network is the maximum geodesic distance (maximum shortest path length) between any two nodes. Note, since this is a weighted network, the diameter can be greater than N. The diameter of a network is the maximum geodesic distance (maximum shortest path length) between any two nodes. Note, edge weights were disregarded during the computation. This is the diameter of the corresponding network without weights. The diameter of a network is the maximum geodesic distance (maximum shortest path length) between any two nodes. Note, since this is a non-weighted network, the diameter is always smaller than N-1. Computing Average Graph Distance. Please wait... Average graph distance computed. Average graph distance computed. d = %1 The average graph distance is the average length of shortest paths (geodesics) for all possible pairs of nodes. The average distance in this connected network is the sum of pair-wise distances divided by N * (N - 1). Average distance computed. Average distance computed. d = %1 The average graph distance is the average length of shortest paths (geodesics) for all possible pairs of nodes. The average distance in this disconnected network is the sum of pair-wise distances divided by the number of existing geodesics. Eccentricities saved as: This empty network is considered connected! Empty network is considered connected! A null network (empty graph) is considered connected. This 1-actor network is considered connected! A 1-actor network (singleton graph) is always considered connected. This directed network is strongly connected. A 1-actor network (singleton graph) is considered connected. This undirected network is connected. This network has an undirected graph which is connected. This directed network is disconnected. There are pairs of nodes that are not connected with any directed path. This undirected network is not connected. There are pairs of nodes that are not connected with any path. Select desired length of walk: (2 to %1) Walks of length %1 matrix saved as: Please note that this function is VERY SLOW on large networks (n>50), since it will calculate all powers of the sociomatrix up to n-1 in order to find out all possible walks. If you need to make a simple reachability test, we advise to use the Reachability Matrix function instead. Are you sure you want to continue? Computing total walks matrix. Please wait... Computing reachability matrix. Please wait... Reachability matrix saved as: Clustering Coefficients saved as: Clique Census saved as: Triad Census saved as: Similarity matrix saved as: Tie profile dissimilarities matrix saved as: Pearson correlation coefficients matrix saved as: Hierarchical Cluster Analysis saved as: Opening Out-Degree Centralities report... Out-Degree Centralities report saved as: Opening Closeness Centralities report... Closeness Centralities report saved as: Opening Influence Range Closeness Centralities report... Influence Range Closeness Centralities report saved as: Opening Betweenness Centralities report... Betweenness Centralities report saved as: Warning! Running Degree Prestige index on an undirected network. This network is not directed (undirected graph). The Degree Prestige index counts inbound edges, therefore it is meaningful on directed networks. For undirected networks, such as this one, Degree Prestige is the same as Degree Centrality. Opening Degree Prestige (in-degree) report... Degree Prestige (in-degree) report saved as: Opening PageRank Prestige report... PageRank Prestige report saved as: Opening Proximity Prestige report... Proximity Prestige report saved as: Opening Information Centralities report... Information Centralities report saved as: Opening Eigenvector Centralities report... Eigenvector Centralities report saved as: Opening Stress Centralities report... Stress Centralities report saved as: Opening Gil-Schmidt Power Centralities report... Gil-Schmidt Power Centralities report saved as: Eccentricity Centralities report saved as: Distribution of %1 values: Min value: %2 Max value: %3 Please note that, due to the small size of this widget, if you display a distribution in Bar Chart where there are more than 10 values, the widget will not show all bars. In this case, use Line or Area Chart (from Settings). In any case, the large chart in the HTML report is better than this widget... Toggle Edges. Please wait... Edges are invisible now. Click again the same menu to display them. Edges visible again... Toggling Edges' Arrows. Please wait... Arrows in edges: on. Arrows in edges: off. Toggle edges bezier. Please wait... Change all edges offset from their nodes to: (1-16) Change edge offset aborted. Changed edge offset from nodes. Toggle Edges Labels. Please wait... Edge labels are invisible now. Click the same option again to display them. Edge labels are visible again... Toggle zero-weight edges saving. Please wait... Zero-weight edges will be saved to graphml files. Zero-weight edges will NOT be saved to graphml files. Toggle openGL. Please wait... Using openGL off. Using OpenGL on. Toggle anti-aliasing. Please wait... To create a new node: - double-click somewhere on the canvas - or press the keyboard shortcut CTRL+. (dot) - or press the Add Node button on the left panel SocNetV can work with either undirected or directed data. When you start SocNetV for the first time, the application uses the 'directed data' mode; every edge you create is directed. To enter the 'undirected data' mode, press CTRL+E+U or enable the menu option Edit->Edges->Undirected Edges If your screen is small, and the canvas appears even smaller hide the Control and/or Statistics panel. Then the canvas will expand to the whole application window. Open the Settings/Preferences dialog->Window options and disable the two panels. A scale-free network is a network whose degree distribution follows a power law. SocNetV generates random scale-free networks according to the BarabΓ‘si–Albert (BA) model using a preferential attachment mechanism. To delete a node permanently: - right-click on it and select Remove Node - or press CTRL+ALT+. and enter its number - or press the Remove Node button on the Control Panel To rotate the network: - drag the bottom slider to left or right - or click the buttons on the corners of the bottom slider - or press CTRL and the left or right arrow. To create a new edge between nodes A and B: - double-click on node A, then double-click on node B. - or middle-click on node A, and again on node B. - or right-click on the node, then select Add Edge from the popup. - or press the keyboard shortcut CTRL+/ - or press the Add Edge button on the Control Panel Add a label to an edge by right-clicking on it and selecting Change Label. You can change the background color of the canvas. Do it from the menu Options > View or permanently save this setting in Settings/Preferences. Default node colors, shapes and sizes can be changed. Open the Settings/Preferences dialog and use the options on the Node tab. The Statistics Panel shows network-level information (i.e. density) as well as info about any node you clicked on (inDegrees, outDegrees, clustering). You can move any node by left-clicking and dragging it with your mouse. If you want you can move multiple nodes at once. Left-click on empty space on the canvas and drag to create a rectangle selection around them. Then left-click on one of the selected nodes and drag it. To save the node positions in a network, you need to save your data in a format which supports node positions, suchs as GraphML or Pajek. Embed visualization models on the network from the options in the Layout menu or the select boxes on the left Control Panel. To change the label of a node right-click on it, and click Selected Node Properties from the popup menu. All basic operations of SocNetV are available from the left Control panel or by right-clicking on a Node or an Edge or on canvas empty space. Node info (number, position, degree, etc) is displayed on the Status bar, when you left-click on it. Edge information is displayed on the Status bar, when you left-click on it. Save your work often, especially when working with large data sets. SocNetV alogorithms are faster when working with saved data. You can change the precision of real numbers in reports. Go to Settings > General and change it under Reports > Real number precision. The Closeness Centrality (CC) of a node v, is the inverse sum of the shortest distances between v and every other node. CC is interpreted as the ability to access information through the 'grapevine' of network members. Nodes with high closeness centrality are those who can reach many other nodes in few steps. This index can be calculated in both graphs and digraphs. It can also be calculated in weighted graphs although the weight of each edge (v,u) in E is always considered to be 1. The Information Centrality (IC) index counts all paths between nodes weighted by strength of tie and distance. This centrality measure developed by Stephenson and Zelen (1989) focuses on how information might flow through many different paths. This index should be calculated only for undirected graphs. Note: To compute this index, SocNetV drops all isolated nodes. Opening the SocNetV Manual in your default web browser.... Newer SocNetV version available! <p>Your version: <p>Remote version: <b> <p><b>There is a newer SocNetV version available!</b></p><p>Do you want to download the latest version now?</p><p>Press Yes, and I will open your default web browser for you to download the latest SocNetV package...</p> Opening SocNetV website in your default web browser.... <p>Remote version: <p>You are running the latest and greatest version of SocNetV.<br/>Nothing to do!</p> <b>Soc</b>ial <b>Net</b>work <b>V</b>isualizer (SocNetV) <p><b>Version</b>: <p>Website: <a href="https://socnetv.org">https://socnetv.org</a></p> <p>(C) 2005-2026 by Dimitris B. Kalamaras</p> <p><a href="https://socnetv.org/contact">Have questions? Contact us!</a></p> <p><b>Fortune cookie: </b><br> " <p><b>License:</b><p> <p>This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.</p> <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p> <p>You should have received a copy of the GNU General Public License along with this program; If not, see http://www.gnu.org/licenses/</p> Pajek Saving network under new filename... Saving aborted Network has not been saved. Do you want to save before closing it? Printing... Choose a node to remove between ( Source node: ( Target node: ( Distance between two nodes Distance calculation operation cancelled. Nothing to do... Toggle Nodes Numbers. Please wait... Node Numbers are invisible now. Click the same option again to display them. Node Numbers are visible again... Toggle Numbers inside nodes. Please wait... Numbers inside nodes... Numbers outside nodes... Node Labels are visible again... Toggle Nodes Labels. Please wait... Node Labels are invisible now. Click the same option again to display them. Change font size: Aborted. Two-Mode Sociomatrix β€” Select Import Mode Two-mode sociomatrix import This file contains a two-mode (bipartite) sociomatrix, where rows represent one set of actors (e.g. persons) and columns represent another set (e.g. events or groups). How would you like to import it? Bipartite graph: creates both sets of nodes and connects each person to the events they attend. (Recommended) Person network: creates only the person nodes and connects two persons if they share at least one event. Event network: creates only the event nodes and connects two events if they share at least one person. Bipartite graph Person network Event network Toggle Edges Weights. Please wait... Edge weights are invisible now. Click the same option again to display them. Edge weights are visible again... Anti-aliasing off. Anti-aliasing on. Toggle anti-aliasing auto adjust. Please wait... Antialiasing auto-adjustment off. Antialiasing auto-adjustment on. Toggle smooth pixmap transformations. Please wait... Smooth pixmap transformations off. Smooth pixmap transformations on. Toggle saving painter state. Please wait... Saving painter state off. Saving painter state on. Toggle canvas background caching state. Please wait... Canvas background caching off. Canvas background caching on. Toggle edge highlighting state. Please wait... Edge highlighting off. Edge highlighting on. Setting canvas update mode. Please wait... Canvas update mode: Setting canvas index method. Please wait... Canvas index method: SocNetV logo print off. SocNetV logo print on. Toggle progressbar... Progress bars off. Progress bars on. Debug messages off. Debug messages on. Background changed. Toggle BackgroundImage... BackgroundImage off. Select one image Images (*.png *.jpg *.jpeg);;All (*.*) Full screen mode off. Press F11 again to enter full screen. Full screen mode on. Press F11 again to exit full screen. Toggle toolbar... Toolbar off. Toolbar on. Toggle statusbar... Status bar off. Status bar on. Toggle left panel... Left Panel off. Left Panel on. Right Panel off. Right Panel on. Cannot read stylesheet file %1: %2 Tip Of The Day Parser Cannot open file: %1 Invalid UCINET-formatted file. The file does not start with DL in first non-comment line %1 Problem interpreting UCINET-formatted file. Cannot convert N value to integer at line %1. Problem interpreting UCINET-formatted file. Cannot convert NM value to integer at line %1 Problem interpreting UCINET-formatted file. Cannot convert NR value to integer at line %1 Problem interpreting UCINET-formatted file. Cannot convert NC value to integer at line %1 Invalid UCINET format declaration. Expected 'FULLMATRIX' or 'edgelist' but found: %1 Error reading UCINET-formatted file: Number of nodes found (%1) does not match declared N=%2 Matrix row size mismatch. Expected %1 but got %2 at line %3. Problem interpreting UCINET fullmatrix-formatted file. In edge (%1->%2), the weight (%3) could not be converted to number, at line %4. Problem interpreting UCINET two-mode fullmatrix-formatted file. The file declared %1 columns initially, but I found a different number %2 of matrix columns, at line %3. Problem interpreting UCINET two-mode file. In edge (%1->%2), the weight (%3) cannot be converted to number, at line %4. Problem interpreting UCINET-formatted file. The file was declared as edgelist but I found a line which did not have 3 elements (source, target, weight), at line %1 Error while reading UCINET-formatted file. Cannot convert N value to integer. Problem interpreting UCINET file. Cannot convert NM value to integer. Error while reading UCINET-formatted file. Cannot convert NR value to integer. Error while reading UCINET-formatted file. Cannot convert NC value to integer. Not a Pajek-formatted file. First not-comment line %1 (at file line %2) does not start with Network or Vertices Not a Pajek-formatted file. First not-comment line does not start with Network or Vertices Invalid Pajek-formatted file. It declares a node with nodeNumber smaller than previous nodes. Invalid Pajek-formatted file. The file declares an edge with a zero source or target nodeNumber. However, each node should have a nodeNumber > 0. Invalid Pajek-formatted file. The file declares arc with a zero source or target nodeNumber. However, each node should have a nodeNumber > 0. Invalid Pajek-formatted file. Could not find node declarations in this file. Invalid adjacency-formatted file. Non-comment line %1 includes reserved keywords ('%2'). Parsing aborted. Invalid Adjacency-formatted file. Node labels line is empty or improperly formatted. Parsing aborted. Invalid Adjacency-formatted file. Row %1 at line %2 has a different number of elements (%3) than expected (%4). Parsing aborted. fileLine: %1 is a comment... Invalid Adjacency-formatted file. Not a NxN matrix. Row %1 declares %2 edges. Expected: %3. Parsing aborted. Error reading Adjacency-formatted file. Element (%1, %2) contains invalid data ('%3'). Parsing aborted. Invalid two-mode sociomatrix file. Non-comment line %1 includes keywords reserved by other file formats (vertices, graphml, network, graph, digraph, DL, xml) Invalid two-mode sociomatrix file. Row %1 has a different number of elements than the first data row. Invalid two-mode sociomatrix file: no data rows found. Invalid GraphML file. XML at startElement but element name not graphml. Invalid GraphML file. XML tokenString at line %1 invalid. Invalid GraphML file. XML has error at line %1, token name %2: %3 Not an GML-formatted file. Non-comment line %1 includes keywords reserved by other file formats (i.e vertices, graphml, network, digraph, DL, xml) Not a proper GML-formatted file. Node id tag at line %1 has non-arithmetic value. Not a proper GML-formatted file. Edge source tag at line %1 has non-arithmetic value. Not a proper GML-formatted file. Edge target tag at line %1 has non-arithmetic value. Not a proper GML-formatted file. Edge weight tag at line %1 has an invalid value. Not a proper GML-formatted file. Node center tag at line %1 cannot be converted to qreal. Not a proper GML-formatted file. Node type tag at line %1 has no value. Not a proper GML-formatted file. Node fill tag at line %1 has no value. Invalid GraphViz (dot) file. The file does not contain 'digraph' or 'graph'. Not a GraphViz-formatted file. First non-comment line includes keywords reserved by other file formats (i.e vertices, graphml, network, DL, xml). Not properly GraphViz-formatted file. First non-comment line should start with "(di)graph netname {" Invalid DOT edge statement: mixed '->' and '--' operators on the same line. Not properly GraphViz-formatted file. Node definition without closing ] Not properly GraphViz-formatted file. Node definition without opening [ Not an EdgeList-formatted file. A non-comment line includes keywords reserved by other file formats (i.e vertices, graphml, network, graph, digraph, DL, xml) Not a properly EdgeList-formatted file. Row %1 has not 3 elements as expected (i.e. source, target, weight) Not an EdgeList-formatted file. Non-comment line %1 includes keywords reserved by other file formats (i.e vertices, graphml, network, graph, digraph, DL, xml) QObject Computing geodesic distances. Please wait... not a GraphML file. invalid GraphML or encoding. Default custom icon for nodes does not exist in the filesystem. The declared icon file was: %1 TextEditor Save file &New Ctrl+N Create a new file &Open... Ctrl+O Open an existing file &Save Ctrl+S Save the document to disk Save &As... Save the document under a new name E&xit Ctrl+Q Exit the application Cu&t Ctrl+X Cut the current selection's contents to the clipboard &Copy Ctrl+C Copy the current selection's contents to the clipboard &Paste Ctrl+V Paste the clipboard's contents into the current selection &About Show the application's About box About &Qt Show the Qt library's About box &File &Edit &Help File Edit Ready TextEditor The document has been modified. Do you want to save your changes? SocNetV Editor Cannot read file %1: %2. File loaded Cannot write file %1: %2. File saved %1[*] - %2 main Network file to load on startup. You can load a network from a file using `socnetv file.net` where file.net/csv/dot/graphml must be of valid format. See README. Force showing progress dialogs/bars during computations. Do not maximize the app window. Show in full screen mode. Print debug messages to stdout/console. Available verbosity <level>s: 'none', 'min' or 'full'. Default: 'min'. level