pax_global_header00006660000000000000000000000064151772100010014504gustar00rootroot0000000000000052 comment=39db829f33691112077c1f006889c8154561287d socnetv-app-39db829/000077500000000000000000000000001517721000100143115ustar00rootroot00000000000000socnetv-app-39db829/.appveyor.yml000066400000000000000000000160731517721000100167660ustar00rootroot00000000000000# 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.5 - 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-app-39db829/.github/000077500000000000000000000000001517721000100156515ustar00rootroot00000000000000socnetv-app-39db829/.github/workflows/000077500000000000000000000000001517721000100177065ustar00rootroot00000000000000socnetv-app-39db829/.github/workflows/build-ci.yml000066400000000000000000001127121517721000100221250ustar00rootroot00000000000000# 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.5" # TODO - READ FROM FILE VERSION: "3.5" # 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@v4 # 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/ qt-version: ['6.5.3', '6.8.0'] # 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@v4 - 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@v6 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@v6 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 and Qt 6.5 - if: matrix.os == 'ubuntu-22.04' && matrix.qt-version == '6.5.3' 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 and newer versions of Qt - if: matrix.os == 'ubuntu-22.04' && matrix.qt-version != '6.5.3' 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 with Qt 6.8 (latest LTS) # Uses linuxdeploy + linuxdeploy-plugin-qt (supports Ubuntu 22.04+) - if: matrix.os == 'ubuntu-22.04' && matrix.qt-version == '6.8.0' 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.8.0' && 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 # # We test both qmake and cmake builds: # In older Qt, we use qmake but we do not upload the artifact. # We only upload the artifact for Qt 6.8.0, which is the latest LTS and the one we will use for the next release. - if: contains( matrix.os, 'macos') && matrix.qt-version == '6.5.3' name: Build ${{env.APP_NAME}} ${{ env.VERSION }} for ${{matrix.os}} with Qt ${{matrix.qt-version}} using qmake run: | echo "🍎 Preparing macOS build..." echo "Building version: ${{env.VERSION}}" # Ensure Qt is installed and in PATH # export PATH="$(brew --prefix qt)/bin:$PATH" which qmake echo "πŸ”§ Running 'qmake CONFIG+=${{env.QMAKE_CONFIG}} ${{env.QMAKE_PROJECT}}' to configure on macos..." qmake CONFIG+=${{env.QMAKE_CONFIG}} ${{env.QMAKE_PROJECT}} echo "🚧 πŸ› οΈ Compiling for macos with make. Please wait..." make echo "πŸ‘‰ Building finished! Searching 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 built binary..." 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 file not found!" exit 1 fi echo "πŸ”§ Removing items we do not deploy from project dir ..." rm -rf moc obj qrc # Run macdeployqt WITHOUT -dmg to just bundle frameworks echo "πŸš€ Running macdeployqt to bundle required libraries..." macdeployqt "${{env.APP_NAME}}.app" -verbose=3 || { echo "Error: macdeployqt failed." exit 1 } # Verify the bundled libraries echo "πŸ”Ž Verify the bundled libraries in the built binary ..." if [[ -f "${{env.APP_NAME}}.app/Contents/MacOS/${{env.APP_NAME}}" ]]; then otool -L "${{env.APP_NAME}}.app/Contents/MacOS/${{env.APP_NAME}}" else echo "❌ Error: Binary file not found!" exit 1 fi # Create DMG manually echo "πŸ“¦ Creating DMG..." hdiutil create -volname "${{env.APP_NAME}}" \ -srcfolder "${{env.APP_NAME}}.app" \ -ov -format UDZO "${{env.APP_NAME}}.dmg" if [[ -f "${{env.APP_NAME}}.dmg" ]]; then DMG_NAME="${{env.APP_NAME}}-${{env.VERSION}}.dmg" mv "${{env.APP_NAME}}.dmg" "${DMG_NAME}" echo "πŸŽ‰ Build and packaging complete. Final DMG: ${DMG_NAME}" ls -lh *.dmg echo "MAC_ARTIFACT=${DMG_NAME}" >> $GITHUB_ENV else echo "❌ Error: DMG creation failed. No DMG file found." exit 1 fi # For the latest Qt 6.8.0, we build with cmake and upload the DMG artifact if it was created successfully. - if: contains( matrix.os, 'macos') && matrix.qt-version != '6.5.3' 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.8.0' && 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 # # Again we test build for both cmake and qmake. # # Test build with qmake for Qt 6.5.3 - if: contains( matrix.os, 'windows') && matrix.qt-version == '6.5.3' name: Build ${{env.APP_NAME}} ${{ env.VERSION }} for ${{matrix.os}} with Qt ${{matrix.qt-version}} using qmake. run: | echo "πŸ”Ž Listing some directories" dir D:\a\app\Qt\ echo "πŸ”§ Running 'qmake6 CONFIG+=${{env.QMAKE_CONFIG}} ${{env.QMAKE_PROJECT}} -r' to configure the project on Windows..." qmake6 CONFIG+=${{env.QMAKE_CONFIG}} TARGET.path=${{env.QMAKE_CONFIG}} ${{env.QMAKE_PROJECT}} -r echo "🚧 πŸ› οΈ Compiling with nmake. Please wait..." nmake echo "πŸ‘‰ Building finished. Listing directory ${{env.QMAKE_CONFIG}} for verification:" dir ${{env.QMAKE_CONFIG}} # # Build with cmake and newer LTS version of Qt (currently 6.8.0) # Then, create installer with Inno Setup. # - if: contains( matrix.os, 'windows') && matrix.qt-version != '6.5.3' 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: We only upload from Windows when building with Qt 6.8, # to avoid uploading too many artifacts for every commit. # - if: contains( matrix.os, 'windows') && matrix.qt-version == '6.8.0' && 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.8.0' name: πŸ”„ Update continuous release description uses: actions/github-script@v6 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-app-39db829/.github/workflows/build-release.yml000066400000000000000000000463171517721000100231610ustar00rootroot00000000000000# 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@v4 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@v6 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.8.0'] 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@v4 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-app-39db829/.github/workflows/validate.yml000066400000000000000000000010531517721000100222210ustar00rootroot00000000000000name: Validate-YAMLs on: push: branches: [master, develop] jobs: validate-yamls: runs-on: ubuntu-latest steps: - name: πŸ“‚ Checkout repository uses: actions/checkout@v4 - 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-app-39db829/.github/workflows/yamlint.conf000066400000000000000000000003531517721000100222330ustar00rootroot00000000000000extends: 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-app-39db829/.gitignore000066400000000000000000000005561517721000100163070ustar00rootroot00000000000000CLAUDE.md *.pro.user build 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-app-39db829/.travis.yml000066400000000000000000000032251517721000100164240ustar00rootroot00000000000000# 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.5 - 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-app-39db829/AUTHORS000066400000000000000000000007601517721000100153640ustar00rootroot00000000000000Developer: Dimitris Kalamaras See my blog at: https://dimitris.apeiro.gr MacPorts packager: - Szabolcs HorvΓ‘t Debian packagers: - 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-app-39db829/CHANGELOG.md000066400000000000000000000616541517721000100161360ustar00rootroot00000000000000# Changelog All notable changes to this project are documented in this file. ## [3.5] – May 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-app-39db829/CMakeLists.txt000066400000000000000000000420731517721000100170570ustar00rootroot00000000000000# 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) # ============================================================================== # 1. Project Setup # ============================================================================== # Set our project name and version: set(APP_NAME SocNetV) set(APP_NAME_LOWER socnetv) project(SocNetV VERSION 3.5 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/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/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_prestige.cpp src/graph/prominence/graph_prominence_distribution.cpp src/graph/matrices/graph_matrix_adjacency.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/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(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 ) # 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/dialogedgedichotomization.ui src/forms/dialogexportpdf.ui src/forms/dialogexportimage.ui # Other UI files ) set(SOURCES src/main.cpp src/mainwindow.cpp src/texteditor.cpp ${ENGINE_SOURCES} ${GRAPH_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/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/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 # 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) # 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) 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 ) # ============================================================================== # 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 ) 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() # Finalize the executable qt_finalize_executable(${TARGET_NAME}) socnetv-app-39db829/COPYING000066400000000000000000001032741517721000100153530ustar00rootroot00000000000000GNU 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-app-39db829/INSTALL000066400000000000000000000073441517721000100153520ustar00rootroot00000000000000Installation ============ 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-app-39db829/README.md000066400000000000000000000300101517721000100155620ustar00rootroot00000000000000# SocNetV [![version](https://img.shields.io/github/release/socnetv/app.svg?logo=c%2B%2B)](https://github.com/socnetv/app/releases) [![Build Status GitHub Actions](https://github.com/socnetv/app/actions/workflows/build-ci.yml/badge.svg)](https://github.com/socnetv/app/actions/workflows/build-ci.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. - 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/25/socnetv-25-padget-power-centrality-size-distribution.png)](https://socnetv.org) ## 2. Features - Standard graph-theoretic and network cohesion metrics: density, diameter, geodesics, connectedness, eccentricity, clustering coefficient, walks, reciprocity, and more. - Matrix routines: Adjacency, Laplacian, Degree, Cocitation, and more. - Advanced centrality and prestige indices: eigenvector, closeness, betweenness, information, 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. - 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. - 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. - 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. #### 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.8). 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.3. 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'. 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) and produces deterministic JSON output suitable for scripting and regression testing. ```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-app-39db829/TODO000066400000000000000000000003471517721000100150050ustar00rootroot00000000000000KNOWN 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-app-39db829/docs/000077500000000000000000000000001517721000100152415ustar00rootroot00000000000000socnetv-app-39db829/docs/ARCHITECTURAL_REFACTORING_ROADMAP.md000066400000000000000000000125231517721000100225460ustar00rootroot00000000000000# ARCHITECTURAL_REFACTORING_ROADMAP This document defines the **current architectural direction and active workstreams** of SocNetV. Detailed execution plans live in: ``` docs/roadmaps/ ``` --- # North Star Evolve SocNetV from a Graph-centric monolith: ``` UI β†’ Graph β†’ everything ``` to a layered and extensible architecture: ``` UI ↓ Graph (thin faΓ§ade / coordinator) ↓ Domain model + services β”œβ”€β”€ algorithms β”œβ”€β”€ IO β”œβ”€β”€ matrices └── caches ``` --- # Current Architectural State SocNetV has reached a stable baseline: - Algorithms extracted into testable components (engines) - Graph acts as faΓ§ade and coordinator - IO layer uses explicit mutation pipeline (Parser β†’ Sink β†’ Graph) - Deterministic regression harness (CLI + golden baselines) is in place - Parser is modularized by format This allows safe, incremental evolution without breaking behavior. --- # Guiding Principles Non-negotiables: - Preserve functionality and numeric results - Preserve performance (no regressions) - Keep changes incremental: **build β†’ run β†’ compare** - Maintain deterministic behavior (CLI regression harness) Engineering approach: - Prefer vertical feature slices over large rewrites - Reuse existing functionality where possible - Avoid premature abstraction - Let real usage drive architectural refinement --- # Active Workstreams --- ## WS9 β€” Graph Exploration & Data Workflows (PRIMARY) Defined in: ``` docs/roadmaps/roadmap_graph_exploration.md ``` ### Goal Make SocNetV usable for **real-world large networks**. ### Scope WS9 consolidates the following GitHub issue tracks: - **Visualization & Decluttering** β†’ #209 (selection, ego networks, edge filtering, layouts) - **Filtering & Subgraphs** β†’ #215 (structural filters, attribute filters, subgraph workflows, queries) - **Data Workflows** β†’ #223 (attribute editing, tables, import/export, transformations) Concrete sub-features are tracked in: - #210–#214 (visualization) - #216–#221 (filtering & querying) - #224–#229 (data workflows) WS9 acts as the **umbrella workstream** coordinating these efforts. ### Architectural Impact Introduces a new conceptual layer: ``` Graph β†’ Filter / Projection Layer β†’ UI ``` Key properties: - non-destructive (visibility-based) - stateful (not dialog-driven) - reusable across UI components ### Strategy - Build incrementally on existing dialogs and systems - Unify behavior over time (not via big rewrite) - Respect current single-window architecture - Prepare for future tab-based multi-graph UI --- ## WS6 β€” Testing / CI / Regression (SUPPORTING) Defined in: ``` docs/roadmaps/roadmap_testing_ci_regression.md ``` ### Goal Prevent silent regressions during ongoing development. ### Responsibilities - Maintain golden baselines - Expand dataset coverage - Ensure deterministic CLI behavior - Support benchmarking ### Role WS6 supports WS9 by ensuring all changes are: - verifiable - reproducible - safe to refactor --- ## WS3 β€” Domain Model Split (MID-TERM) Defined in: ``` docs/roadmaps/roadmap_domain_model_split.md ``` ### Goal Introduce a domain model independent of UI and Graph faΓ§ade. Target separation: ``` model (nodes, edges, relations) vs algorithms / services / caches ``` ### Strategy - Proceed incrementally - Use WS9 requirements to guide abstraction boundaries - Avoid blocking feature development --- ## WS7 β€” MainWindow Decomposition (LATER) ### Goal Break down the MainWindow monolith into smaller UI components. ### Strategy - Follow real UI evolution from WS9 - Avoid premature modularization - No UX changes --- ## WS5 β€” Matrices Modernization Defined in: ``` docs/roadmaps/roadmap_matrices_modernization.md ``` ### Goal Isolate and modernize matrix operations. --- ## WS8 β€” IO Layer Stabilization ### Goal Refine and simplify the parser/IO architecture. Possible direction: - FormatHandler abstraction - cleaner dispatch model - easier extensibility --- # Workstream Priorities 1. **WS9 β€” Graph Exploration & Data Workflows** 2. **WS6 β€” Testing / CI / Regression** 3. **WS3 β€” Domain Model Split** 4. **WS7 β€” MainWindow Decomposition** 5. **WS5 β€” Matrices** 6. **WS8 β€” IO** --- # Workstream Relationships ``` WS9 (product evolution) ↓ supported by WS6 (regression safety) WS3 (domain model) follows insights from WS9 WS7 (UI decomposition) follows UI complexity from WS9 WS5 / WS8 independent infrastructure improvements ``` --- # Target Architecture (Long-Term) ``` domain/ β”œβ”€β”€ model/ β”œβ”€β”€ algorithms/ β”œβ”€β”€ matrices/ β”œβ”€β”€ io/ └── services/ ``` Graph remains a faΓ§ade coordinating these layers during transition. --- # Contribution Workflow When contributing: 1. Identify the relevant workstream 2. Follow its roadmap in `docs/roadmaps/` 3. Keep commits small and mechanical 4. After each change: ``` build ./scripts/run_golden_compares.sh ./scripts/run_benchmarks.sh ``` Golden baselines and performance must remain stable. --- # Summary SocNetV now evolves along two axes: ### Product Evolution - Graph exploration - Filtering and querying - Structured data workflows ### Architectural Evolution - Regression safety - Domain model separation - UI decomposition Development should prioritize **real user value (WS9)** while maintaining architectural integrity through incremental refactoring. socnetv-app-39db829/docs/README_DEVELOPER_NOTES.md000066400000000000000000000152621517721000100211030ustar00rootroot00000000000000# SocNetV Developer Notes This folder documents the **current architecture** of SocNetV and the **ongoing modernization effort**. If you are new to the codebase, start here, then read the high-level refactoring roadmap: * [`ARCHITECTURAL_REFACTORING_ROADMAP.md`](ARCHITECTURAL_REFACTORING_ROADMAP.md) Then read the current product-oriented roadmap: * [`docs/roadmaps/roadmap_graph_exploration.md`](docs/roadmaps/roadmap_graph_exploration.md) Detailed execution plans live under: ``` docs/roadmaps/ ``` --- # Project Snapshot SocNetV is a Qt-based desktop application for social network analysis and visualization. Historically, most functionality flowed through a central `Graph` object which acted as: * domain model (network storage) * algorithm host (distances, centralities, clustering, etc.) * UI bridge (signals/progress) * I/O coordinator (loading datasets) This design worked but made testing, modularization, and safe refactoring difficult. --- # Regression Safety Harness To safeguard the modernization effort, SocNetV includes a **headless regression harness**: ``` socnetv-cli ``` This tool allows deterministic execution of algorithms and parsing pipelines. It supports: * golden output comparisons * performance benchmarking * IO roundtrip validation Documentation: ``` src/tools/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 scripts must pass after structural refactors. The CLI tool executes deterministic algorithm kernels and compares results against committed JSON baselines. This guarantees that architectural refactors do not change algorithm outputs or graph semantics. --- # CLI Kernel Architecture The regression harness is organized around **kernel modules**. Each kernel protects a specific algorithm family and emits a deterministic JSON schema. Current kernels include: ``` kernel_distance_v1 kernel_reachability_v2 kernel_walks_v3 kernel_prominence_v4 kernel_io_roundtrip_v5 kernel_clustering_v6.cpp (Clustering, Triads, Cliques) ``` Each kernel owns: * its execution logic * JSON schema definition * strict comparison logic Schemas are versioned and never modified after release. This ensures deterministic verification of algorithm correctness during architectural refactors. --- # Current Architectural State Refactoring workstreams **WS1, WS2, and WS4 are complete**. The project now has: * engine-based algorithms * a thin Graph faΓ§ade * a deterministic regression harness * unified GUI/CLI parsing via `IGraphParseSink` --- # Current Development Focus ## Primary Workstream **WS9 β€” Graph Exploration & Data Workflows** Defined in: ``` docs/roadmaps/roadmap_graph_exploration.md ``` Focus areas: * graph filtering (structural + attribute-based) * subgraph exploration workflows * structured data editing (nodes/edges as tables) * CSV / JSON import-export * future query system and temporal filtering --- ## Supporting Workstream **WS6 β€” Testing / CI / Regression** All development must be validated through: * CLI regression harness * golden comparisons * benchmarks WS6 ensures: * safe refactoring * deterministic behavior * performance stability --- # Graph as FaΓ§ade `Graph` now acts primarily as: * state holder and invariants guardian * explicit faΓ§ade API for UI and CLI * delegator to algorithm slices * central UI signal coordinator `graph.cpp` contains only: ``` Graph::Graph(...) Graph::clear(...) ``` All other functionality lives under: ``` src/graph/ ``` organized by responsibility: ``` centrality/ clustering/ cohesion/ 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. ## Algorithm slices Responsibilities: * 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/`) Responsibilities: * construct QtWidgets / QtCharts objects * render visualizations * export PNG charts * emit UI update signals to `MainWindow` Example: ``` src/graph/ui/graph_ui_prominence_distribution.cpp ``` --- ## Rule for New Code If you add new analytics: 1. **compute results in algorithm slices** 2. **perform rendering in the UI faΓ§ade** This separation is mandatory. --- # Distance Engine Shortest-path algorithms were extracted into: ``` src/engine/ ``` Main components: ``` distance_engine.cpp distance_progress_sink.h graph_distance_progress_sink.cpp ``` These engines can run: * from the GUI * from the CLI regression harness --- # Parsing and I/O The parser architecture was modernized during **WS4**. --- ## Current Parsing Architecture ``` Parser ↓ IGraphParseSink ↓ Graph ``` Key components: ``` src/graph/io/graph_parse_sink.h src/graph/io/graph_parse_sink_graph.cpp ``` Typical mutation calls: ``` createNode(...) createEdge(...) setRelation(...) addNewRelation(...) removeDummyNode(...) fileLoaded(...) ``` GUI and CLI share the same mutation pipeline β†’ deterministic behavior. --- # Code Shape (High-Level) ## UI Layer ``` MainWindow dialogs graphics widgets/items ``` --- ## Core Coordinator ``` Graph (faΓ§ade) ``` --- ## Data Structures ``` GraphVertex edge storage analysis caches ``` --- ## Engines / Algorithm Slices Examples: ``` DistanceEngine centrality clustering prominence similarity layouts generators reporting reachability ``` --- ## IO ``` Parser IGraphParseSink GraphParseSinkGraph ``` --- # Development Workflow Notes ## Build * CMake + Qt6 (Linux / macOS / Windows) * Refactors must remain incremental --- ## Regression Discipline After structural changes: ``` ./scripts/run_golden_compares.sh ./scripts/run_benchmarks.sh ``` Golden outputs and performance must remain stable. --- # Launchpad PPA builds Supported: ``` Ubuntu 22.04 LTS (Jammy) Ubuntu 24.04 LTS (Noble) ``` --- # Mental Model for Contributors ``` UI ↓ Graph (faΓ§ade) ↓ Algorithm slice / engine ↓ UI faΓ§ade (if rendering required) ↓ Signal to MainWindow ``` Do **not bypass this flow**. --- # Development Philosophy (Important) SocNetV evolves through: ### 1. Product-driven development (WS9) - deliver real user value - enable large-network exploration - build on existing systems ### 2. Incremental architectural evolution - refactor safely using WS6 harness - avoid large disruptive rewrites - let real usage guide abstraction (WS3, WS7) socnetv-app-39db829/docs/roadmaps/000077500000000000000000000000001517721000100170475ustar00rootroot00000000000000socnetv-app-39db829/docs/roadmaps/roadmap_distances_geodesic_engine.md000066400000000000000000000220751517721000100262460ustar00rootroot00000000000000# Distance & Geodesic Engine Refactor Roadmap This roadmap documents the ongoing architectural refactor of `Graph::graphDistancesGeodesic()` into a testable, maintainable engine while preserving exact behavior and results. --- ## Goals - Reduce Graph-centric complexity - Improve maintainability / evolvability - Enable headless verification and unit tests - Preserve performance (no regressions vs v3.2) - Preserve UI behavior during refactor ### Non-negotiables - Preserve functionality and numeric results (bit-for-bit where possible). - Preserve current debug output and progress UI signaling behavior unless explicitly changed. - Keep refactors incremental: compile + run + compare results at every step. --- ## CURRENT STATUS (WHAT’S DONE) ### Phase A β€” Extraction Foundation βœ… - Introduced `DistanceEngine` as the owner of the geodesic-distance computation previously living in `Graph::graphDistancesGeodesic()`. - `DistanceEngine` has direct access to `Graph` internals via `friend class DistanceEngine;` (explicit transitional design choice). --- ### Phase B β€” Verified Behavioral Parity βœ… - Verified with Zachary’s Karate Club dataset: - identical distances and connectivity handling - identical centrality / prestige indices - identical results vs SocNetV 3.2 release --- ### Phase C β€” Scratch State Separation βœ… #### C1 βœ… - Broke computation into three internal methods: - `initRun(...)` - `runAllSources(...)` - `finalize(...)` - Preserved logic and debug output while reducing `compute()` size. #### C2.3 βœ… - Introduced explicit scratch structs to eliminate giant parameter lists. - Scratch structures are now the **single source of truth** for transient algorithm state. --- ### Phase D β€” Engine Decoupling & Testability βœ… Phase D transitions from β€œcode movement” to **structural safety and testability** without changing UI behavior. --- #### βœ… D.1 Progress / UI Decoupling (COMPLETED) **What was done** - Introduced `IDistanceProgressSink`. - Implemented: - `GraphDistanceProgressSink` (Qt/UI-backed) - `NullDistanceProgressSink` (headless / test usage) - Replaced all direct `emit graph.*` calls with sink calls. **Result** - UI behavior unchanged - DistanceEngine no longer depends directly on Qt - Headless execution is now possible - Performance preserved --- #### βœ… D.2 Reduce unnecessary Graph internals access (COMPLETED) **What was done** DistanceEngine no longer mutates Graph internals directly. Instead, intent-revealing Graph APIs encapsulate transient SSSP / Brandes state. ##### 1. Stack encapsulation (SSSP / Brandes) ```cpp void ssspStackClear(); bool ssspStackEmpty() const; int ssspStackTop() const; void ssspStackPop(); int ssspStackSize() const; ```` ##### 2. Nth-order neighborhood (Power Centrality) ```cpp void ssspNthOrderClear(); H_f_i sizeOfNthOrderNeighborhood; H_f_i::const_iterator ssspNthOrderBegin() const; H_f_i::const_iterator ssspNthOrderEnd() const; ``` ##### 3. Component size accumulator ```cpp void ssspComponentReset(int value = 1); void ssspComponentAdd(int delta); int ssspComponentSize() const; ``` ##### 4. Connectivity bookkeeping ```cpp void notConnectedPairsClear(); void notConnectedPairsInsert(int from, int to); int notConnectedPairsSize() const; ``` **Result** * No algorithmic changes * Reduced coupling * Clear semantic boundaries * Safer future refactors --- #### βœ… D.3 β€” Golden Regression Harness (COMPLETED β€” multi-format, graph + per-node) Phase D.3 establishes a deterministic, format-agnostic regression harness that protects the DistanceEngine refactor against semantic and numeric drift. This phase elevates the headless CLI into a **full safety net**. --- ##### What Was Implemented See script: [scripts/run_golden_compares.sh](../../scripts/run_golden_compares.sh) βœ” Headless CLI execution path βœ” Deterministic JSON output βœ” Strict comparison mode (CI-ready) βœ” Multi-format baseline coverage DistanceEngine is now verifiable independently of UI and MainWindow. --- ##### JSON Regression Schema (v1) ###### Dataset & Run Metadata * file path / name * file type * run flags: * computeCentralities * considerWeights * inverseWeights * dropIsolates ###### Graph-Level Metrics * `nodes` (N) * `LINKS_SNA` (loader semantics) * `TIES_GRAPH` (Graph::edgesEnabled canonical ties/arcs) * directed / weighted flags * average geodesic distance * diameter * disconnected_pairs * connected ###### Per-Node Vectors (deterministic order by id) * CC / SCC * BC / SBC * SC / SSC * EC / SEC * PC / SPC * distance_sum * eccentricity --- ##### Determinism Guarantees * Vertex order strictly sorted by id * Floating-point values serialized as strings * NaN values handled explicitly * Field-by-field JSON comparison * Non-zero exit on mismatch (CI-safe) This ensures even subtle algorithmic drift (e.g., stack ordering, Brandes accumulation changes, loader semantics differences) is detected. --- ##### Baseline Coverage ###### GraphML * Erdos–RΓ©nyi N=10 * Small-world N=10 ###### UCINET (.dl) * Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl * weighted / unweighted runs * directed semantics verified * disconnected_pairs validated ###### Pajek (.paj) * Dunbar Gelada baboon colony (H22a) * undirected valued edges * weighted + unweighted runs * connectedness verified --- ##### Why This Matters GraphML behaved correctly for ties. Historically: * Pajek and DL imports had subtle tie-count differences * Weighted flags were sometimes ambiguous * Loader semantics varied across formats D.3 guarantees: * Loader correctness * Canonical tie semantics * Connectivity bookkeeping * Full per-node centrality correctness * Multi-format parity DistanceEngine refactors are now protected across: * directed vs undirected * weighted vs unweighted * connected vs disconnected * sparse vs dense * multi-format imports --- #### βœ… D.4 β€” Document engine boundary ##### DistanceEngine owns * Algorithm flow: `initRun`, `runAllSources`, `finalize` * Scratch lifetime + invariants * Use of Graph traversal primitives / accessors only * Progress reporting **only** via `IDistanceProgressSink` ##### Graph owns * Storage and access to: * vertices/edges (enabled/disabled) * per-vertex metric storage (CC/BC/…) * connectivity bookkeeping container (notConnectedPairs…) * β€œNarrow” algorithm support primitives (already done: stack, nth-order, component counter) * Cached results exposure (your new `*Cached()` accessors are perfect here) ##### UI owns * Creating the sink used by DistanceEngine (`GraphDistanceProgressSink`) * Translating progress to Qt widgets/signals * Nothing algorithmic --- #### βœ… D.5 β€” Physical extraction from `graph.cpp` (COMPLETED) ##### Step 1: Create files * Add: * `src/engine/distance_engine.h` * `src/engine/distance_engine.cpp` * Move the **DistanceEngine class** and implementation from `graph.cpp` into these. * Keep **exact same code**, just relocated. ##### Step 2: Keep access unchanged (transitional) * Keep `friend class DistanceEngine;` in `Graph` * Keep any β€œGraph scratch structs” where they are for now (or move with DistanceEngine if they’re private to it). * Use forward declarations to avoid include explosions: * `class Graph;` * `class IDistanceProgressSink;` * Only include `graph.h` inside `distance_engine.cpp`, not in the header (unless absolutely required). ##### Step 3: Wire it back In `graph.cpp`, replace the inlined engine body with: * `#include "engine/distance_engine.h"` * `DistanceEngine engine(*this, sink); engine.compute(...);` (whatever your current call shape is) ##### Step 4: Build + run golden compares * `socnetv-cli --compare-json ...` on *all* baselines * Ensure **no diffs** in JSON ##### Step 5: Only then do cleanup After it’s safely compiled and baselines pass: * reduce includes * tidy headers * (optionally) split scratch structs if they’re better colocated #### βœ… D.6 β€” Micro-benchmarking **Deliverable** * Timing output (ms, N, E) * Performance regression guardrail vs v3.2 see [scripts/run_benchmarks.sh](../../scripts/run_benchmarks.sh) ## CURRENT SHAPE (REFERENCE) ### Engine API ```cpp DistanceEngine::compute( computeCentralities, considerWeights, inverseWeights, dropIsolates ); ``` ### Internal structure * `initRun(...)` * `runAllSources(...)` * `finalize(...)` ### Scratch State * `DistanceScratch` holds: * iterators * counters * N/E snapshots * per-run scalars * connectivity tracking --- ## NEXT STEPS (WHAT WE DO NEXT) ## Phase E β€” Regression Guardrails * Karate Club * Synthetic graphs (line, star, disconnected, isolates, N=1/2) * Weighted + inverse-weighted variants Verify: * distance summaries * centrality vectors * connectivity bookkeeping --- ## Phase F β€” Optional Improvements (Post-safety-net) Only after regression coverage exists: * Narrow or remove `friend` access * Split centrality algorithms further * Tighten types (`int` β†’ `qsizetype` etc.) --- ## WORK RULES * No semantic changes during refactor phases * One small step per commit * Always compare against known datasets * Debug output stays unless explicitly cleaned up later socnetv-app-39db829/docs/roadmaps/roadmap_domain_model_split.md000066400000000000000000000014401517721000100247350ustar00rootroot00000000000000# Domain Model Split Roadmap (Skeleton) ## Goal Introduce a domain model that is independent from UI concerns and can be tested headlessly. ## Current Reality - `Graph` mixes storage, algorithm state, caches, and UI signaling. - `GraphVertex` acts as both node storage and analysis result cache. ## Target Direction - Separate β€œmodel” (nodes/edges/relations) from β€œservices/algorithms”. - Keep `Graph` as faΓ§ade during transition. ## Milestones - M1: Identify minimal model surface required by algorithms - M2: Introduce `GraphModel` (adapter over existing Graph internals initially) - M3: Move pure data containers out of UI/Qt dependencies where possible - M4: Gradually relocate caches into explicit cache objects ## Work Rules - Prefer adapters/wrappers first, not data migrations. socnetv-app-39db829/docs/roadmaps/roadmap_graph_exploration.md000066400000000000000000000376121517721000100246320ustar00rootroot00000000000000# Roadmap: Graph Exploration (WS9) ## Overview This roadmap defines the evolution of SocNetV from a visualization-focused application into a full **graph exploration and data workflow platform**. It consolidates three major feature tracks: * **Feature 1 (#209)**: Visualization & decluttering * **Feature 2 (#215)**: Filtering, querying & subgraphs * **Feature 3 (#223)**: Structured data workflows --- ## Core Vision SocNetV should support the full workflow: ``` Load β†’ Visualize β†’ Filter β†’ Explore β†’ Extract β†’ Edit β†’ Export ``` --- ## Architectural Direction ### Current State * Graph is tightly coupled with UI (MainWindow) * Many operations are dialog-driven * No unified filtering or projection layer ### Target Model ``` Graph (data) ↓ Filter / Projection Layer ↓ UI (GraphicsWidget, dialogs, tables) ``` ### Key Principles * **Non-destructive operations** (visibility instead of deletion) * **Stateful filtering** (not one-off dialogs) * **Reusable logic** across UI components --- ## Constraints * Single-window application (Qt MainWindow) * Graph is currently owned by MainWindow * No multi-document interface (MDI) ### Design Decision (Current) * Subgraphs are handled as **filtered views (in-place)** * No new windows for subgraphs (yet) ### Future Direction * Explore **tab-based multi-graph UI** (preferred over multiple windows) --- ## Feature Breakdown --- # Feature 1 β€” Visualization & Decluttering (#209) βœ” ## Goal Make large graphs readable and explorable. ## Phases ### Phase 1 β€” Immediate UX * βœ” Focus on selection (#210) β€” `Graph::vertexFilterBySelection()`, `filterNodesBySelectionAct` (Ctrl+X, Ctrl+S) * βœ” Ego networks (k=1) (#211) β€” `Graph::vertexFilterByEgoNetwork()`, `filterNodesByEgoNetworkAct` (Ctrl+X, Ctrl+F) * βœ” Hide non-selected nodes (#212) β€” closed as duplicate of #210 * βœ” Edge filtering by weight (#213) β€” `Graph::edgeFilterByWeight()`, dialog + `Graph::edgeFilterReset()`, `editFilterEdgesRestoreAllAct` (Ctrl+E, Ctrl+R) **Cross-cutting UX (Phase 1):** * βœ” Non-destructive node filter restore β€” `Graph::vertexFilterRestoreAll()`, `filterNodesRestoreAllAct` (Ctrl+X, Ctrl+R) * βœ” Right-click on node auto-selects it before context menu opens (`GraphicsWidget::mousePressEvent`) * βœ” Ego network + Focus on Selection + Restore All Nodes wired into node right-click context menu ### Phase 2 β€” Layout Improvements (#214) * βœ” Improved force-directed layout * βœ” Ego-centered radial layout (#214) β€” `Graph::layoutEgoRadial()`, Layout menu (`Ctrl+Alt+E`) and node right-click context menu ### Phase 3 β€” Advanced Visualization * Community-based layouts * Edge bundling --- # Feature 2 β€” Filtering & Subgraphs (#215) ## Goal Enable exploration of large graphs through non-destructive filtering, attribute-based and structural queries, and subgraph extraction. ## Key Concept Introduce a **Graph View / Projection Layer**: ``` Graph (data) β†’ Filter / Projection Layer β†’ UI ``` The underlying graph remains unchanged; filtering operates on visibility state. ## Dependencies * Node/edge attribute system (#96, #130) * Structured data workflows (#223) * Graph faΓ§ade (WS3) * Parser improvements (WS4) --- ## Phases ### Phase 1 β€” Structural Filtering βœ” * βœ” Extend existing node filtering (centrality, degree) (#216) β€” centrality filter integrated into snapshot/restore history stack * βœ” Integrate edge filtering by weight β€” `Graph::edgeFilterReset()`, `editFilterEdgesRestoreAllAct` (`Ctrl+E, Ctrl+R`) * Filter edges by relation type β€” switch active relation hides cross-relation edges (existing behaviour); dedicated "show only relation X" action is future work ### Phase 2 β€” Attribute Filtering βœ” * βœ” Filter nodes and edges by attribute (#217): * `FilterCondition` struct (scope, key, op, value; `label()` for chip text) in `src/graph/filters/filter_condition.h` * `DialogFilterByAttribute` β€” scope radio (Nodes/Edges/Both), editable key combo populated from graph attributes, operator dropdown (`=` `β‰ ` `>` `<` `β‰₯` `≀` `contains`), value field; emits `userChoices(FilterCondition)` * `Graph::vertexFilterByAttribute(const FilterCondition &)` β€” non-destructive, snapshot/restore stack (`Ctrl+X, Ctrl+A`) * `Graph::edgeFilterByAttribute(const FilterCondition &)` β€” same snapshot/restore stack as node filters * Numeric-aware evaluation: compares as `double` when both sides parse; falls back to lexicographic; `contains` is case-insensitive substring * Filter: combo added to Control Panel (Network group) for quick access * Filter toolbar group: dedicated icons for each filter action ### Phase 3 β€” Unified Filtering System βœ” * βœ” Persistent **filter bar** (#219) β€” thin strip between toolbar and canvas, auto-shows/hides: * Each active condition shown as a chip: `Nodes: type = investor Γ—` * `FilterBarWidget` (`src/widgets/filterbarwidget.h/.cpp`): chips + "Clear all" button * Γ—-close enabled only on the most recently applied chip (stack limitation β€” arbitrary removal deferred to #221) * "Clear all" drains the full node snapshot stack and resets the edge filter * All filter actions emit a chip: centrality, ego network, selection, weight, attribute * Bar syncs with menu/toolbar restore actions (`Restore All Nodes`, `Restore All Edges`) * Styled via `default.qss` * Logical composition (AND/OR): deferred to #221 (query system); sequential stack already gives AND semantics by effect ### Phase 4 β€” Subgraph Extraction (#218) * Create subgraph from selection or current filtered view (#218) **Current approach:** * Subgraph = visible subset (same graph object); no copy created **Future options:** * Copy-out as independent `Graph` object (serialise visible nodes/edges) * Optional: open subgraph in a new window * Preferred long-term: tab-based multi-graph UI (switch between subgraphs) ### Phase 5 β€” Export Filtered / Extracted Graph (#220) * Export the currently visible (filtered) subset to any supported format (#220) * Save a named subgraph to file for later reload * Basis for save/load subgraph workflows (see Phase 6) ### Phase 6 β€” Persistent Named Subgraphs * Maintain multiple named subgraph views derived from the same base graph * Switch between subgraphs without reloading * Save and reload named subgraphs (persisted alongside or inside the graph file) * Relates to: tab-based multi-graph UI (Feature 1 Phase 3), MDI long-term plan ### Phase 7 β€” Query System (#221) * Visual no-code query builder β€” compose conditions across structural and attribute dimensions * Example query: `degree > 5 AND type = "investor"` * Arbitrary chip removal (pop any snapshot from the stack, replay remaining β€” prerequisite for full query semantics) * Optional DSL (long-term): text-based query language for scripting and the CLI tool --- # Feature 3 β€” Data Workflows (#223) ## Goal Treat graphs as structured datasets. --- ## Phases ### Phase 1 β€” Attribute Editing βœ” * βœ” Improve node/edge attribute editing (#224) β€” closes #224 * Phase A: Single-key node attribute API (`Graph::vertexCustomAttributeSet`, `vertexCustomAttributeRemove`) * Phase B: Edge custom attribute storage (`GraphVertex::m_outEdgeCustomAttributes`, `Graph::edgeCustomAttributesSet`) * Phase C: `DialogEdgeEdit` β€” edge properties dialog with custom key/value table (label, weight, color, attributes) * Phase D: GraphML roundtrip for edge custom attributes (`d2000+` key definitions on export; parser collects and stores on import) * Phase E: `Graph::vertexFilterByAttribute(key, value)` β€” Filter menu `Ctrl+X, Ctrl+A`; foundation for #217 ### Phase 2 β€” Table Views βœ” * βœ” Node/edge data table dock (#225) * `NodeTableModel` (`QAbstractTableModel`): caches all node rows; fixed columns (#, Label, Visible, Shape, Size, Color) plus dynamic custom attrs. Read-only cells (#, Visible, Shape) rendered with a muted background. `setData()` writes back via `vertexLabelSet`, `vertexSizeSet`, `vertexColorSet`, `vertexCustomAttributeSet`. * `EdgeTableModel`: caches edge rows for the current relation; fixed columns (Source, Target, Relation, Weight, Label, Color) plus dynamic custom attrs. Read-only cells (Source, Target, Relation) shaded. `setData()` writes back via `edgeWeightSet`, `edgeLabelSet`, `edgeColorSet`, `edgeCustomAttributesSet`. * `GraphTableWidget`: `QTabWidget` (Nodes | Edges); each tab has a live-search bar (QSortFilterProxyModel, all columns, case-insensitive), a Refresh button, and a sortable `QTableView` with inline editing on double-click. Emits `nodeSelected(int)` on row click. * `QDockWidget` at `BottomDockWidgetArea`; toggled by **Ctrl+T** (`Options` menu and `Edit` menu, `viewDataTableAct`). Auto-refreshes on file load and graph reset when visible. * `viewDataTableAct` has a dedicated `data_table_48px.svg` icon. ### Phase 3 β€” Structured Export βœ” * βœ” CSV / JSON export (#226) β€” closes #226 * `TableExport::toCSV(model, path)` / `TableExport::toJSON(model, path)` β€” free functions in `src/graph/io/table_export.*`; accept any `QAbstractItemModel*`; QtCore only, no UI. * **Export CSV** / **Export JSON** buttons in each tab of `GraphTableWidget`; export the proxy model (currently visible/search-filtered rows); tooltip makes the scope explicit. * `Network β†’ Export to other...` gains **Nodes as CSV**, **Edges as CSV**, **Nodes as JSON**, **Edges as JSON** β€” always export all rows (source model, unfiltered); models are refreshed from `activeGraph` before writing. * `GraphTableWidget::exportStatusMessage` signal wired to the MainWindow status bar. ### Phase 4 β€” Structured Import βœ” **Cross-cutting workflow unlocked by Phases 3+4:** spreadsheet-based bulk attribute editing (#232) β€” export table to CSV/JSON, edit freely in any spreadsheet tool, re-import. Each node/edge gets its own values; native columns (Label, Size, Color, Weight) are routed to their proper setters; read-only columns (Visible, Shape, Relation) are silently skipped; full roundtrip with no data loss or duplicate columns. * βœ” CSV / JSON attribute import (#227) β€” refs #169 * `TableImport::fromCSV(path)` / `TableImport::fromJSON(path)` β€” free functions in `src/graph/io/table_import.*`; return `ParsedTable{headers, rows, ok, errorString}`; QtCore only, no UI * `DialogImportAttributes` (`src/forms/dialogimportattributes.*`) β€” file-browse + preview table (first 8 rows) + column-mapping controls; parameterised by scope (Nodes / Edges) and format (CSV / JSON); `Import` button disabled until a valid file is loaded * Nodes scope: **ID column** combo + **Match by** radio (Node number / Node label) * Edges scope: **Source column** + **Target column** combos; auto-selects columns named `source`/`target`/`src`/`tgt`/`dest` * `Graph::vertexAttributesImport(headers, rows, idColumn, matchByLabel)` in `graph_vertex_style.cpp` β€” iterates rows, matches vertices by number or label, calls `vertexCustomAttributeSet()` for each non-ID column; returns matched count * `Graph::edgeAttributesImport(headers, rows, srcColumn, tgtColumn)` in `graph_edge_style.cpp` β€” matches edges by source/target number, merges new attributes via `edgeCustomAttributesSet()`; returns matched count * **Import CSV** / **Import JSON** buttons added to each tab of `GraphTableWidget`; invoke `DialogImportAttributes`, call the appropriate Graph method, refresh the table, emit `importStatusMessage`; MainWindow wires `importStatusMessage` β†’ status bar ### Phase 5 β€” Bulk Editing (#228) **Goal:** in-app same-value-to-many-targets operations β€” complementary to the spreadsheet workflow (#232) which already handles heterogeneous per-row editing. * Assign a single attribute value to all currently selected or filtered nodes/edges * Context menu entry: "Set attribute for selection…" * Bulk-edit dialog: attribute name field (combo from existing keys) + value field + scope (selection / visible subset) * Calls `vertexCustomAttributeSet` / `edgeCustomAttributesSet` for each target in a single transaction * Remove an attribute key from multiple nodes/edges at once * "Remove attribute from selection" β€” calls `vertexCustomAttributeRemove` per target * Add a new attribute key (with an optional default value) to all nodes/edges * Useful for initialising a new column before importing real values **Selection sources:** * Nodes/edges manually selected on canvas * Current filtered/visible subset (integrates with #215 filter stack) * Multi-row selection in the Data Table (#225) **UX rules:** * Operations must not alter the filter stack (non-destructive) * Table auto-refreshes after each bulk operation * All operations must be undoable (undo stack integration β€” dependency on future undo/redo system) ### Phase 6 β€” Transformations (#229) * Derived fields: compute a new attribute value from one or more existing attributes (e.g. `full_name = first + " " + last`) * Value normalization: min-max or z-score scaling of a numeric attribute across all nodes/edges * Type coercion: convert stored string values to canonical types (integer, float, boolean) β€” useful before filtering with numeric operators --- ## Cross-Cutting Systems ### Attribute System (#96) Foundation for: * filtering * editing * export/import ### Metadata System (#130) Defines: * ingestion * persistence * usage of attributes ### Temporal Data (#222) Future extension: * time-based filtering * timeline UI --- ## UI Evolution ### Current * Dialog-driven workflows ### Target * Persistent panels: * Filter panel * Table views * Attribute inspector --- ## Implementation Strategy ### Short-term * Reuse existing functionality * Refactor incrementally ### Mid-term * Introduce shared filtering logic * Decouple UI from graph operations ### Long-term * Introduce explicit projection layer * Support multiple graph views (tabs) --- ## Outcome SocNetV evolves into: * Graph visualization tool βœ” * Graph analysis tool βœ” * Graph exploration platform βœ” * Graph data workflow tool βœ” --- ## Notes for Contributors * Prefer non-destructive operations * Avoid duplicating filtering logic * Reuse attribute system consistently * Keep UI simple and incremental --- ## Documentation Debt β€” Website & Manual Updates The SocNetV website and manual live in a separate public repo at `~/socnetv/website/` (GitHub: `https://github.com/socnetv/website`). Core pages and manual content are under `src/content/docs/`. The following WS9 features are **implemented and shipped in v3.5** but not yet reflected in the website or manual: | Feature | Issue | Manual page(s) to update | |---|---|---| | Focus on selection (hide non-selected nodes) | #210 | Filtering & exploration section | | Ego network (k=1) filter | #211 | Filtering & exploration section | | Edge filtering by weight | #213 | Filtering & exploration section | | Non-destructive node filter restore (Restore All Nodes) | #216 | Filtering & exploration section | | Ego-centered radial layout | #214 | Layouts reference | | Right-click context menu: ego, focus, restore | #209/#211 | Usage / interaction reference | | Attribute-based filtering (nodes + edges) | #217 | Filtering & exploration section | | Filter bar with chips | #219 | Filtering & exploration section | | Edge Properties dialog with custom attributes | #224 | Attributes & metadata section (new) | | Node/edge data table dock (Ctrl+T) | #225 | Data management section (new) | | Structured CSV/JSON export | #226 | Data management section, Export reference | | Structured CSV/JSON import | #227 | Data management section, Import reference | | Spreadsheet bulk editing workflow | #232 | Data management section (new workflow page) | **Priority order for documentation:** 1. Data table dock + export/import (most user-facing, no equivalent docs exist) 2. Attribute-based filtering + filter bar (users will search for "how do I filter by attribute") 3. Spreadsheet workflow (new pattern, deserves its own how-to page) 4. Ego network, focus on selection, restore all nodes 5. Remaining layout and edge-weight filtering entries socnetv-app-39db829/docs/roadmaps/roadmap_io_parser_refactor.md000066400000000000000000000165121517721000100247510ustar00rootroot00000000000000# IO / Parser Refactor Roadmap ## Goal Reduce tight coupling between `Parser` (Qt signals/threads) and core `Graph` state, while preserving **identical parsing semantics** and **deterministic outputs** (golden parity required). ## Scope & Non-Goals * βœ… Mechanical extraction / boundary cleanup * βœ… Deterministic, non-UI entrypoints for loading * ❌ No changes to parsing behavior, graph semantics, or numeric outputs * ❌ No UI object construction in IO/algorithm slices (F4 boundary) --- # Historical Context Before WS4 * `Parser` mutated `Graph` via Qt signals. * GUI and CLI wired those signals to `Graph` mutators. * Mutation contract was implicit and spread across multiple locations. * Signal fan-out was the de facto API. This was fragile and hard to test deterministically. --- # Current Reality * `Parser` now mutates `Graph` exclusively via `IGraphParseSink`. * Legacy Parserβ†’Graph signal fan-out has been removed. * Headless and GUI loading paths are sink-backed and behaviorally identical. * Parsing still runs on a dedicated worker thread (GUI + CLI). --- # Parser β†’ Graph Mutation Contract (Post-P3) The mutation stream from `Parser` into `Graph` is now defined by `IGraphParseSink`. All graph/model mutations during parsing must flow exclusively through: * `addNewRelation(...)` * `setRelation(...)` * `createNode(...)` * `createNodeAtPosRandom(...)` * `createNodeAtPosRandomWithLabel(...)` * `createEdge(...)` * `removeDummyNode(...)` * `fileLoaded(...)` Legacy Qt mutation signals have been removed (P3.6–P3.7). ### Terminal signal * `Parser::finished(QString)` remains for lifecycle completion. * Preferred completion signal remains `Graph::signalGraphLoaded`. ### Completion condition (important) Headless loaders block until: * Preferred: `Graph::signalGraphLoaded` * Fallback: `Parser::finished` --- # Milestones ## P1 β€” Parser API + signal/slot contract documented βœ… DONE * Documented Parser mutation surface and completion conditions. * Historical note: completion condition = `Graph::signalGraphLoaded`, with `Parser::finished` as fallback. --- ## P2 β€” Centralize Parserβ†’Graph wiring helper βœ… DONE (superseded) * Implemented shared wiring helper to prevent drift (GUI + headless). NOTE: This helper was later removed once sink-based mutation became the sole plane (see P3.5). --- ## P3 β€” Introduce explicit sink-based mutation plane βœ… DONE ### P3.1 β€” Add mutation sink interface βœ… DONE * Added `IGraphParseSink` (signal-parity contract). ### P3.2 β€” Add Graph-backed sink adapter βœ… DONE * Added `GraphParseSinkGraph` forwarding sink β†’ Graph faΓ§ade mutators. ### P3.3 β€” Parser supports optional sink βœ… DONE * Added `Parser::setParseSink(...)` and `Parser::setOwnedParseSink(...)` for thread-safe lifetime. * Parser forwards mutation events to sink (preserving ordering and payload parity). ### P3.4 β€” Switch GUI load path to sink-backed mutation βœ… DONE * `Graph::loadFile(...)` configures an owned `GraphParseSinkGraph` on the Parser. * GUI no longer uses mutation signal wiring. ### P3.5 β€” Remove legacy Parserβ†’Graph wiring helper βœ… DONE * Deleted `src/graph/io/graph_parser_wiring.{h,cpp}` and removed from build files. ### P3.6 β€” Stop emitting legacy Parser mutation signals βœ… DONE * Removed `emit` for mutation signals (sink is the sole mutation plane). ### P3.7 β€” Remove legacy Parser mutation signals βœ… DONE * Deleted unused mutation signal declarations from `parser.h`. * Parsing mutations now flow exclusively through `IGraphParseSink` (GUI + headless). --- ## P4 β€” Golden IO coverage + roundtrip stability βœ… DONE Implemented via CLI golden harness: * `scripts/run_golden_compares.sh` * `io_roundtrip` kernel baselines in `src/tools/baselines/io_roundtrip/` Coverage includes: * GraphML * Pajek * Adjacency * DOT * DL * GML * EdgeList Formats without exporters are still covered: **β€œexport skipped”** outcome is baseline-locked. ### Notes Adding new IO coverage: 1. generate baseline JSON via `socnetv-cli --kernel io_roundtrip ... --dump-json ...` 2. commit baseline 3. add `run_case_io` entry in `run_golden_compares.sh` --- # P5 β€” Introduce ParseConfig boundary βœ… DONE Goal achieved: Reduce coupling between `Parser::load(...)` and internal parse handlers while preserving semantics. Implementation: * Introduced immutable: ``` struct ParseConfig ``` * Constructed at the start of `Parser::load(...)` from existing parameters. * Internal parse handlers progressively migrated to accept: ``` const ParseConfig& ``` instead of individual defaults. Important constraints preserved: * No logic changes * No ordering changes * No mutation stream changes * No sink payload changes Verification performed during migration: * build * `run_golden_compares.sh` * `run_benchmarks.sh` All outputs preserved. --- # P6 β€” Split parser.cpp by file type translation units βœ… DONE Goal: Reduce translation unit size and improve locality without changing behavior. ### Execution Performed incrementally, **one format per commit**, with: * build verification * golden regression tests * benchmark checks ### Final layout ``` src/parser/ parser_common.cpp parser_edgelist.cpp parser_adjacency.cpp parser_dl.cpp parser_pajek.cpp parser_graphml.cpp parser_gml.cpp parser_dot.cpp parser.cpp ``` ### Responsibilities **parser.cpp** * Parser constructor / destructor * sink wiring * `load()` dispatcher * minimal coordinator logic **parser_common.cpp** * shared helpers (`isComment`, future utilities) **parser_* files** * format-specific parsing logic ### Extracted formats * EdgeList (simple + weighted) * Adjacency / Sociomatrix / two-mode sociomatrix * UCINET DL * Pajek * GraphML * GML * GraphViz DOT ### Additional helper relocation During extraction, format-local helpers were moved to their respective files: Examples: * DL β†’ `createRandomNodes` * Pajek β†’ `normalizeQuotedIdentifier` * Adjacency β†’ `createEdgesForRow` `createNodeWithDefaults` `containsReservedKeywords` Shared helpers moved to `parser_common.cpp`. ### Build updates Each new translation unit added to: * `CMakeLists.txt` * `socnetv.pro` ### Code size policy result `parser.cpp` reduced from **~5500 LOC β†’ ~1200 LOC**. All parser TUs now comply with the project guideline: * preferred: 500–1500 LOC --- # Current Architectural State (Post-WS4) The parser architecture is now: ``` Graph (faΓ§ade) ↓ Parser ↓ IGraphParseSink ↓ Graph mutation layer ``` Internally: ``` Parser::load() β”œβ”€ parser_edgelist.cpp β”œβ”€ parser_adjacency.cpp β”œβ”€ parser_dl.cpp β”œβ”€ parser_dot.cpp β”œβ”€ parser_gml.cpp β”œβ”€ parser_pajek.cpp └─ parser_graphml.cpp ``` Key properties: * deterministic mutation stream * format-local code isolation * CLI + GUI share identical parsing logic * IO behavior locked by golden regression harness * performance guarded by benchmark suite --- # Future Work ## P7 β€” Optional: explicit parse transaction layer ⏳ OPTIONAL Goal: Make parsing explicitly transactional while preserving semantics. Concept: ``` ParseTransaction ``` * Created inside `Parser::load(...)` * Parser emits events into the transaction * Sink applies mutations Benefits: * clearer mutation lifecycle * easier debugging / instrumentation * possible replay or validation Proceed only if it simplifies the design without altering behavior. socnetv-app-39db829/docs/roadmaps/roadmap_mainwindow_decomposition.md000066400000000000000000000155331517721000100262130ustar00rootroot00000000000000# MainWindow Decomposition Roadmap (WS7) ## Goal Reduce `MainWindow` from a 15,000-line monolith into a thin coordinator that delegates UI concerns to focused sub-controllers and panel widgets, while preserving all existing behavior and user-visible functionality. --- ## Motivation After WS2, `Graph` is a clean faΓ§ade. The UI side has not yet received the same treatment. `MainWindow` currently: * owns all menus, toolbars, dock widgets, status bar, and dialogs * handles all user action slots (~200+ slots) * wires signals from `Graph` directly into UI updates * performs layout and panel management directly * contains display logic, chart config, and settings persistence This makes it hard to test UI behavior, hard to reason about responsibilities, and expensive to change any single feature. --- ## Non-Goals * No behavior changes. * No visual/UX changes. * No Qt version changes. * No new features. * No dependency on WS3 or WS4 completion (WS7 is structurally independent). --- ## Prerequisites * WS2 complete (Graph faΓ§ade stable). βœ… * CLI headless baseline exists (WS4/P2), to anchor regression during UI changes. --- ## Guiding Principles * Decompose by **responsibility**, not by line count. * Each extracted sub-controller or panel must compile independently. * No slot or signal signature changes during extraction (pure movement). * Golden + benchmark comparisons must pass after each milestone. * MainWindow remains the single owner of the `Graph` instance throughout WS7. --- ## Target Shape (End State) ``` MainWindow (thin coordinator, ~2000 lines) β”‚ β”œβ”€β”€ AppMenuController β€” menu bar construction + action routing β”œβ”€β”€ AppToolbarController β€” toolbar management β”œβ”€β”€ StatusBarController β€” status bar updates β”‚ β”œβ”€β”€ CanvasPanel β€” GraphicsWidget host + canvas interactions β”œβ”€β”€ LeftPanel β€” node/edge property panel β”œβ”€β”€ RightPanel / StatsPanel β€” analysis results + chart display β”‚ β”œβ”€β”€ DialogManager β€” dialog instantiation + lifecycle └── AppSettingsController β€” settings persistence + apply logic ``` MainWindow wires these together but does not own their internal logic. --- ## Milestones --- ### MW1 β€” Audit and Categorize MainWindow Slots **Objective:** understand what lives in MainWindow before touching anything. Deliverables: * Categorize all public/private slots by responsibility bucket: * Graph state reactions (graph loaded, vertex added, etc.) * UI state reactions (toolbar toggle, panel visibility, etc.) * Dialog launchers * Settings apply * Canvas interactions * Analysis result display * Identify which slots are purely UI (no Graph call) vs. coordinator (calls Graph faΓ§ade). * Note any slots that do too much (mixed UI + Graph + display). Definition of Done: * A documented slot inventory exists (can be a comment block in `mainwindow.h` or a separate doc). * No code changes. * Build passes. --- ### MW2 β€” Extract StatusBarController **Objective:** first small extraction β€” low risk, high signal. Deliverables: * `StatusBarController` class owns all status bar update logic. * MainWindow constructs it and passes it relevant signals. * All `statusBar()->showMessage(...)` and related calls routed through it. Definition of Done: * MainWindow no longer calls `statusBar()` directly except in initialization. * Golden + benchmarks pass. * No behavior change. --- ### MW3 β€” Extract AppMenuController **Objective:** separate menu construction from business logic. Deliverables: * `AppMenuController` owns menu bar construction and action object creation. * Actions are still connected to MainWindow slots (no slot moves yet). * Menu structure is driven by controller, not inline MainWindow code. Definition of Done: * Menu construction code no longer lives in `MainWindow::setupMenuBar()` or equivalent. * Actions remain triggerable; no UX change. * Golden + benchmarks pass. --- ### MW4 β€” Extract DialogManager **Objective:** centralize dialog lifecycle management. Deliverables: * `DialogManager` owns instantiation, `exec()`/`show()`, and cleanup of all dialog classes. * MainWindow calls `m_dialogManager->openNodeEditDialog(...)` instead of `new DialogNodeEdit(...)` inline. * DialogManager receives the minimum context it needs (Graph faΓ§ade reference, parent widget). Definition of Done: * No `new Dialog*` calls remain directly in MainWindow slot bodies. * All existing dialogs still open and function correctly. * Golden + benchmarks pass. --- ### MW5 β€” Extract AppSettingsController **Objective:** isolate settings persistence and apply logic. Deliverables: * `AppSettingsController` owns read/write of `QSettings`. * Owns the "apply settings to Graph + UI" logic currently scattered in MainWindow. * MainWindow calls `m_settingsController->apply(...)` on startup and settings dialog close. Definition of Done: * No direct `QSettings` calls remain in MainWindow outside of initialization. * Settings dialog still functions correctly. * Golden + benchmarks pass. --- ### MW6 β€” Extract CanvasPanel **Objective:** encapsulate canvas interactions. Deliverables: * `CanvasPanel` wraps `GraphicsWidget` and owns canvas-level slot handling: * zoom, fit, pan signals/slots * node/edge click/hover reactions * canvas resize handling * MainWindow holds a `CanvasPanel*` and forwards Graph signals to it. Definition of Done: * GraphicsWidget-related slots no longer live in MainWindow directly. * Canvas interactions unchanged. * Golden + benchmarks pass. --- ### MW7 β€” Reduce MainWindow to Coordinator **Objective:** ensure MainWindow is now a wiring layer only. Deliverables: * MainWindow connects sub-controllers to Graph signals and to each other. * No display logic, no dialog construction, no settings reads remain inline. * Slot count in MainWindow reduced to coordinator-level wiring only. * `mainwindow.cpp` target: under 3,000 lines. Definition of Done: * All prior golden + benchmark comparisons pass. * `mainwindow.h` / `mainwindow.cpp` reflect coordinator role clearly. * Responsibility boundaries documented. --- ## Regression Discipline For every milestone: * Golden metric comparisons must pass (algorithms are not touched, but load flow must stay intact). * Performance benchmarks must remain within tolerance. * Manual smoke test: load a dataset, run a centrality, open a dialog, export a report. --- ## Work Rules * Sub-controllers must not access `Graph` internals directly β€” only via the faΓ§ade API. * Sub-controllers may receive `Graph*` (faΓ§ade) as constructor parameter. * Sub-controllers must not own the `Graph` instance. * New files live under `src/ui/controllers/` and `src/ui/panels/`. * No new signals/slots added to `Graph` during WS7. --- ## Sequencing Note WS7 is independent of WS3, WS4, and WS5. It can proceed in parallel after WS4/P2. MW1 (audit) can begin immediately. socnetv-app-39db829/docs/roadmaps/roadmap_matrices_modernization.md000066400000000000000000000050201517721000100256420ustar00rootroot00000000000000# Matrices Modernization Roadmap (Skeleton) ## Goal Isolate matrix creation and computations into coherent types and services. ## Current Reality - Matrix-related logic is scattered and sometimes intertwined with Graph/UI. - Matrix algebra methods (inverse, power iteration, etc.) run synchronously on the main thread with no cancellation support and no progress reporting. - Callers cannot interrupt mid-computation; cancellation only works at the boundary between Graph-level methods, not inside linear algebra kernels. ## Known Issues (found during #52 Cancel-button fix) ### I1 β€” Matrix algebra methods are not cancellation-aware `Matrix::inverse()`, `Matrix::inverseByGaussJordanElimination()`, and `Matrix::powerIteration()` run to completion regardless of user cancel. Once `createMatrixAdjacency()` completes and hands off to these methods, there is no way to interrupt them. Affected callers: - `createMatrixAdjacencyInverse()` β†’ `invAM.inverse(AM)` or `invAM.inverseByGaussJordanElimination(AM)` - `centralityEigenvector()` β†’ `AM.powerIteration(...)` - `centralityInformation()` β†’ `invM.inverse(WM)` Fix direction: pass a cancellation-check callable into these methods, or split them into iterative steps that check a flag between iterations. ### I2 β€” `createMatrixAdjacencyInverse()` does not check cancel flag after `createMatrixAdjacency()` returns. Added a guard before the inversion call as a partial fix (cancels before algebra starts), but cannot cancel mid-inversion. See I1. ### I3 β€” `writeMatrix()` had missing `file.close()` on cancel paths Fixed during #52: all early-return cancel paths now close the file and return `false`. Callers in MainWindow now check the return value. ### I4 β€” No cancellation support in similarity/dissimilarity distance matrix computations (`Matrix::distancesMatrix()`). Called from `writeMatrix()` for MATRIX_DISTANCES_EUCLIDEAN/HAMMING/JACCARD/MANHATTAN/ CHEBYSHEV cases. These cases do not yet have cancel guards in `writeMatrix()`. ## Target Direction - Clear matrix types (adjacency, laplacian, distance, similarity, etc.) - Deterministic constructors - Cancellation-aware algebra kernels (at minimum: inverse, power iteration) - Progress reporting from inside long algebra operations - Headless tests ## Milestones - A1: Inventory matrix-related classes and their current callers - A2: Extract construction code paths - A3: Add golden outputs for small graphs - A4: Add cancellation support to Matrix algebra methods (see I1) - A5: Add cancel guards to remaining writeMatrix() cases (see I4) socnetv-app-39db829/docs/roadmaps/roadmap_testing_ci_regression.md000066400000000000000000000175141517721000100254740ustar00rootroot00000000000000# Testing / CI / Regression Roadmap ## Goal Prevent silent regressions during modernization. ## Current Reality - Manual comparisons exist; headless CLI now prints metrics. ## Target Direction - Golden outputs committed in-repo - A deterministic comparison tool - CI job that runs core datasets ## Milestones - T1: Define output schemas (metrics + per-node vectors) - T2: Add golden baselines for a small suite of datasets - T3: Add comparison mode (fail on mismatch) - T4: Integrate into CI (GitHub Actions) ## Work Rules - Keep outputs stable (version schemas when changing format). --- ## Updated Current State (Post-WS1/WS2/WS4) The regression harness is now a first-class part of the modernization effort. ### CLI faΓ§ade + kernels `socnetv-cli` is a thin faΓ§ade (argument parsing + dispatch only). All deterministic logic lives in kernel translation units under: ``` src/tools/cli/kernels/ ``` Current kernels: ``` kernel_distance_v1.cpp kernel_reachability_v2.cpp kernel_walks_v3.cpp kernel_prominence_v4.cpp kernel_io_roundtrip_v5.cpp ``` The CLI supports: - deterministic metric printing (`cli::printKV`) - JSON dump mode (`--dump-json`) - JSON compare mode (`--compare-json`) - benchmarks (distance kernel via `--bench`) - an IO roundtrip kernel (`--kernel io_roundtrip`) - strict mode for timing guardrails (`--strict`, used by benchmarking scripts) ### Headless loading is unified and deterministic Both GUI and CLI use the same IO mutation pipeline introduced in WS4: ``` Parser ↓ IGraphParseSink ↓ Graph ``` The CLI loads graphs through: ``` tools/headless_graph_loader.h ``` This loader blocks on: - Preferred: `Graph::signalGraphLoaded` - Fallback: `Parser::finished` ### Regression scripts (currently active) 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/ ``` ### What is already implemented vs the original milestones The original milestone plan is still valid, but several items are already implemented: - **T1 (schemas):** JSON-based outputs exist and are baseline-locked via `--dump-json` + `--compare-json`. - **T2 (golden suite):** Golden baselines exist for multiple kernels and multiple IO formats (including export-skipped locking where exporters are missing). - **T3 (comparison mode):** JSON comparison exists (`--compare-json`) and is exercised by `run_golden_compares.sh`. - **T4 (CI):** still pending (CI integration is the main remaining step). --- ## Next Steps (WS6 priority) WS6 should prioritize expanding **headless feature coverage** and making the harness easier to run locally. CI integration is explicitly a later step. ### 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: - clustering metrics / coefficients - 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 - **kernel_attribute_import_v7** β€” 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.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.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) --- ### 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. --- ## Notes - 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. ``` ### One small thing to fix later (not required now) Your benchmark script output still shows `BUILD_TYPE=Debug` even when you run the Release binary. That’s a script-reporting detail (not a functional bug), but it can confuse future contributors. It’s a good tiny WS6 task. If you paste me your current `docs/roadmaps/roadmap_testing_ci_regression.md` file path/contents (if it differs from the skeleton you showed), I can also produce a `diff`-style patch, but you don’t need to β€” the above is ready to drop in. socnetv-app-39db829/docs/roadmaps/roadmap_ui_graph_facade.md000066400000000000000000000115221517721000100241560ustar00rootroot00000000000000# Graph as FaΓ§ade / Coordinator β€” WS2 (COMPLETED) ## Purpose of This Document This document records the detailed execution of **WS2**: turning `Graph` from a monolithic algorithm host into a thin faΓ§ade / coordinator. WS2 is complete. This document now serves as an architectural reference and historical record. --- ## Goal (Achieved) Make `Graph` a thin coordinator that: * 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 --- ## Non-Goals (Respected) * No behavior changes * No numeric drift * No performance regressions * No domain model rewrite (WS3) * No IO/parser redesign (WS4) All changes were mechanical, incremental, and verified. --- ## Original Shape (Before WS2) Historically, `Graph` mixed: * UI coordination (Qt signals/slots to `MainWindow`, `GraphicsWidget`, `Parser`) * storage + graph invariants * algorithms (distances, centralities, walks, layouts, generators) * exports/reporting * crawler/web functionality * caches and analysis state `graph.cpp` was a monolithic compilation unit. --- ## Final Shape (After WS2) ### Structural Result `graph.cpp` now contains only: * `Graph::Graph(...)` * `Graph::clear(...)` All other functionality lives in dedicated translation units under: ``` src/graph/ ``` Grouped by responsibility: * distances/ * centrality/ * clustering/ * cohesion/ * similarity/ * layouts/ * generators/ * prominence/ * matrices/ * reporting/ * storage/ * relations/ * filters/ * core/ * io/ * ui/ Each slice: * Compiles independently * Is listed explicitly in `GRAPH_SOURCES` * Passes golden comparisons * Remains within benchmark guardrails --- ## Milestones Completed ### βœ… F0 β€” FaΓ§ade Contract Defined * Defined what UI/CLI are allowed to call. * Marked faΓ§ade region in `graph.h`. * Internal helpers are no longer considered public API. --- ### βœ… F1 β€” Engine Boundary Tightened * `DistanceEngine` extracted. * No direct access to `Graph` internals. * Narrow helper surface for SSSP context. * Golden parity verified. --- ### βœ… F2 β€” Mechanical Extraction of `graph.cpp` * No logic changes. * No signature changes. * Pure translation unit slicing. * Verified after each slice (golden + perf). This was the structural core of WS2. --- ### βœ… F3 β€” UI Boundary Tightened Audit of: * `MainWindow` * `GraphicsWidget` * Dialog forms * Graphics items Findings: * UI interactions are centralized. * No UI component accesses `Graph` internals directly. * Thread affinity (`thread()`, `moveToThread`) was the only non-faΓ§ade pattern. Actions: * Introduced faΓ§ade wrappers for thread management. * Eliminated direct UI access to QObject internals. Result: UI interacts with `Graph` strictly through faΓ§ade-supported methods. --- ### βœ… F4 β€” Algorithm / UI Separation Enforced Formal boundary: Algorithm slices under `src/graph/`: * Compute data only * May use QtCore * Do **not** construct QtWidgets / QtCharts objects * Do **not** emit UI signals UI orchestration under `src/graph/ui/`: * Builds Qt UI objects * Handles PNG export * Emits signals to `MainWindow` Applied to prominence distribution subsystem. All QtCharts construction removed from algorithm slice. Golden and benchmark parity confirmed. --- ## Architectural State After WS2 The runtime flow for analytics now follows: ``` UI ↓ Graph (faΓ§ade) ↓ Algorithm slice / engine ↓ UI faΓ§ade (if rendering required) ↓ Signal to MainWindow ``` `Graph` is: * State holder * Invariant guardian * Delegation layer * UI signal coordinator It is no longer an algorithm host in the structural sense. --- ## What WS2 Enables WS2 establishes: * Clear separation between computation and presentation * Stable faΓ§ade surface for future refactors * Safer ground for: * WS3 (Domain model split) * WS4 (IO boundary clarification) * Further engine extraction WS2 is considered structurally complete. Future work should build on this boundary rather than weaken it. --- You’re right β€” it should stay (clearly labeled as **optional / later**, not WS2 work). Add this at the end: --- ## Optional Future Step (Not Part of WS2) If/when we want to reduce the β€œhelper surface” between `Graph` and engines: ### Style B β€” Engine-Facing Context Object Idea: * Introduce a small `GraphAccess` / `GraphSSSPContext` struct that: * exposes only what a specific engine needs * is constructed by `Graph` * is passed into the engine as a dependency Benefits: * fewer tiny forwarding methods on `Graph` * reduces the need for `friend` * clearer engine API boundary * simplifies unit testing of engines This is intentionally deferred until after WS2 stabilization and should be considered only when it produces clear maintenance wins without behavior/performance risk. --- socnetv-app-39db829/man/000077500000000000000000000000001517721000100150645ustar00rootroot00000000000000socnetv-app-39db829/man/socnetv.1000066400000000000000000000145061517721000100166350ustar00rootroot00000000000000.TH socnetv 1 "Apr 2026" "SocNetV-3.5" "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-app-39db829/scripts/000077500000000000000000000000001517721000100160005ustar00rootroot00000000000000socnetv-app-39db829/scripts/README__run_benchmarks.md000066400000000000000000000167321517721000100225100ustar00rootroot00000000000000# 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 three benchmark families. By default (`--type all`), it runs only the **baseline-enforced** families: - Distance / Centrality - IO Roundtrip The third 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. ## 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, prominence) 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 io` β†’ run only IO timing benchmarks * `--type clustering` β†’ run only optional clustering timing probes * `--type all` β†’ run the baseline-enforced benchmark families only (`distance` + `io`) (default) Examples: ```bash ./scripts/run_benchmarks.sh --type distance ./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**. They should only be re-recorded when: * A deliberate algorithmic improvement is made * A known performance bug is fixed * The build environment materially changes 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-app-39db829/scripts/entitlements.plist000066400000000000000000000003701517721000100215700ustar00rootroot00000000000000 com.apple.security.app-sandbox socnetv-app-39db829/scripts/flathub/000077500000000000000000000000001517721000100174255ustar00rootroot00000000000000socnetv-app-39db829/scripts/flathub/org.socnetv.SocNetV.desktop000066400000000000000000000003771517721000100246160ustar00rootroot00000000000000[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-app-39db829/scripts/flathub/org.socnetv.SocNetV.svg000066400000000000000000000154461517721000100237470ustar00rootroot00000000000000 socnetv-app-39db829/scripts/flathub/org.socnetv.SocNetV.yaml000066400000000000000000000006261517721000100241040ustar00rootroot00000000000000app-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-app-39db829/scripts/innosetup.iss000066400000000000000000000071621517721000100205520ustar00rootroot00000000000000#define APPTITLE "Social Network Visualizer" #define APPSHORT "SocNetV" #define RELEASEFOLDER "release\" #define EXECUTABLE APPSHORT + ".exe" #define NUMERICVERSION GetVersionNumbersString(RELEASEFOLDER+EXECUTABLE) #define VERSION "3.5" #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-app-39db829/scripts/lib/000077500000000000000000000000001517721000100165465ustar00rootroot00000000000000socnetv-app-39db829/scripts/lib/find_socnetv_cli.sh000077500000000000000000000016361517721000100224230ustar00rootroot00000000000000#!/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-app-39db829/scripts/perf_baselines/000077500000000000000000000000001517721000100207615ustar00rootroot00000000000000socnetv-app-39db829/scripts/perf_baselines/linux-x86_64/000077500000000000000000000000001517721000100230545ustar00rootroot00000000000000socnetv-app-39db829/scripts/perf_baselines/linux-x86_64/perf_expected.env000066400000000000000000000010151517721000100264000ustar00rootroot00000000000000# 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=283 EXP_BA500_M3_C1_W0_MEDIAN_MS=679 EXP_DIST_GRAPHML_1000N_10000A_C0_W0_MEDIAN_MS=28423 EXP_DIST_GRAPHML_1000N_10000A_C1_W0_MEDIAN_MS=47020 EXP_EIES48_T1_C1_W1_MEDIAN_MS=162 EXP_EIES48_T2_C1_W1_MEDIAN_MS=192 EXP_IO_GRAPHML_1000N_10000A_MEDIAN_MS=219 EXP_IO_GRAPHML_2000N_40000E_MEDIAN_MS=892 EXP_IO_PAJ_BA500_MEDIAN_MS=31 socnetv-app-39db829/scripts/perf_baselines/macos-arm64/000077500000000000000000000000001517721000100230125ustar00rootroot00000000000000socnetv-app-39db829/scripts/perf_baselines/macos-arm64/perf_expected.env000066400000000000000000000010131517721000100263340ustar00rootroot00000000000000# 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=158 EXP_BA500_M3_C1_W0_MEDIAN_MS=457 EXP_DIST_GRAPHML_1000N_10000A_C0_W0_MEDIAN_MS=16782 EXP_DIST_GRAPHML_1000N_10000A_C1_W0_MEDIAN_MS=30940 EXP_EIES48_T1_C1_W1_MEDIAN_MS=100 EXP_EIES48_T2_C1_W1_MEDIAN_MS=112 EXP_IO_GRAPHML_1000N_10000A_MEDIAN_MS=138 EXP_IO_GRAPHML_2000N_40000E_MEDIAN_MS=497 EXP_IO_PAJ_BA500_MEDIAN_MS=12 socnetv-app-39db829/scripts/perf_baselines/macos-m5/000077500000000000000000000000001517721000100224025ustar00rootroot00000000000000socnetv-app-39db829/scripts/perf_baselines/macos-m5/perf_expected.env000066400000000000000000000003451517721000100257330ustar00rootroot00000000000000# 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-app-39db829/scripts/perf_expected.env000066400000000000000000000003451517721000100213310ustar00rootroot00000000000000# 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-app-39db829/scripts/run_benchmarks.sh000077500000000000000000000276011517721000100213460ustar00rootroot00000000000000#!/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 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|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|io|clustering) ;; *) echo "ERROR: invalid --type: $BENCH_TYPE (expected: all|distance|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 allowed=$(( expected + (expected / 10) )) if within_threshold "$load_ms" "$expected"; then echo "OK: ${load_ms}ms <= ${allowed}ms (baseline ${expected}ms, +10%)" return 0 else echo "FAIL: ${load_ms}ms > ${allowed}ms (baseline ${expected}ms, +10%)" [[ "$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 # allowed threshold = expected + 10% allowed=$(( expected + (expected / 10) )) if within_threshold "$median" "$expected"; then echo "OK: ${median}ms <= ${allowed}ms (baseline ${expected}ms, +10%)" return 0 else echo "FAIL: ${median}ms > ${allowed}ms (baseline ${expected}ms, +10%)" [[ "$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" == "io" ]] return fi return 1 } # ---------------- benchmark cases ---------------- # NOTE: CLI enforces --bench only for --kernel distance (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 # ---------------- 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-app-39db829/scripts/run_golden_compares.sh000077500000000000000000000237311517721000100223720ustar00rootroot00000000000000#!/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 # # 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) # # 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="${ROOT_DIR}/src/tools/baselines" 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" 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_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}/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}/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}/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}/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" # 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" 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-app-39db829/scripts/run_golden_io_roundtrip.sh000077500000000000000000000042121517721000100232670ustar00rootroot00000000000000#!/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-app-39db829/scripts/run_io_roundtrip_shipped_datasets.sh000077500000000000000000000021561517721000100253500ustar00rootroot00000000000000#!/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-app-39db829/scripts/travis_before.sh000066400000000000000000000044051517721000100211710ustar00rootroot00000000000000#!/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-app-39db829/scripts/travis_install_deps.sh000066400000000000000000000057501517721000100224140ustar00rootroot00000000000000#!/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-app-39db829/scripts/travis_make_build_linux.sh000066400000000000000000000106671517721000100232510ustar00rootroot00000000000000#!/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-app-39db829/scripts/travis_make_build_macos.sh000066400000000000000000000111031517721000100231760ustar00rootroot00000000000000#!/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-app-39db829/scripts/travis_upload_packages.sh000066400000000000000000000024331517721000100230500ustar00rootroot00000000000000#!/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-app-39db829/scripts/update_translations.sh000077500000000000000000000132621517721000100224260ustar00rootroot00000000000000#!/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-app-39db829/socnetv.appdata.xml000066400000000000000000000043201517721000100201240ustar00rootroot00000000000000 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-app-39db829/socnetv.desktop000066400000000000000000000004631517721000100173700ustar00rootroot00000000000000[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-app-39db829/socnetv.pro000077500000000000000000000216371517721000100165300ustar00rootroot00000000000000lessThan(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.5 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 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 HEADERS += src/mainwindow.h \ src/texteditor.h \ src/graph.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/dialogimportattributes.h \ src/graph/filters/filter_condition.h \ src/graph/io/table_export.h \ src/graph/io/table_import.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/global.h SOURCES += src/main.cpp \ src/mainwindow.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/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/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_prestige.cpp \ src/graph/prominence/graph_prominence_distribution.cpp \ src/graph/matrices/graph_matrix_adjacency.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/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/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 RESOURCES = src/images.qrc \ src/data.qrc # This is Windows only win32 { VERSION = 3.5.0.1 # major.minor.patch.build VERSION_PE_HEADER = 3.5 # 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-app-39db829/socnetv.spec000066400000000000000000000213611517721000100166510ustar00rootroot00000000000000# # 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.5 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 %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 * 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-app-39db829/src/000077500000000000000000000000001517721000100151005ustar00rootroot00000000000000socnetv-app-39db829/src/chart.cpp000066400000000000000000000246641517721000100167210ustar00rootroot00000000000000/** * @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 Chart::Chart(QWidget *parent) : QChartView (parent ), m_chart(new QChart) { qDebug() << "Constructing a Chart"; setChart(m_chart); m_chart->setAnimationOptions(QChart::SeriesAnimations); } Chart::~Chart(){ qDebug()<< "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) { qDebug() << "Adding a series to chart" ; if (series) { m_chart->addSeries(series); qDebug() << "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); qDebug() << "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) { qDebug() <<"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() { qDebug() <<"Removing all series... " ; if ( ! m_chart->series().empty() ) { m_chart->removeAllSeries(); qDebug() <<"series count:" << m_chart->series().size(); } } /** * @brief Creates default axes. Must be called AFTER loading a series to the chart */ void Chart::createDefaultAxes(){ qDebug() << "Creating default axes..." ; m_chart->createDefaultAxes(); } QList Chart::axes(Qt::Orientations orientation, QAbstractSeries *series) const { qDebug() << "Chart::axes()" ; if (series == Q_NULLPTR) { qDebug() << "Chart::axes() - no series defined" ; return m_chart->axes(orientation); } else { qDebug() << "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(){ qDebug() << "Removing all axes"; if ( ! axes(Qt::Horizontal).isEmpty() ) { qDebug() << "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() ) { qDebug() << "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) { qDebug()<<"Adding axis X to chart"; addAxis(axis, Qt::AlignBottom); qDebug()<<"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) { qDebug()<<"Adding axis Y to chart"; addAxis(axis, Qt::AlignLeft); qDebug()<<"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) { qDebug()<< "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){ qDebug()<< "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){ qDebug()<< "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){ qDebug()<< "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){ qDebug()<< "Setting axis X min..."; m_chart->axes(Qt::Vertical).first()->setMin(min); } void Chart::setAxisXLabelsAngle (const int &angle){ qDebug()<< "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){ qDebug()<< "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){ qDebug()<< "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){ qDebug()<< "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){ qDebug()<< "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){ qDebug()<< "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){ qDebug()<< "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){ qDebug()<< "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) { qDebug()<< "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) { qDebug()<< "Setting chart background pen..."; m_chart->setBackgroundPen(pen); } /** * @brief Set the theme of the QChart * @param theme */ void Chart::setTheme(QChart::ChartTheme theme) { qDebug()<< "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) { qDebug()<< "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){ qDebug()<< "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){ qDebug() << "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() { qDebug()<< "Setting a simple theme to chart axes..."; setAxisXLabelFont(); setAxisXLinePen(); setAxisXGridLinePen(); setAxisYLabelFont(); setAxisYLinePen(); setAxisYGridLinePen(); setMargins(QMargins()); } void Chart::resetToTrivial() { qDebug()<< "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-app-39db829/src/chart.h000066400000000000000000000055661517721000100163660ustar00rootroot00000000000000/** * @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-app-39db829/src/data.qrc000066400000000000000000000030321517721000100165160ustar00rootroot00000000000000 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/Zachary_Karate_Club.dl socnetv-app-39db829/src/data/000077500000000000000000000000001517721000100160115ustar00rootroot00000000000000socnetv-app-39db829/src/data/Benchmark_BA_Directed_N500_m3.paj000066400000000000000000001251151517721000100236520ustar00rootroot00000000000000*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-app-39db829/src/data/Bernard_Killworth_Fraternity.dl000066400000000000000000000405331517721000100241620ustar00rootroot00000000000000DL 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-app-39db829/src/data/Campnet.paj000066400000000000000000000030371517721000100200770ustar00rootroot00000000000000*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-app-39db829/src/data/ErdosRenyi_N10_E10.graphml000066400000000000000000000102471517721000100224770ustar00rootroot00000000000000 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-app-39db829/src/data/Freeman_34_possible_graphs_with_N_5_multirelational.paj000066400000000000000000000047511517721000100306640ustar00rootroot00000000000000*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-app-39db829/src/data/Freeman_EIES_network_48actors_Acquaintanceship_at_time_1.dl000066400000000000000000000123171517721000100313020ustar00rootroot00000000000000dl 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-app-39db829/src/data/Freeman_EIES_network_48actors_Acquaintanceship_at_time_2.dl000066400000000000000000000143641517721000100313070ustar00rootroot00000000000000dl 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-app-39db829/src/data/Freeman_EIES_network_48actors_Messages.dl000066400000000000000000000072011517721000100256430ustar00rootroot00000000000000dl 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-app-39db829/src/data/Freeman_EIES_networks_32actors.dl000066400000000000000000000206331517721000100241740ustar00rootroot00000000000000DL 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-app-39db829/src/data/Galaskiewicz_CEOs_and_clubs_affiliation_network_data.2sm000066400000000000000000000014131517721000100310420ustar00rootroot000000000000000 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-app-39db829/src/data/Herschel_Graph.paj000066400000000000000000000013731517721000100213670ustar00rootroot00000000000000*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-app-39db829/src/data/Knoke_Bureaucracies_Network.pajek000066400000000000000000000015511517721000100244440ustar00rootroot00000000000000*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-app-39db829/src/data/Krackhardt_High-tech_managers.paj000066400000000000000000000065341517721000100243300ustar00rootroot00000000000000*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-app-39db829/src/data/Krackhardt_Kite_N10.paj000066400000000000000000000003531517721000100221560ustar00rootroot00000000000000*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-app-39db829/src/data/Mexican_Power_Network_1940s.lst000066400000000000000000000004771517721000100236560ustar00rootroot0000000000000018 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-app-39db829/src/data/Padgett_Florentine_Families.paj000066400000000000000000000033521517721000100240760ustar00rootroot00000000000000*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-app-39db829/src/data/Petersen_Graph.paj000066400000000000000000000014361517721000100214170ustar00rootroot00000000000000*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-app-39db829/src/data/Sampson_Monks_N18.net000066400000000000000000000010211517721000100217300ustar00rootroot00000000000000*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-app-39db829/src/data/SmallWorld_N10_E12.graphml000066400000000000000000000114171517721000100224760ustar00rootroot00000000000000 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-app-39db829/src/data/Stephenson_Zelen_40_AIDS_patients_sex_contact.paj000066400000000000000000000044651517721000100274050ustar00rootroot00000000000000*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-app-39db829/src/data/Stephenson_Zelen_5actors_6edges_IC_test_dataset.paj000066400000000000000000000006331517721000100300060ustar00rootroot00000000000000*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-app-39db829/src/data/Stephenson_Zelen_Dunbar_Dunbar_Gelada_baboon_colony_H22a_IC.paj000066400000000000000000000022131517721000100320430ustar00rootroot00000000000000*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-app-39db829/src/data/Stokman_Ziegler_Corporate_Interlocks_Netherlands.dl000066400000000000000000000013631517721000100301740ustar00rootroot00000000000000DL 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-app-39db829/src/data/Stokman_Ziegler_Corporate_Interlocks_West_Germany.dl000066400000000000000000000013101517721000100303210ustar00rootroot00000000000000DL 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-app-39db829/src/data/Thurman_Office_Networks_Coalitions.dl000066400000000000000000000022521517721000100253040ustar00rootroot00000000000000DL 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-app-39db829/src/data/TinyAdj_Dir_N3_E4_clucof.adj000066400000000000000000000000351517721000100230520ustar00rootroot000000000000000 | 0 | 1 1 | 0 | 1 1 | 1 | 0socnetv-app-39db829/src/data/TinyAdj_Undir_N3.adj000066400000000000000000000001061517721000100215310ustar00rootroot00000000000000# unweighted, undirected, symmetric, diagonal zeros 0 1 0 1 0 1 0 1 0 socnetv-app-39db829/src/data/TinyAdj_Weighted_Dir_N3.adj000066400000000000000000000001171517721000100230100ustar00rootroot00000000000000# weighted, non-symmetric β†’ directed, diagonal zeros 0 2.5 0 0 0 1.5 3.0 0 0 socnetv-app-39db829/src/data/TinyDirChain_N3.paj000066400000000000000000000000601517721000100213660ustar00rootroot00000000000000*Vertices 3 1 "A" 2 "B" 3 "C" *Arcs 1 2 1 2 3 1 socnetv-app-39db829/src/data/TinyDir_N2_E1_Attributes.graphml000066400000000000000000000045151517721000100240460ustar00rootroot00000000000000 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-app-39db829/src/data/TinyDir_N3_REL2.graphml000066400000000000000000000057231517721000100221020ustar00rootroot00000000000000 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-app-39db829/src/data/TinyEdgeList_Simple_Dir_N3.lst000066400000000000000000000000611517721000100235450ustar00rootroot00000000000000# unweighted Directed chain of length 2. 1 2 2 3 socnetv-app-39db829/src/data/TinyEdgeList_Symmetric_Undir_N3.lst000066400000000000000000000000241517721000100246320ustar00rootroot00000000000000# 1 2 2 1 2 3 3 2 socnetv-app-39db829/src/data/TinyEdgeList_Weighted_Dir_N3.wlst000066400000000000000000000000671517721000100242510ustar00rootroot00000000000000# Format: source target weight 1 2 2.5 2 3 1.5 3 1 3.0 socnetv-app-39db829/src/data/TinyGML_Dir_N3.gml000066400000000000000000000003231517721000100211310ustar00rootroot00000000000000graph [ 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-app-39db829/src/data/TinyGML_Weighted_Dir_N3.gml000066400000000000000000000004061517721000100227530ustar00rootroot00000000000000graph [ 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-app-39db829/src/data/TinyGraphML_Dir_N3.graphml000066400000000000000000000046431517721000100226700ustar00rootroot00000000000000 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-app-39db829/src/data/TinyGraphML_Undir_N3.graphml000066400000000000000000000043371517721000100232330ustar00rootroot00000000000000 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-app-39db829/src/data/TinyGraphML_Weighted_Dir_N3.graphml000066400000000000000000000013361517721000100245040ustar00rootroot00000000000000 2.5 1.5 3.0 socnetv-app-39db829/src/data/TinyGraphviz_Dir_N3.dot000066400000000000000000000001361517721000100223150ustar00rootroot00000000000000# Directed, No weights, no attributes digraph TinyGraphviz_Dir_N3 { 1 -> 2; 2 -> 3; } socnetv-app-39db829/src/data/TinyGraphviz_Undir_N3.dot000066400000000000000000000000701517721000100226550ustar00rootroot00000000000000graph TinyGraphviz_Undir_N3 { 1 -- 2; 2 -- 3; } socnetv-app-39db829/src/data/TinyGraphviz_Weighted_Dir_N3.dot000066400000000000000000000001641517721000100241360ustar00rootroot00000000000000digraph TinyGraphviz_Weighted_Dir_N3 { 1 -> 2 [weight=2.5]; 2 -> 3 [weight=1.5]; 3 -> 1 [weight=3.0]; } socnetv-app-39db829/src/data/TinyPath_N3_E2.paj000066400000000000000000000000621517721000100211310ustar00rootroot00000000000000*Vertices 3 1 "A" 2 "B" 3 "C" *Edges 1 2 1 2 3 1 socnetv-app-39db829/src/data/Wasserman_Faust_7actors_star_circle_line_graphs.paj000066400000000000000000000014621517721000100302010ustar00rootroot00000000000000*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-app-39db829/src/data/Wasserman_Faust_Countries_Trade_Data_Basic_Manufactured_Goods.pajek000066400000000000000000000035561517721000100331750ustar00rootroot00000000000000*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-app-39db829/src/data/Zachary_Karate_Club.dl000066400000000000000000000111411517721000100221650ustar00rootroot00000000000000DL 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-app-39db829/src/engine/000077500000000000000000000000001517721000100163455ustar00rootroot00000000000000socnetv-app-39db829/src/engine/distance_engine.cpp000066400000000000000000001521221517721000100221730ustar00rootroot00000000000000/** * @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 #include #include 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; }; 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; }; 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) { qDebug() << "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; 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, csssp, sink); if (sink.progressCanceled()) { qDebug() << "DistanceEngine::compute() - canceled. Skipping finalize."; sink.progressKill(); return; } // ---- Finalization: connectivity scan + aggregation ---- finalize(computeCentralities, dropIsolates, ds, csfin, sink); } graph.calculatedDistances = true; qDebug() << "Graph::graphDistancesGeodesic()- FINISHED computing distances"; sink.progressKill(); } 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.progressCreate(ds.N, ds.pMsg); graph.setSymmetricCached(graph.isSymmetric()); if (ds.E == 0) { for (ds.it = graph.verticesBegin(); ds.it != graph.verticesEnd(); ++ds.it) { for (ds.it1 = graph.verticesBegin(); ds.it1 != graph.verticesEnd(); ++ds.it1) { // Set all pair-wise distances to RAND_MAX (*ds.it)->setDistance((*ds.it1)->number(), RAND_MAX); // Set all pair-wise shortest-path counts (sigmas) to 0 (*ds.it)->setShortestPaths((*ds.it1)->number(), 0); } } 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) { // All pair-wise distances are set to RAND_MAX by default // inside GraphVertex::distance() // so we don't need to explicitly set them here. // We just clear distance hashmap of each actor. (*ds.it)->clearDistance(); // Set all pair-wise shortest-path counts (sigmas) to 0 // (*it)->setShortestPaths((*it1)->number(), 0); (*ds.it)->clearShortestPaths(); 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, CentralityScratchSSSP &csssp, IDistanceProgressSink &sink) { qDebug() << "*********** MAIN LOOP: " "for every s in V solve the Single Source Shortest Path (SSSP) problem..."; for (ds.it = graph.verticesBegin(); ds.it != graph.verticesEnd(); ++ds.it) { ds.s = (*ds.it)->number(); ds.si = graph.vertexIndexByNumber(ds.s); ds.distances_sum_for_s = 0; qDebug() << "***** PHASE 1 (SSSP): " << "Source vertex s" << ds.s << "vpos" << ds.si; sink.progressUpdate(++ds.progressCounter); if (sink.progressCanceled()) { qDebug() << "DistanceEngine::runAllSources() - canceled by user. Aborting."; return; } if (!(*ds.it)->isEnabled()) { qDebug() << "***** PHASE 1 (SSSP): s" << ds.s << "disabled. SKIP/CONTINUE"; continue; } if (computeCentralities) { qDebug() << "***** PHASE 1 (SSSP): " "Empty Stack which will return vertices in " "order of their (non increasing) distance from s ..."; //- Complexity linear O(n) graph.ssspStackClear(); qDebug() << "***** PHASE 1 (SSSP): " "...and for each vertex: empty list Ps of predecessors"; for (ds.it1 = graph.verticesBegin(); ds.it1 != graph.verticesEnd(); ++ds.it1) { (*ds.it1)->clearPs(); } graph.ssspNthOrderClear(); } qDebug() << "***** PHASE 1 (SSSP): " "Call BFS or dijkstra for s" << ds.s << " vpos " << ds.si << " to compute distance and shortest paths to every vertex t"; if (!considerWeights) { bfsSSSP(ds.s, ds.si, computeCentralities, dropIsolates); } else { dijkstraSSSP(ds.s, ds.si, computeCentralities, inverseWeights, dropIsolates); } qDebug() << "***** PHASE 1 (SSSP): " "FINISHED BFS / DIJKSTRA ALGORITHM. " "Continuing to calculate centralities"; if (computeCentralities) { qDebug() << "***** PHASE 2 (CENTRALITIES): " "s" << ds.s << "vpos" << ds.si << "CC" << csssp.CC; // Compute Power Centrality // In = [ 1/(N-1) ] * ( Nd1 + Nd2 * 1/2 + ... + Ndi * 1/i ) // where // Ndi (sizeOfNthOrderNeighborhood) is the number of nodes at distance i from this node. // N is the sum Nd0 + Nd1 + Nd2 + ... + Ndi, that is the amount of nodes in the same component as the current node graph.ssspComponentReset(1); csssp.PC = 0; csssp.hfi = graph.ssspNthOrderBegin(); // FIXME do we need to check for disabled nodes somewhere? while (csssp.hfi != graph.ssspNthOrderEnd()) { qDebug() << " sizeOfNthOrderNeighborhood.value(" << csssp.hfi.key() << ")" << csssp.hfi.value(); csssp.PC += (1.0 / csssp.hfi.key()) * csssp.hfi.value(); graph.ssspComponentAdd(csssp.hfi.value()); ++csssp.hfi; } (*ds.it)->setPC(csssp.PC); graph.sumPC += csssp.PC; if (graph.ssspComponentSize() != 1) csssp.SPC = (1.0 / (graph.ssspComponentSize() - 1.0)) * csssp.PC; else csssp.SPC = 0; (*ds.it)->setSPC(csssp.SPC); // Set std PC graph.sumSPC += csssp.SPC; // add to sumSPC -- used later to compute mean and variance qDebug() << "***** PHASE 2 (CENTRALITIES): " "s" << ds.s << "vpos" << ds.si << "PC" << csssp.PC; // Compute Betweenness Centrality qDebug() << "***** PHASE 2 (BC/ACCUMULATION): " "Start back propagation of dependencies." << "Set dependency delta[u]=0 on each vertex"; for (ds.it1 = graph.verticesBegin(); ds.it1 != graph.verticesEnd(); ++ds.it1) { (*ds.it1)->setDelta(0.0); // compute sum of distances from current vertex to every other vertex ds.distances_sum_for_s += (*ds.it)->distance((*ds.it1)->number()); qDebug() << " Compute Centralities: " "For CC: sum of distances. distance(" << (*ds.it)->number() << "," << (*ds.it1)->number() << ") = " << (*ds.it)->distance((*ds.it1)->number()) << "new sum of distances for s =" << ds.distances_sum_for_s; } qDebug() << " Compute Centralities: " "For CC: total sum of distances for s =" << ds.distances_sum_for_s; graph.addToDistanceSum(ds.distances_sum_for_s); // Compute Closeness Centrality if (ds.distances_sum_for_s != 0 && ds.distances_sum_for_s < RAND_MAX) { // Connected actor: // There is a path from this actor to all others // Invert the sum of distances and set it as CC csssp.CC = 1.0 / ds.distances_sum_for_s; } else { // Not connected actor. Cases: // a) Isolated: The actor has no outbound links // b) Disconnected graph: There is no path from this actor // to some of the other actors, which means her distance to // them is infinite // For these two cases, set CC as zero. csssp.CC = 0; } (*ds.it)->setCC(csssp.CC); qDebug() << "***** PHASE 2 (BC/ACCUMULATION): " "Visit all vertices in reverse order of their discovery (from s = " << ds.s << " ) to sum dependencies. Initial Stack size " << graph.ssspStackSize(); while (!graph.ssspStackEmpty()) { ds.w = graph.ssspStackTop(); ds.wi = graph.vertexIndexByNumber(ds.w); qDebug() << "***** PHASE 2 (BC/ACCUMULATION): " "Stack top is vertex w " << ds.w << "This is the furthest vertex from s. Popping it."; graph.ssspStackPop(); QList lst = graph.vertexAtIndex(ds.wi)->Ps(); qDebug() << "***** PHASE 2 (BC/ACCUMULATION): " "preLOOP: Size of predecessors list Ps[w]" << lst.size(); qDebug() << "***** PHASE 2 (BC/ACCUMULATION): " "LOOP over every vertex u in Ps of w" << ds.w; if (lst.size() > 0) // just in case...do a sanity check for (ds.it2 = lst.cbegin(); ds.it2 != lst.cend(); ds.it2++) { ds.u = (*ds.it2); ds.ui = graph.vertexIndexByNumber(ds.u); csssp.sigma_u = graph.vertexAtIndex(ds.si)->shortestPaths(ds.u); csssp.sigma_w = graph.vertexAtIndex(ds.si)->shortestPaths(ds.w); csssp.delta_u = graph.vertexAtIndex(ds.ui)->delta(); csssp.delta_w = graph.vertexAtIndex(ds.wi)->delta(); qDebug() << "***** PHASE 2 (BC/ACCUMULATION): " "Selecting Ps[w] element u" << ds.u << "with delta_u" << csssp.delta_u << "sigma(s,u)" << csssp.sigma_u << "sigma(s,w)" << csssp.sigma_w << "delta_w" << csssp.delta_w; if (graph.vertexAtIndex(ds.si)->shortestPaths(ds.w) > 0) { // delta[u]=delta[u]+(1+delta[w])*(sigma[u]/sigma[w]) ; csssp.d_su = csssp.delta_u + (1.0 + csssp.delta_w) * ((qreal)csssp.sigma_u / (qreal)csssp.sigma_w); } else { csssp.d_su = csssp.delta_u; qDebug() << "***** PHASE 2 (BC/ACCUMULATION): " "zero shortest paths from s to w - " "using SAME DELTA for vertex u"; } qDebug() << "***** PHASE 2 (BC/ACCUMULATION): " "Assigning new delta d_su" << csssp.d_su << " to u" << ds.u; graph.vertexAtIndex(ds.ui)->setDelta(csssp.d_su); } // end for qDebug() << "***** PHASE 2 (BC/ACCUMULATION): " "Adding delta_w to BC of w"; if (ds.w != ds.s) { qDebug() << "***** PHASE 2 (BC/ACCUMULATION): " "w!=s. For this furthest vertex we need to add its new delta" << csssp.delta_w << "to old BC index:" << graph.vertexAtIndex(ds.wi)->BC(); csssp.d_sw = graph.vertexAtIndex(ds.wi)->BC() + csssp.delta_w; qDebug() << "***** PHASE 2 (BC/ACCUMULATION): " "s" << ds.s << "vpos" << ds.si << "BC = d_sw" << csssp.d_sw; graph.vertexAtIndex(ds.wi)->setBC(csssp.d_sw); } // END if } // END while stack } // END if computeCentralities } // END for SSSP problem qDebug() << "*********** MAIN LOOP (SSSP problem): FINISHED."; } void DistanceEngine::finalize(const bool computeCentralities, const bool dropIsolates, DistanceScratch &ds, CentralityScratchFinalize &csf, IDistanceProgressSink &sink) { // check if there are disconnected nodes // and get the distance sums qDebug() << "Checking if there are disconnected nodes"; graph.setConnectedCached(true); for (ds.it = graph.verticesBegin(); ds.it != graph.verticesEnd(); ++ds.it) { if (!(*ds.it)->isEnabled()) { qDebug() << "actor i" << (*ds.it)->number() << "disabled. SKIP/CONTINUE"; continue; } ds.pairDistance = 0; for (ds.it1 = graph.verticesBegin(); ds.it1 != graph.verticesEnd(); ++ds.it1) { if (!(*ds.it1)->isEnabled()) { qDebug() << " actor j" << (*ds.it1)->number() << "disabled. SKIP/CONTINUE"; continue; } if ((*ds.it1)->number() == (*ds.it)->number()) { qDebug() << " == actor j" << (*ds.it1)->number() << "SKIP/CONTINUE"; continue; } ds.pairDistance = (*ds.it)->distance((*ds.it1)->number()); if (ds.pairDistance == RAND_MAX) { graph.notConnectedPairsInsert((*ds.it)->number(), (*ds.it1)->number()); (*ds.it)->setEccentricity(RAND_MAX); graph.setConnectedCached(false); qDebug() << "actor i" << (*ds.it)->number() << "has infinite eccentricity. " "There is no path from it to actor j" << (*ds.it1)->number(); } else { qDebug() << "actor i" << (*ds.it)->number() << "distanceSum" << (*ds.it)->distanceSum(); (*ds.it)->setDistanceSum((*ds.it)->distanceSum() + ds.pairDistance); } } // end for qDebug() << "actor i" << (*ds.it)->number() << "Final distanceSum" << (*ds.it)->distanceSum(); if (computeCentralities) { // Compute Eccentricity (max geodesic distance) csf.eccentricity = (*ds.it)->eccentricity(); qDebug() << "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 qDebug() << "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 qDebug() << "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))); qDebug() << "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?) qDebug() << "Graph::graphDistancesGeodesic() - Average distance:" << graph.graphDistanceGeodesicAverageCached(); graph.setAverageDistanceCached(graph.graphSumDistanceCached() / graph.graphGeodesicsCountCached()); } if (computeCentralities) { qDebug() << "Graph: graphDistancesGeodesic() - " "Computing centralities..."; for (ds.it = graph.verticesBegin(); ds.it != graph.verticesEnd(); ++ds.it) { if (dropIsolates && (*ds.it)->isIsolated()) { qDebug() << "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()) { qDebug() << "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(); qDebug() << "SC of " << (*ds.it)->number() << " divided by 2 (because the graph is symmetric) " << (*ds.it)->SC(); } graph.sumSC += csf.SC; qDebug() << "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 qDebug() << "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; qDebug() << "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: d(s, t) is set to the distance of each t from s For every vertex t: s(s, t) 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 sizeOfNthOrderNeighborhood [ 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 the list , thus Ps stores all predecessors of y on all all shortest paths from s c) Each vertex u popped from Q is pushed to a stack Stack */ void DistanceEngine::bfsSSSP(const int &s, const int &si, const bool &computeCentralities, const bool &dropIsolates) { Q_UNUSED(dropIsolates); qDebug() << "BFS:"; int u = 0, ui = 0, w = 0, wi = 0; int dist_u = 0, temp = 0, dist_w = 0; int relation = 0; // int weight=0; bool edgeStatus = false; H_edges::const_iterator it1; // set distance of s from s equal to 0 graph.vertexAtIndex(si)->setDistance(s, 0); // set sigma of s from s equal to 1 graph.vertexAtIndex(si)->setShortestPaths(s, 1); // qDebug("BFS: Construct a queue Q of integers and push source vertex s=%i to Q as initial vertex", s); std::queue Q; Q.push(s); qDebug() << "BFS: LOOP: While Q not empty "; while (!Q.empty()) { u = Q.front(); Q.pop(); ui = graph.vertexIndexByNumber(u); qDebug() << "BFS: Dequeue: first element of Q is u" << u << "graph.vertexIndexByNumber" << ui; if (!graph.vertexAtIndex(ui)->isEnabled()) { continue; } if (computeCentralities) { qDebug() << "BFS: Compute centralities: Pushing u" << u << "to global Stack "; graph.ssspStackPush(u); } qDebug() << "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; } w = it1.key(); // weight = it1.value().second.first; wi = graph.vertexIndexByNumber(w); qDebug("BFS: u=%i is connected with node w=%i of graph.vertexIndexByNumber wi=%i. ", u, w, wi); qDebug("BFS: Start path discovery"); // if distance (s,w) is infinite, w found for the first time. if (graph.vertexAtIndex(si)->distance(w) == RAND_MAX) { qDebug("BFS: First time visiting w=%i. Enqueuing w to the end of Q", w); Q.push(w); qDebug() << "BFS: First check if distance(s,u) = infinite and set it to zero"; dist_u = graph.vertexAtIndex(si)->distance(u); dist_w = dist_u + 1; qDebug() << "BFS: Setting dist_w = d ( s" << s << ", w" << w << ") equal to dist_u=d(s,u) plus 1. New dist_w" << dist_w; ; graph.vertexAtIndex(si)->setDistance(w, dist_w); graph.addToDistanceSum(dist_w); graph.incGeodesicsCount(); qDebug() << "== BFS - d(" << s << "," << w << ")=" << graph.vertexAtIndex(si)->distance(w); if (computeCentralities) { qDebug() << "BFS: Calculate PC: store the number of nodes at distance " << dist_w << "from s"; graph.ssspNthOrderIncrement(dist_w); qDebug() << "BFS: Calculate CC: the sum of distances (will invert it l8r)"; graph.vertexAtIndex(si)->setCC(graph.vertexAtIndex(si)->CC() + dist_w); qDebug() << "BFS: Calculate Eccentricity: the maximum distance "; if (graph.vertexAtIndex(si)->eccentricity() < dist_w) graph.vertexAtIndex(si)->setEccentricity(dist_w); } // qDebug("BFS: Checking m_graphDiameter"); if (dist_w > graph.graphDiameterCached()) { graph.setDiameterCached(dist_w); // qDebug() << "BFS: new m_graphDiameter = " << m_graphDiameter ; } } qDebug() << "BFS: Start path counting"; // Is edge (u,w) on a shortest path from s to w via u? if (graph.vertexAtIndex(si)->distance(w) == graph.vertexAtIndex(si)->distance(u) + 1) { temp = graph.vertexAtIndex(si)->shortestPaths(w) + graph.vertexAtIndex(si)->shortestPaths(u); qDebug() << "BFS: Found a NEW SHORTEST PATH from s" << s << "to w" << w << "via u" << u << "Setting Sigma(s, w)" << temp; if (s != w) { graph.vertexAtIndex(si)->setShortestPaths(w, temp); } if (computeCentralities) { qDebug() << "BFS/SC: Computing centralities: Computing SC "; if (s != w && s != u && u != w) { qDebug() << "BFS: setSC of u=" << u << " to " << graph.vertexAtIndex(ui)->SC() + 1; graph.vertexAtIndex(ui)->setSC(graph.vertexAtIndex(ui)->SC() + 1); } else { // qDebug() << "BFS/SC: skipping setSC of u, because s=" // <SC(); qDebug() << "BFS: appending u" << u << " to list Ps[w=" << w << "] with the predecessors of w on all shortest paths from s "; graph.vertexAtIndex(wi)->appendToPs(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: d(s, t) is set to the distance of each t from s For every vertex t: s(s, t) 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 sizeOfNthOrderNeighborhood [ 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 the list Ps, thus Ps stores all predecessors of y on all all shortest paths from s c) Each vertex u popped from prQ is pushed to a stack Stack */ void DistanceEngine::dijkstraSSSP(const int &s, const int &si, const bool &computeCentralities, const bool &inverseWeights, const bool &dropIsolates) { 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 qDebug() << "### 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 graph.vertexAtIndex(si)->setDistance(s, 0); // set sp ( s , s ) = 1 graph.vertexAtIndex(si)->setShortestPaths(s, 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 in graphDistancesGeodesic // qDebug() << " push " << v << " to prQ with infinite distance from s"; // prQ.push(GraphDistance(v,RAND_MAX)); // TODO // Previous node in optimal path from source // previous[v] := undefined } } qDebug() << "### 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)); qDebug() << "### dijkstra: LOOP: While prQ not empty "; while (!prQ.empty()) { qDebug() << " *** 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 qDebug() << " *** 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)) { qDebug() << " *** 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()) { qDebug() << " *** dijkstra: vertex disabled. Skipping!"; continue; } // Check if we need to compute centralities if (computeCentralities) { qDebug() << " *** dijkstra: Compute centralities: pushing u =" << u << " to global Stack "; graph.ssspStackPush(u); } // LOOP over every edge of u qDebug() << " --- 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; qDebug() << " --- dijkstra: edge (u, w) = (" << u << "," << w << ") =" << weight; // Invert edge weight if the user told us to do so if (inverseWeights) { weight = 1.0 / weight; qDebug() << " --- dijkstra: inverting weight to " << weight; } // Start path discovery qDebug() << " --- dijkstra: Start path discovery"; // Get the distance of u from source dist_u = graph.vertexAtIndex(si)->distance(u); // 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; qDebug() << " --- dijkstra: dist_w = RAND_MAX " << RAND_MAX; } else { // dist_u finite, therefore dist_w is (dist_u + edge weight) dist_w = dist_u + weight; qDebug() << " --- dijkstra: dist_w = dist_u + weight = " << dist_u << "+" << weight << "=" << dist_w; } // Get the currently computed distance of w from source cur_dist_w = graph.vertexAtIndex(si)->distance(w); qDebug() << " --- 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) { qDebug() << " --- dijkstra: dist_w : " << dist_w << " == current d(s,w) : " << cur_dist_w; sp_w = graph.vertexAtIndex(si)->shortestPaths(w) + graph.vertexAtIndex(si)->shortestPaths(u); // WRONG! We do not know for sure that we are in a shortest path!!! qDebug() << " --- dijkstra: (POSSIBLE BUG?) Found ANOTHER SP from s =" << s << " to w=" << w << " via u=" << u << " - Setting Sigma(s, w) = " << sp_w; if (s != w) { graph.vertexAtIndex(si)->setShortestPaths(w, sp_w); } if (computeCentralities) { if (s != w && s != u && u != w) { qDebug() << " --- dijkstra: Compute Centralities: " "setSC of u" << u << "to" << graph.vertexAtIndex(ui)->SC() + 1; graph.vertexAtIndex(ui)->setSC(graph.vertexAtIndex(ui)->SC() + 1); } else { qDebug() << " --- dijkstra: Compute Centralities: " "Skipping setSC of u, because s=" << s << " w=" << w << " u=" << u; } qDebug() << " --- dijkstra: Compute Centralities: " "SC is " << graph.vertexAtIndex(ui)->SC(); qDebug() << " --- dijkstra: Compute Centralities: " "Appending u=" << u << " to list Ps[w =" << w << "] with the predecessors of w on all shortest paths from s "; graph.vertexAtIndex(wi)->appendToPs(u); } } else if (dist_w > 0 && dist_w < cur_dist_w) { qDebug() << " --- 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)); graph.vertexAtIndex(si)->setDistance(w, dist_w); graph.incGeodesicsCount(); qDebug() << " --- dijkstra: " "Set d ( s=" << s << ", w=" << w << " ) = " << dist_w << "=" << graph.vertexAtIndex(si)->distance(w); if (dist_w > graph.graphDiameterCached()) { graph.setDiameterCached(dist_w); qDebug() << " --- dijkstra: " "New graph diameter =" << graph.graphDiameterCached(); } if (s != w) { qDebug() << " --- dijkstra: " "Found NEW shortest path from s =" << s << " to w =" << w << " via u =" << u << " - Setting Sigma(s, w) = 1 "; graph.vertexAtIndex(si)->setShortestPaths(w, 1); } if (computeCentralities) { graph.ssspNthOrderIncrement(dist_w); qDebug() << " --- dijkstra: Compute Centralities: " "For PC: sizeOfNthOrderNeighborhood: number of nodes at distance " << dist_w << "from s is " << graph.ssspNthOrderValue(dist_w); if (graph.vertexAtIndex(si)->eccentricity() < dist_w) { graph.vertexAtIndex(si)->setEccentricity(dist_w); qDebug() << " --- dijkstra: Compute Centralities: " "For EC: max distance =" << graph.vertexAtIndex(si)->eccentricity(); } qDebug() << " --- dijkstra: Compute Centralities: " "Appending u=" << u << " to list Ps[w =" << w << "] with the predecessors of w on all shortest paths from s "; graph.vertexAtIndex(wi)->appendToPs(u); } } else { qDebug() << " --- dijkstra: " "NOT a new SP"; } ++it1; } // END loop for every outEdge of u qDebug() << " --- dijkstra: LOOP END over every edge (" << u << ", w ) e E... "; } // END loop while prQ not empty qDebug() << "### dijkstra: LOOP END. prQ is empty - Returning."; } // END dijkstraSSSP() socnetv-app-39db829/src/engine/distance_engine.h000066400000000000000000000045321517721000100216410ustar00rootroot00000000000000/** * @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" 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); void runAllSources(const bool computeCentralities, const bool considerWeights, const bool inverseWeights, const bool dropIsolates, struct DistanceScratch &ds, struct CentralityScratchSSSP &csssp, IDistanceProgressSink &sink); void finalize(const bool computeCentralities, const bool dropIsolates, struct DistanceScratch &ds, struct CentralityScratchFinalize &csfin, IDistanceProgressSink &sink); void bfsSSSP(const int &s, const int &si, const bool &computeCentralities, const bool &dropIsolates); void dijkstraSSSP(const int &s, const int &si, const bool &computeCentralities, const bool &inverseWeights, const bool &dropIsolates); }; #endif // SOCNETV_DISTANCE_ENGINE_H socnetv-app-39db829/src/engine/distance_progress_sink.h000066400000000000000000000017751517721000100232720ustar00rootroot00000000000000/** * @file distance_progress_sink.h * @brief Declares the IDistanceProgressSink interface for receiving progress updates 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 progressCreate(int total, const QString &msg) = 0; virtual void progressUpdate(int value) = 0; virtual void progressKill() = 0; virtual bool progressCanceled() const = 0; }; socnetv-app-39db829/src/engine/graph_distance_progress_sink.cpp000066400000000000000000000027021517721000100247750ustar00rootroot00000000000000/** * @file graph_distance_progress_sink.cpp * @brief Implements the GraphDistanceProgressSink class that receives progress updates from the DistanceEngine and forwards them to the Graph for UI updates. * @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); } void GraphDistanceProgressSink::progressCreate(const int total, const QString &msg) { graph.resetProgressCanceled(); emit graph.signalProgressBoxCreate(total, msg); } void GraphDistanceProgressSink::progressUpdate(const int value) { emit graph.signalProgressBoxUpdate(value); } void GraphDistanceProgressSink::progressKill() { emit graph.signalProgressBoxKill(); } bool GraphDistanceProgressSink::progressCanceled() const { return graph.progressCanceled(); } socnetv-app-39db829/src/engine/graph_distance_progress_sink.h000066400000000000000000000022551517721000100244450ustar00rootroot00000000000000/** * @file graph_distance_progress_sink.h * @brief Declares the GraphDistanceProgressSink class that implements IDistanceProgressSink to receive progress updates from the DistanceEngine and forward them to the Graph for UI updates. * @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 progressCreate(int total, const QString &msg) override; void progressUpdate(int value) override; void progressKill() override; bool progressCanceled() const override; private: Graph &graph; }; socnetv-app-39db829/src/engine/null_distance_progress_sink.h000066400000000000000000000021021517721000100243050ustar00rootroot00000000000000/** * @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 progressCreate(int, const QString &) override {} void progressUpdate(int) override {} void progressKill() override {} bool progressCanceled() const override { return false; } }; socnetv-app-39db829/src/forms/000077500000000000000000000000001517721000100162265ustar00rootroot00000000000000socnetv-app-39db829/src/forms/dialogclusteringhierarchical.cpp000077500000000000000000000066511517721000100246430ustar00rootroot00000000000000/** * @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 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) { qDebug()<< "DialogClusteringHierarchical::matrixChanged()" << matrix; } /** * @brief Gets user choices */ void DialogClusteringHierarchical::getUserChoices(){ qDebug()<< "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(); qDebug()<< "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-app-39db829/src/forms/dialogclusteringhierarchical.h000077500000000000000000000031031517721000100242750ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogclusteringhierarchical.ui000066400000000000000000000251751517721000100244750ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogdatasetselect.cpp000077500000000000000000000113211517721000100227400ustar00rootroot00000000000000/** * @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 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(){ qDebug()<< "DialogDataSetSelect: gathering Data!..."; int index = (ui->selectBox)->currentIndex(); QString dataset_name = datasets_filenames[index]; qDebug()<< "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-app-39db829/src/forms/dialogdatasetselect.h000077500000000000000000000021511517721000100224060ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogdatasetselect.ui000066400000000000000000000060131517721000100225720ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogdissimilarities.cpp000077500000000000000000000041071517721000100233150ustar00rootroot00000000000000/** * @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 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(){ qDebug()<< "DialogDissimilarities: gathering Data!..."; QString varLocation = (ui.variablesLocationSelect)->currentText(); QString metric = (ui.metricSelect)->currentText(); bool diagonal = (ui.diagonalCheckBox)->isChecked(); qDebug()<< "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-app-39db829/src/forms/dialogdissimilarities.h000077500000000000000000000023601517721000100227610ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogdissimilarities.ui000066400000000000000000000155611517721000100231530ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogedgedichotomization.cpp000077500000000000000000000024741517721000100241550ustar00rootroot00000000000000/** * @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 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(){ qDebug()<< "Dialog: gathering Data!..."; qreal my_threshold = ui.weightThreshold->value() ; qDebug()<< "DialogEdgeDichotomization::getUserChoices() - We will dichotomize edges according to threshold: " << my_threshold; qDebug()<< "Dialog: emitting userChoices" ; emit userChoices( my_threshold ); } socnetv-app-39db829/src/forms/dialogedgedichotomization.h000077500000000000000000000020251517721000100236120ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogedgedichotomization.ui000066400000000000000000000107631517721000100240050ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogedgeedit.cpp000066400000000000000000000132671517721000100216750ustar00rootroot00000000000000/** * @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 #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); qDebug() << "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() { qDebug() << "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-app-39db829/src/forms/dialogedgeedit.h000066400000000000000000000033461517721000100213370ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogedgeedit.ui000066400000000000000000000156551517721000100215330ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogexportimage.cpp000066400000000000000000000144621517721000100224450ustar00rootroot00000000000000/** * @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 #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 { qDebug() << "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(); qDebug() << "format:" << m_format; QString m_fileName = ui->fileEdit->text(); qDebug() << "filename" << m_fileName; qDebug() << "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); qDebug() << "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(); qDebug()<< "user choices: " << m_fileName << m_format << m_quality << m_compression; emit userChoices(m_fileName, m_format, m_quality, m_compression); } socnetv-app-39db829/src/forms/dialogexportimage.h000066400000000000000000000026441517721000100221110ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogexportimage.ui000066400000000000000000000265411517721000100223010ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogexportpdf.cpp000066400000000000000000000120271517721000100221270ustar00rootroot00000000000000/** * @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 #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 ) ) { qDebug() << "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 { qDebug() << "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(){ qDebug()<< "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; } qDebug()<< "Dialog: emitting userChoices" ; emit userChoices( m_fileName, m_orientation, m_dpi, m_printerMode ); } socnetv-app-39db829/src/forms/dialogexportpdf.h000066400000000000000000000030541517721000100215740ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogexportpdf.ui000066400000000000000000000364351517721000100217730ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogfilterbyattribute.cpp000066400000000000000000000065231517721000100236640ustar00rootroot00000000000000/** * @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 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); 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-app-39db829/src/forms/dialogfilterbyattribute.h000066400000000000000000000023251517721000100233250ustar00rootroot00000000000000/** * @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(); private slots: void onScopeChanged(); void getUserChoices(); signals: void userChoices(const FilterCondition &cond); private: Ui::DialogFilterByAttribute *ui; QStringList m_nodeKeys; QStringList m_edgeKeys; void repopulateKeys(FilterCondition::Scope scope); }; #endif // DIALOGFILTERBYATTRIBUTE_H socnetv-app-39db829/src/forms/dialogfilterbyattribute.ui000066400000000000000000000136611517721000100235200ustar00rootroot00000000000000 DialogFilterByAttribute 0 0 420 300 380 260 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… Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBoxaccepted() DialogFilterByAttributeaccept() 200280 200150 buttonBoxrejected() DialogFilterByAttributereject() 200280 200150 socnetv-app-39db829/src/forms/dialogfilteredgesbyweight.cpp000077500000000000000000000031021517721000100241510ustar00rootroot00000000000000/** * @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 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(){ qDebug()<< "Dialog: gathering Data!..."; bool overThreshold=false; float my_threshold = static_cast ( (ui.weightThreshold)->value() ); if ( ui.overThresholdBt->isChecked() ) { qDebug()<< "Dialog: We will filter edges weighted more than threshold: " << my_threshold; overThreshold = true; } else { qDebug()<< "Dialog: We will filter edges weighted less than threshold: " << my_threshold; overThreshold = false; } qDebug()<< "Dialog: emitting userChoices" ; emit userChoices( my_threshold, overThreshold ); } socnetv-app-39db829/src/forms/dialogfilteredgesbyweight.h000077500000000000000000000020641517721000100236240ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogfilteredgesbyweight.ui000066400000000000000000000117001517721000100240040ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogfilternodesbycentrality.cpp000066400000000000000000000062441517721000100250700ustar00rootroot00000000000000/** * @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 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); qDebug() << "DialogFilterNodesByCentrality:" << "index:" << ui.indexComboBox->currentText() << "(" << static_cast(centralityIndex) << ")" << "threshold:" << threshold << "overThreshold:" << overThreshold; emit userChoices(threshold, overThreshold, centralityIndex); } socnetv-app-39db829/src/forms/dialogfilternodesbycentrality.h000066400000000000000000000026451517721000100245360ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogfilternodesbycentrality.ui000066400000000000000000000156631517721000100247300ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogimportattributes.cpp000066400000000000000000000222311517721000100235330ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogimportattributes.h000066400000000000000000000054501517721000100232040ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialognodeedit.cpp000077500000000000000000000376651517721000100217310ustar00rootroot00000000000000/** * @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 #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); qDebug() << "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; } qDebug() << "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()) { qDebug() << 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() { qDebug() << " 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() { qDebug() << " DialogNodeEdit::selectColor()"; nodeColor = QColorDialog::getColor( Qt::red, this, tr("Select node color")); if (nodeColor.isValid()) { qDebug() << " color selected " << nodeColor.name(); pixmap.fill(nodeColor); ui->colorButton->setIcon(QIcon(pixmap)); } else { // user pressed Cancel qDebug() << " 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() { qDebug() << " DialogNodeEdit::checkErrors()"; QString userLabel = ui->labelEdit->text(); userLabel = userLabel.simplified(); ui->labelEdit->setText(userLabel); if (ui->labelEdit->text().isEmpty()) { qDebug() << "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-app-39db829/src/forms/dialognodeedit.h000077500000000000000000000045011517721000100213550ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialognodeedit.ui000066400000000000000000000462201517721000100215440ustar00rootroot00000000000000 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-app-39db829/src/forms/dialognodefind.cpp000066400000000000000000000162601517721000100217050ustar00rootroot00000000000000/** * @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 #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(); qDebug() << "DialogNodeFind::getIndex() str"<plainTextEdit->toPlainText(); qDebug()<< "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"; } qDebug()<< "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(); qDebug()<< "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(); qDebug()<< "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+"))) { qDebug()<< "DialogNodeFind::checkErrors() - error! not number" << str; setError(true); } else { qDebug()<< "DialogNodeFind::checkErrors() - adding number" << str; list << str; } } } else { // user wants to search by labels qDebug()<< "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 { qDebug()<< "DialogNodeFind::checkErrors() - error! search by index without > or <" ; setError(true); } } } } /** * @brief Gathers user input and emits userChoices signal */ void DialogNodeFind::getUserChoices() { qDebug()<< "DialogNodeFind::getUserChoices()" << list; qDebug()<< "DialogNodeFind::getUserChoices() type" << searchType; emit userChoices( list, searchType, selectedIndex ); } socnetv-app-39db829/src/forms/dialognodefind.h000066400000000000000000000026571517721000100213570ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialognodefind.ui000066400000000000000000000201571517721000100215400ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogpreviewfile.cpp000077500000000000000000000107031517721000100224370ustar00rootroot00000000000000/** * @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" 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); qDebug () << "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()); // qDebug () << " - codec: " << codec->name() // << " supported by QStringConverter. QTextStream Encoding set to: " // << QStringConverter::nameForEncoding(test_support.value()); // } // else { // // Encoding not supported. Retreat to UTF-9 // qDebug () << " - codec: " << codec->name() // << " NOT supported by QStringConverter. QTextStream set to autoDetectUnicode. "; // 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); qDebug () << "User accepted. Returning codec name:" << codec->name(); emit loadNetworkFileWithCodec(m_fileName, codec->name(), m_fileFormat); QDialog::accept(); } socnetv-app-39db829/src/forms/dialogpreviewfile.h000077500000000000000000000030451517721000100221050ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogranderdosrenyi.cpp000077500000000000000000000112711517721000100231470ustar00rootroot00000000000000/** * @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 #include #include #include #include #include "dialogranderdosrenyi.h" DialogRandErdosRenyi::DialogRandErdosRenyi(QWidget *parent, const qreal eprob) : QDialog(parent) { qDebug() << "::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() { qDebug()<< " 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() { qDebug() << "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); qDebug() << "nodes " << nodes ; qDebug() << "model " << model; qDebug() << "eprob " << ui.probDoubleSpinBox->value(); qDebug() << "edges " << edges; qDebug() << "mode " << mode; qDebug() << "diag " << diag; emit userChoices(nodes, model, edges, ui.probDoubleSpinBox->value(), mode, diag); } socnetv-app-39db829/src/forms/dialogranderdosrenyi.h000077500000000000000000000027101517721000100226120ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogranderdosrenyi.ui000066400000000000000000000434331517721000100230040ustar00rootroot00000000000000 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-app-39db829/src/forms/dialograndlattice.cpp000077500000000000000000000045151517721000100224140ustar00rootroot00000000000000/** * @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 #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() { qDebug() << "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); qDebug() << "nodes " << nodes ; qDebug() << "length " << length; qDebug() << "dimension " << dimension; qDebug() << "neighLength" << neighLength; qDebug() << "mode " << mode; qDebug() << "diag " << circular; emit userChoices(nodes, length, dimension, neighLength, mode, circular); } socnetv-app-39db829/src/forms/dialograndlattice.h000077500000000000000000000030121517721000100220500ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialograndlattice.ui000066400000000000000000000360531517721000100222460ustar00rootroot00000000000000 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-app-39db829/src/forms/dialograndregular.cpp000077500000000000000000000101351517721000100224230ustar00rootroot00000000000000/** * @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 #include #include #include #include #include #include "dialograndregular.h" DialogRandRegular::DialogRandRegular(QWidget *parent) : QDialog(parent) { qDebug() << "::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); qDebug()<< " 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() { qDebug() << "DialogRandRegular::getUserChoices() " ; nodes = ui.nodesSpinBox->value(); degree= ui.degreeSpinBox->value(); mode = (ui.directedRadioButton->isChecked() ? "digraph" : "graph" ); diag = (ui.diagCheckBox->isChecked() ? true : false); qDebug() << "nodes " << nodes ; qDebug() << "degree" << degree; qDebug() << "mode " << mode; qDebug() << "diag " << diag; emit userChoices(nodes, degree, mode, diag); } socnetv-app-39db829/src/forms/dialograndregular.h000077500000000000000000000024331517721000100220720ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialograndregular.ui000066400000000000000000000337461517721000100222700ustar00rootroot00000000000000 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-app-39db829/src/forms/dialograndscalefree.cpp000077500000000000000000000074041517721000100227200ustar00rootroot00000000000000/** * @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 #include #include #include DialogRandScaleFree::DialogRandScaleFree(QWidget *parent) : QDialog(parent) { qDebug() << "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() { qDebug()<< " 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() { qDebug() << "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); qDebug() << "nodes " << nodes ; qDebug() << "initialNodes " << initialNodes; qDebug() << "mode " << mode; qDebug() << "diag " << diag; emit userChoices(nodes, power, initialNodes, edgesPerStep,zeroAppeal, mode); } socnetv-app-39db829/src/forms/dialograndscalefree.h000077500000000000000000000027471517721000100223720ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialograndscalefree.ui000066400000000000000000000417141517721000100225520ustar00rootroot00000000000000 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-app-39db829/src/forms/dialograndsmallworld.cpp000077500000000000000000000101261517721000100231420ustar00rootroot00000000000000/** * @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 #include #include #include #include #include #include "dialograndsmallworld.h" DialogRandSmallWorld::DialogRandSmallWorld(QWidget *parent) : QDialog(parent), ui(new Ui::DialogRandSmallWorld) { qDebug() << "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() { qDebug()<< " 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() { qDebug() << "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); qDebug() << "nodes " << nodes ; qDebug() << "bprob " << bprob; qDebug() << "degree" << degree; qDebug() << "mode " << mode; qDebug() << "diag " << diag; emit userChoices(nodes, degree, bprob, mode, diag); } socnetv-app-39db829/src/forms/dialograndsmallworld.h000077500000000000000000000026021517721000100226070ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialograndsmallworld.ui000066400000000000000000000313031517721000100227720ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogsettings.cpp000077500000000000000000000772611517721000100217720ustar00rootroot00000000000000/** * @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 #include #include #include #include #include #include #include #include "global.h" SOCNETV_USE_NAMESPACE 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; } qDebug() << "reportsChartTypeSelect" << ui->reportsChartTypeSelect->currentText(); //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 ); /** * 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" ); } qDebug() << "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" ); } qDebug() << "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 ); 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->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->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->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() ) { qDebug() << "DialogSettings::getReportsChartType() - type: " << type; m_appSettings["initReportsChartType"] = QString::number(type-1); emit setReportsChartType(type-1); //} } /** * @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]; qDebug()<< "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()) { qDebug() << 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 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"; } qDebug()<< "DialogSettings::getEdgeShape() - new default shape " << m_appSettings["initEdgeShape"]; emit setEdgeShape(m_appSettings["initEdgeShape"], 0); } /** * @brief Changes the edge offset from source and target nodes * @param size */ void DialogSettings::getEdgeOffsetFromNode( int offset) { qDebug()<< "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); } DialogSettings::~DialogSettings() { delete ui; } socnetv-app-39db829/src/forms/dialogsettings.h000077500000000000000000000113261517721000100214250ustar00rootroot00000000000000/** * @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 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 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); signals: void setReportsDataDir (const QString &dir); void setReportsRealNumberPrecision(const int &precision); void setReportsLabelLength(const int &length); void setReportsChartType(const int &type); 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 setEdgeColor(const QColor, const int &); void setEdgeShape(const QString, const long int); 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 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-app-39db829/src/forms/dialogsettings.ui000066400000000000000000004572271517721000100216260ustar00rootroot00000000000000 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 118 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> 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;">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 false <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;">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>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 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;">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 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-app-39db829/src/forms/dialogsimilaritymatches.cpp000077500000000000000000000044771517721000100236640ustar00rootroot00000000000000/** * @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 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(){ qDebug()<< "DialogSimilarityMatches: gathering Data!..."; QString matrix = (ui.matrixSelect)->currentText(); QString varLocation = (ui.variablesLocationSelect)->currentText(); QString measure = (ui.measureSelect)->currentText(); bool diagonal = (ui.diagonalCheckBox)->isChecked(); qDebug()<< "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-app-39db829/src/forms/dialogsimilaritymatches.h000077500000000000000000000025241517721000100233200ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogsimilaritymatches.ui000066400000000000000000000201441517721000100235010ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogsimilaritypearson.cpp000077500000000000000000000037331517721000100237010ustar00rootroot00000000000000/** * @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 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(){ qDebug()<< "DialogSimilarityPearson: gathering Data!..."; QString matrix = (ui.matrixSelect)->currentText(); QString varLocation = (ui.variablesLocationSelect)->currentText(); bool diagonal = (ui.diagonalCheckBox)->isChecked(); qDebug()<< "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-app-39db829/src/forms/dialogsimilaritypearson.h000077500000000000000000000024171517721000100233440ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogsimilaritypearson.ui000066400000000000000000000146171517721000100235340ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogsysteminfo.cpp000077500000000000000000000141641517721000100223230ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogsysteminfo.h000077500000000000000000000016011517721000100217600ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogsysteminfo.ui000066400000000000000000000055701517721000100221540ustar00rootroot00000000000000 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-app-39db829/src/forms/dialogwebcrawler.cpp000077500000000000000000000273501517721000100222610ustar00rootroot00000000000000/** * @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 #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(); qDebug()<< "seed url:" << seedUrlInputStr << "Sanitizing..."; seedUrlInputStr = seedUrlInputStr.simplified(); seedUrl = QUrl(seedUrlInputStr); qDebug()<< "seed url:" << seedUrl.toString() << " scheme " << seedUrl.scheme() << " host " << seedUrl.host() << " path " << seedUrl.path(); if ( seedUrl.scheme().isEmpty() || ( seedUrl.scheme() != "http" && seedUrl.scheme() != "https" )) { qDebug()<< "seed url has no scheme. Setting the default scheme (http) "; seedUrl.setUrl("//" + seedUrlInputStr); seedUrl.setScheme("http"); qDebug() << seedUrl; } if (seedUrl.path().isEmpty() ) { qDebug()<< "seed url without path. Adding default path '/'..."; seedUrl.setPath("/"); } if (! seedUrl.isValid() || seedUrl.host() == "" || !seedUrl.host().contains(".") ) { qDebug()<< "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 // qDebug()<< "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(); qDebug() << "return empty urlPatterns (ALL)"; } ui.patternsIncludedTextEdit->setGraphicsEffect(0); errorPatternsIncl = false; } // // CHECK URL PATTERNS TO EXCLUDE TEXTEDIT // qDebug()<< "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(" at ::" << data.indexOf('>',0) << "\n"; str = data.mid ( data.indexOf('>',0) +1, data.indexOf("

",0) - (data.indexOf('>',0) +1) ); qDebug () << "str ::" << str ; str.remove("
"); str=str.simplified(); // remove wildcards, if there are any if (str.contains("*")) { str.remove("*"); } qDebug () << "str fin ::" << str; // urls and classes cannot contain spaces... if (str.contains(" ")) { userInputParsed.clear(); qDebug () << "urls cannot contain spaces... Break." ; break; } userInputParsed << str; } } else { userInputParsed.clear(); } qDebug () << "stringlist size" << userInputParsed.size()<< "\n"; return userInputParsed; } /** * @brief gathers data from web crawler form */ void DialogWebCrawler::getUserChoices(){ qDebug()<< "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-app-39db829/src/forms/dialogwebcrawler.h000077500000000000000000000040141517721000100217160ustar00rootroot00000000000000/** * @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-app-39db829/src/forms/dialogwebcrawler.ui000066400000000000000000000464761517721000100221230ustar00rootroot00000000000000 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-app-39db829/src/forms/edgeeditdialog.ui000066400000000000000000000221231517721000100215170ustar00rootroot00000000000000 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-app-39db829/src/global.h000066400000000000000000000176261517721000100165250ustar00rootroot00000000000000/** * @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.5"; // ============================================================================ // 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 DP = 10, ///< Degree Prestige PRP = 11, ///< PageRank Prestige PP = 12 ///< Proximity Prestige }; /** * @enum ChartType * @brief Chart style for prominence distribution visualizations. */ enum ChartType { None = -1, Spline = 0, Area = 1, Bars = 2 }; /** * @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 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-app-39db829/src/graph.cpp000077500000000000000000000357501517721000100167220ustar00rootroot00000000000000/** * @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" /** * @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) { qDebug() << "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_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; calculatedCentralities = false; calculatedIRCC = false; calculatedPP = false; calculatedPRP = false; calculatedTriad = false; m_reportsDataDir = ""; m_reportsRealPrecision = 6; m_reportsLabelLength = 8; m_reportsChartType = ChartType::Spline; 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; randomizeThings(); htmlHead = QString("" "" "" "" "" "" "" "" "" "" "" "" "") .arg(VERSION); htmlHeadLight = QString("" "" "" "" "" "" "" "" "" "" "") .arg(VERSION); htmlEnd = ""; } /** * @brief Destroys the Graph object */ Graph::~Graph() { qDebug() << "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) { qDebug() << "Clearing graph, vertices and data structures... Reason:" << reason; qDebug() << "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) { qDebug() << "clearing DM matrix"; DM.clear(); } if (SIGMA.size() > 0) { qDebug() << "clearing SIGMA matrix"; SIGMA.clear(); } if (sumM.size() > 0) { qDebug() << "clearing sumM"; sumM.clear(); } if (invAM.size() > 0) { qDebug() << "clearing invAM"; invAM.clear(); } if (AM.size() > 0) { qDebug() << "clearing AM"; AM.clear(); } if (invM.size() > 0) { qDebug() << "clearing invM"; invM.clear(); } if (XM.size() > 0) { qDebug() << "clearing XM"; XM.clear(); } if (XSM.size() > 0) { qDebug() << "clearing XSM"; XSM.clear(); } if (XRM.size() > 0) { qDebug() << "clearing XRM"; XRM.clear(); } m_verticesList.clear(); m_verticesSet.clear(); m_verticesIsolatedList.clear(); m_vertexPairsNotConnected.clear(); m_vertexPairsUnilaterallyConnected.clear(); influenceDomains.clear(); influenceRanges.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_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; calculatedIRCC = false; calculatedPP = false; calculatedPRP = false; calculatedTriad = false; m_graphModStatus = ModStatus::NewNet; // if ( urlQueue->size() > 0 ) { // urlQueue->clear(); // } if (reason != "exit") { qDebug() << "Finished clearing graph data. Changing graph modification status to" << m_graphModStatus; setModStatus(m_graphModStatus, true); } qDebug() << "Finished clearing graph data and structures."; } socnetv-app-39db829/src/graph.h000077500000000000000000001536261517721000100163720ustar00rootroot00000000000000/** * @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 // stack is a wrapper around in C++ // see: www.cplusplus.com/reference/stl/stack #include #include "global.h" #include "graph/filters/filter_condition.h" #include "graphvertex.h" #include "matrix.h" #include "parser.h" #include "webcrawler.h" 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); 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(); bool visibilityHistoryEmpty() const; void edgeFilterByWeight(const qreal, const bool); void edgeFilterReset(); void edgeFilterByRelation(int relation, bool status); void edgeFilterUnilateral(const bool &toggle); 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 signalProgressBoxCreate(const int max = 0, const QString msg = "Please wait"); void signalProgressBoxKill(const int max = 0); void signalProgressBoxUpdate(const int &count = 0); 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 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 void setVertexVisibility(const int &number, const bool &toggle); void setNodePos(const int &, const qreal &, const qreal &); 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 }; /* 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(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; int vertexExists(const int &v1); int vertexExists(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); 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 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(); 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; 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 writeDataSetToFile(const QString dir, const QString); void writeMatrixAdjacencyTo(QTextStream &os, const bool &saveEdgeWeights = true); void writeReciprocity(const QString fileName, const bool considerWeights = false); 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); void writeMatrixHTMLTable(QTextStream &outText, Matrix &M, const bool &markDiag = true, const bool &plain = false, const bool &printInfinity = true, const bool &dropIsolates = false); void writeMatrixAdjacency(const QString fileName, const bool &markDiag = true); 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); 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); bool writeMatrixSimilarityPearson(const QString fileName, const bool considerWeights, const QString &matrix = "adjacency", const QString &varLocation = "rows", const bool &diagonal = false); bool writeEccentricity(const QString fileName, const bool considerWeights = false, const bool inverseWeights = false, const bool dropIsolates = false); // friend QTextStream& operator << (QTextStream& os, Graph& m); bool writeCentralityDegree(const QString, const bool weights, const bool dropIsolates); bool writeCentralityCloseness(const QString, const bool weights, const bool inverseWeights, const bool dropIsolates); bool writeCentralityClosenessInfluenceRange(const QString, const bool weights, const bool inverseWeights, const bool dropIsolates); bool writeCentralityBetweenness(const QString, const bool weights, const bool inverseWeights, const bool dropIsolates); bool writeCentralityPower(const QString, const bool weigths, const bool inverseWeights, const bool dropIsolates); bool writeCentralityStress(const QString, const bool weigths, const bool inverseWeights, const bool dropIsolates); bool writeCentralityEccentricity(const QString, const bool weigths, const bool inverseWeights, const bool dropIsolates); bool writeCentralityInformation(const QString, const bool weigths, const bool inverseWeights); bool writeCentralityEigenvector(const QString, const bool &weigths = true, const bool &inverseWeights = false, const bool &dropIsolates = false); bool writePrestigeDegree(const QString, const bool weights, const bool dropIsolates); bool writePrestigeProximity(const QString, const bool weights, const bool inverseWeights, const bool dropIsolates); bool writePrestigePageRank(const QString, const bool Isolates = false); 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); bool writeCliqueCensus(const QString &fileName, const bool considerWeights); bool writeClusteringCoefficient(const QString, const bool); bool writeTriadCensus(const QString, const bool); /* 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); 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); // ============================================================================ // LEGACY/INTERNAL (ENGINE SUPPORT PRIMITIVES) // ---------------------------------------------------------------------------- // NOTE (WS2/F0): These exist to support DistanceEngine during transition. // UI code must not call these. Prefer keeping them engine-only. // ============================================================================ // --- SSSP/Brandes stack helpers (DistanceEngine should not touch Stack directly) --- void ssspStackClear(); bool ssspStackEmpty() const; int ssspStackTop() const; void ssspStackPop(); int ssspStackSize() const; void ssspStackPush(int v); // --- SSSP nth-order neighborhood (for Power Centrality) --- void ssspNthOrderClear(); // // LEGACY/INTERNAL: transitional storage. // DistanceEngine may use this via the accessors below. // (Later WS2/F1 may hide this field and keep only accessors.) // Stores the number of vertices at distance n from a given vertex, for n=0,1,2,... during SSSP traversal. H_f_i sizeOfNthOrderNeighborhood; H_f_i::const_iterator ssspNthOrderBegin() const; H_f_i::const_iterator ssspNthOrderEnd() const; int ssspNthOrderValue(qreal dist) const; void ssspNthOrderIncrement(int dist); void ssspNthOrderIncrement(qreal dist); // --- SSSP component size accumulator --- void ssspComponentReset(int value = 1); void ssspComponentAdd(int delta); int ssspComponentSize() const; // --- 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(); 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); 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 &updateProgress = false, const bool &dropIsolates = false, const bool &considerWeights = false, const bool &inverseWeights = false, const bool &symmetrize = false); void writeMatrixWalks(const QString &fn, const int &length = 0, const bool &simpler = false); QList vertexinfluenceRange(int v1); QList vertexinfluenceDomain(int v2); void writeReachabilityMatrixPlainText(const QString &fn, const bool &dropIsolates = false); 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(const bool updateProgress = false); 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 progressCreate(int max, const QString &msg); void progressUpdate(int value); void progressFinish(); 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 */ 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_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 influenceRanges, influenceDomains; 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, sumM, invAM, AM, invM, WM; Matrix XM, XSM, XRM, CLQM; stack Stack; /** used in resolveClasses and graphDistancesGeodesic() */ H_StrToInt discreteDPs, discreteSDCs, discreteCCs, discreteBCs, discreteSCs; H_StrToInt discreteIRCCs, discreteECs, discreteEccentricities; H_StrToInt discretePCs, discreteICs, discretePRPs, discretePPs, discreteEVCs; QString m_reportsDataDir; int m_reportsRealPrecision; int m_reportsLabelLength; ChartType m_reportsChartType; 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 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 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 classesEVC, maxNodeEVC, minNodeEVC; qreal sizeOfComponent; /** 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 calculatedAdjacencyMatrix, calculatedDistances, calculatedCentralities; bool calculatedIsolates; bool calculatedEVC; bool calculatedDP, calculatedDC, calculatedPP; bool calculatedIRCC, calculatedIC, calculatedPRP; bool calculatedTriad; bool calculatedGraphSymmetry, calculatedGraphReciprocity; bool calculatedGraphDensity, calculatedGraphWeighted; bool m_progressCanceled; bool m_graphIsDirected, m_graphIsSymmetric, m_graphIsWeighted, m_graphIsConnected; int csRecDepth; QString m_fileName, m_graphName, initEdgeColor, initVertexColor, initVertexNumberColor, initVertexLabelColor; QString initVertexShape, initVertexIconPath; QString htmlHead, htmlHeadLight, htmlEnd; QDateTime actualDateTime; }; #endif socnetv-app-39db829/src/graph/000077500000000000000000000000001517721000100162015ustar00rootroot00000000000000socnetv-app-39db829/src/graph/centrality/000077500000000000000000000000001517721000100203575ustar00rootroot00000000000000socnetv-app-39db829/src/graph/centrality/graph_centrality.cpp000066400000000000000000000454421517721000100244330ustar00rootroot00000000000000/** * @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 * @param considerWeights * @param inverseWeights */ void Graph::centralityInformation(const bool considerWeights, const bool inverseWeights) { qDebug() << "Graph::centralityInformation()"; if (calculatedIC) { qDebug() << "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; progressFinish(); 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; progressFinish(); return; } createMatrixAdjacency(dropIsolates, considerWeights, inverseWeights, symmetrize); if (progressCanceled()) { progressFinish(); return; } QString pMsg = tr("Computing Information Centralities. \nPlease wait..."); progressStatus(pMsg); progressCreate(n, 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); } progressUpdate(n / 3); if (progressCanceled()) { progressFinish(); return; } progressStatus(tr("Computing inverse adjacency matrix. Please wait...")); invM.inverse(WM); progressStatus(tr("Computing IC scores. Please wait...")); progressUpdate(2 * n / 3); if (progressCanceled()) { progressFinish(); return; } 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(); progressUpdate(n); progressFinish(); } /** * @brief Computes Eigenvector centrality * @param considerWeights * @param inverseWeights */ void Graph::centralityEigenvector(const bool &considerWeights, const bool &inverseWeights, const bool &dropIsolates) { if (calculatedEVC) { qDebug() << "Graph not changed - EVC already computed. Return."; return; } qDebug() << "(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; progressFinish(); return; } qreal *EVC = new (nothrow) qreal[N]; Q_CHECK_PTR(EVC); qreal SEVC = 0; createMatrixAdjacency(dropIsolates, considerWeights, inverseWeights, symmetrize); if (progressCanceled()) { delete[] EVC; progressFinish(); return; } QString pMsg = tr("Computing Eigenvector Centrality scores. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, pMsg); if (useDegrees) { qDebug() << "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 { qDebug() << "Using unit initial EVC vector"; for (int k = 0; k < N; k++) EVC[k] = 1; } progressUpdate(N / 3); if (progressCanceled()) { delete[] EVC; progressFinish(); return; } AM.powerIteration(EVC, sumEVC, maxEVC, maxNodeEVC, minEVC, minNodeEVC, 0.0000001, 500); progressUpdate(2 * N / 3); if (progressCanceled()) { delete[] EVC; progressFinish(); 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; progressUpdate(N); progressFinish(); } /** * @brief Calculates the degree (outDegree) centrality of each vertex - diagonal included * @param considerWeights * @param dropIsolates */ void Graph::centralityDegree(const bool &considerWeights, const bool &dropIsolates) { if (calculatedDC) { qDebug() << "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); qDebug() << pMsg; progressStatus(pMsg); progressCreate(N, pMsg); progressUpdate(N / 3); if (progressCanceled()) { progressFinish(); 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) } progressUpdate(2 * N / 3); if (progressCanceled()) { progressFinish(); 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 qDebug() << "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; progressUpdate(N); progressFinish(); } /** * @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. * @param considerWeights * @param inverseWeights * @param dropIsolates */ void Graph::centralityClosenessIR(const bool considerWeights, const bool inverseWeights, const bool dropIsolates) { if (calculatedIRCC) { qDebug() << "Graph not changed - no need to recompute IRCC. Returning"; return; } qDebug() << "(Re)Computing IRCC closeness centrality..."; graphDistancesGeodesic(false, considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { return; } // calculate centralities VList::const_iterator it, jt; int progressCounter = 0; 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); progressCreate(N, pMsg); qDebug() << "dropIsolates" << dropIsolates; qDebug() << "computing scores for actors: " << N; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); 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 = (*it)->distance((*jt)->number()); if (dist != RAND_MAX) { sumD += dist; Ji++; // compute |Ji| } qDebug() << "dist(" << (*it)->number() << "," << (*jt)->number() << ") =" << dist << "sumD" << sumD << " Ji" << Ji; } qDebug() << "" << (*it)->number() << " sumD" << sumD << "distanceSum" << (*it)->distanceSum(); // sanity check for sumD=0 (=> node is disconnected) if (sumD != 0) { averageD = sumD / Ji; qDebug() << "averageD = sumD / Ji" << averageD; qDebug() << "Ji / (N-1)" << Ji << "/" << N - 1; IRCC = (Ji / (qreal)(N - 1)) / averageD; qDebug() << "[ 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; progressFinish(); } /** * @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) { qDebug() << "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::DP: return calculatedDP; case IndexType::PRP: return calculatedPRP; case IndexType::PP: return calculatedPP; default: return false; } }socnetv-app-39db829/src/graph/centrality/graph_prestige.cpp000066400000000000000000000362731517721000100241010ustar00rootroot00000000000000/** * @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. * @param weights * @param dropIsolates */ void Graph::prestigeDegree(const bool &considerWeights, const bool &dropIsolates) { if (calculatedDP) { qDebug() << "Graph not changed - no need to recompute Degree Prestige scores. Returning"; return; } qDebug() << "(Re)Computing Degree Prestige scores..."; int N = vertices(dropIsolates); int v2 = 0, v1 = 0; int progressCounter = 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); progressCreate(N, pMsg); qDebug() << "vertices" << N << "graph modified. Recomputing..."; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); if (progressCanceled()) { delete enabledInEdges; progressFinish(); return; } v1 = (*it)->number(); qDebug() << "computing DP for vertex" << v1; DP = 0; if (!(*it)->isEnabled()) { qDebug() << "vertex disabled. Continue."; continue; } qDebug() << "Iterate over inbound edges of " << v1; enabledInEdges = (*it)->inEdgesEnabledHash(); hit = enabledInEdges->cbegin(); while (hit != enabledInEdges->cend()) { v2 = hit.key(); qDebug() << "inbound edge from" << v2; if (!edgeExists(v2, v1)) { // sanity check qDebug() << "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; qDebug() << "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; qDebug() << "vertex " << (*it)->number() << " DP " << DP << " SDP " << (*it)->SDP(); resolveClasses(SDP, discreteDPs, classesSDP); qDebug("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; qDebug("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; qDebug("Graph: varianceSDP = %f, groupDP = %f", varianceSDP, groupDP); } delete enabledInEdges; calculatedDP = true; progressFinish(); } /** * @brief Computes Proximity Prestige of each vertex * Also the mean value and the variance of it.. */ void Graph::prestigeProximity(const bool considerWeights, const bool inverseWeights, const bool dropIsolates) { if (calculatedPP) { qDebug() << "Graph not changed - no need to recompute proximity prestige. Returning"; return; } qDebug() << "(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; int progressCounter = 0; QString pMsg = tr("Computing Proximity Prestige scores. \nPlease wait ..."); progressStatus(pMsg); progressCreate(V, pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); 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 = (*jt)->distance((*it)->number()); if (dist != RAND_MAX) { PP += dist; Ii++; // compute |Ii| } } qDebug() << "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); // qDebug("PP classes = %i ", 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; qDebug() << "sumPP = " << sumPP << " meanPP = " << meanPP << " variancePP " << variancePP; calculatedPP = true; progressFinish(); } /** * @brief Calculates the PageRank Prestige of each vertex * @param dropIsolates */ void Graph::prestigePageRank(const bool &dropIsolates) { if (calculatedPRP) { qDebug() << "Graph not changed - no need to recompute Pagerank scores. Return "; return; } qDebug() << "(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); progressCreate(N, 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(); qDebug() << "node " << (*it)->number() << " PR = " << (*it)->PRP() << " inLinks (set const): " << inLinks << " outLinks (set const): " << outLinks; } if (edgesEnabled() == 0) { qDebug() << "all vertices are isolated and of equal PR. Stop"; return; } progressUpdate(N / 3); if (progressCanceled()) { progressFinish(); return; } // begin iteration - continue until we reach our desired delta while (maxDelta > delta) { qDebug() << "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(); qDebug() << "computing PR for node: " << (*it)->number() << " current PR " << oldPRP; if ((*it)->isIsolated()) { // isolates have constant PR = 1/N qDebug() << "isolated - CONTINUE "; continue; } jt = (*it)->m_inEdges.cbegin(); qDebug() << "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(); qDebug() << "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; qDebug() << "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; qDebug() << "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); qDebug() << "Setting new maxDelta = " << maxDelta; } } // normalize in every iteration qDebug() << "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++; } progressUpdate(2 * N / 3); if (progressCanceled()) { progressFinish(); return; } if (N != 0) { meanPRP = sumPRP / (qreal)N; } else { meanPRP = SPRP; } qDebug() << "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); qDebug() << "vertex: " << (*it)->number() << " PR = " << PRP << " standard PR = " << SPRP << " t_sumPRP " << t_sumPRP; t_variance = (PRP - meanPRP); t_variance *= t_variance; qDebug() << "PRP " << (*it)->PRP() << " t_variance " << PRP - meanPRP << " t_variance^2" << t_variance; variancePRP += t_variance; } qDebug() << "PRP' Variance " << variancePRP << " N " << N; variancePRP = variancePRP / (qreal)N; qDebug() << "PRP' Variance: " << variancePRP; calculatedPRP = true; progressUpdate(N); progressFinish(); return; } socnetv-app-39db829/src/graph/clustering/000077500000000000000000000000001517721000100203605ustar00rootroot00000000000000socnetv-app-39db829/src/graph/clustering/graph_clustering_coefficients.cpp000066400000000000000000000230641517721000100271520ustar00rootroot00000000000000/** * @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. * * 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(); qDebug() << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "Not modified. Returning cached clucof =" << clucof; return clucof; } qDebug() << "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) { if (it.key() != v1) 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) { if (it.key() != v1) 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) { if (it.key() != v1) neighborhood.insert(it.key()); } delete inN; inN = nullptr; } k = neighborhood.size(); qDebug() << "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. qDebug() << "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); qDebug() << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "Undirected edge added:" << edge; } } else { // Count each directed edge independently. if (!neighborhoodEdges.contains(edge)) { neighborhoodEdges.insert(edge, true); qDebug() << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "Directed edge added:" << edge; } } } } } nom = neighborhoodEdges.size(); qDebug() << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "edges in neighbourhood =" << nom; if (nom == 0) { qDebug() << "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; qDebug() << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "Undirected graph. Max neighbourhood edges =" << denom; } else { denom = k * (k - 1.0); qDebug() << "Graph::clusteringCoefficientLocal(" << v1 << ") -" << "Directed graph. Max neighbourhood edges =" << denom; } clucof = nom / denom; qDebug() << "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(const bool updateProgress) { qDebug() << "Graph::clusteringCoefficient()"; averageCLC = 0; varianceCLC = 0; maxCLC = 0; minCLC = 1; qreal temp = 0; qreal x = 0; qreal N = vertices(); int progressCounter = 0; VList::const_iterator vertex; QString pMsg = tr("Computing Clustering Coefficient. \n" "Please wait..."); progressStatus(pMsg); progressCreate(N, pMsg); for (vertex = m_graph.cbegin(); vertex != m_graph.cend(); ++vertex) { if (updateProgress) { progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); return averageCLC; } } temp = clusteringCoefficientLocal((*vertex)->number()); if (temp > maxCLC) { maxCLC = temp; maxNodeCLC = (*vertex)->number(); } if (temp < minCLC) { minNodeCLC = (*vertex)->number(); minCLC = temp; } averageCLC += temp; } averageCLC = averageCLC / N; qDebug() << "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; if (updateProgress) { progressFinish(); } return averageCLC; } socnetv-app-39db829/src/graph/clustering/graph_clustering_hierarchical.cpp000066400000000000000000000333301517721000100271240ustar00rootroot00000000000000/** * @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); qDebug() << "Graph::graphClusteringHierarchical() - " << "metric" << metric << "method" << graphClusteringMethodTypeToString(method) << "diagonal" << diagonal << "diagram" << diagram << "dropIsolates" << dropIsolates; qDebug() << "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(); qDebug() << "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); progressCreate(N, pMsg); while (clustersLeft > 1) { progressUpdate(seq); if (progressCanceled()) { progressFinish(); return false; } qDebug() << "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]; qDebug() << "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); qDebug() << "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; qDebug() << " 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; qDebug() << " 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(); // qDebug() << " key now"<< prev.key() << ": " << prev.value() ; } } m_clustersIndex.erase(--it); // erase the last element in map qDebug() << "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; qDebug() << "j = mergedClusterIndex " << mergedClusterIndex + 1; for (int i = 0; i < clustersLeft; i++) { if (i == deletedClusterIndex) { // qDebug() << "Graph::graphClusteringHierarchical() -" // <<"SKIP this as it is one of the merged clusters."; 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); } qDebug() << "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); } qDebug() << "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; } qDebug() << "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); } qDebug() << "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(); qDebug() << "m_clustersByName" << m_clustersByName; progressFinish(); return true; } socnetv-app-39db829/src/graph/clustering/graph_triad_census.cpp000066400000000000000000000302571517721000100247370ustar00rootroot00000000000000/** * @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; int N = vertices(); int progressCounter = 0; VList::const_iterator v1; VList::const_iterator v2; VList::const_iterator v3; qDebug() << "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); qDebug() << " initializing triadTypeFreqs[" << i << "] = " << triadTypeFreqs[i]; } QString pMsg = tr("Computing Triad Census. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, pMsg); for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); v1++) { progressUpdate(++progressCounter); if (progressCanceled()) { calculatedTriad = false; progressFinish(); 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++; qDebug() << "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 qDebug() << " ****** 003 COUNTER: " << counter_021; calculatedTriad = true; progressFinish(); 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; qDebug() << "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! // qDebug() << "triad vertices: ( "<< vert1->number() << ", "<< vert2->number()<< ", "<< vert3->number()<< " ) = (" <number() ; 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())) { // qDebug() << " vertex " << source->number() << " is IN linked from " <number(); if (isInLinked) { triadTypeFreqs[4]++; //"021U" break; } else if (isOutLinked) { triadTypeFreqs[5]++; //"021C" break; } else { isInLinked = true; } } } } break; case 3: qDebug() << "triad vertices: ( " << vert1->number() << ", " << vert2->number() << ", " << vert3->number() << " ) = (" << mut << "," << asy << "," << nul << ")"; isTrans = false; foreach (GraphVertex *source, m_triad) { qDebug() << " 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; // qDebug() << "triad vertices: ( "<< vert1->number() << ", "<< vert2->number()<< ", "<< vert3->number()<< " ) = (" <number() ; 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; qDebug() << "triad vertices: ( " << vert1->number() << ", " << vert2->number() << ", " << vert3->number() << " ) = (" << mut << "," << asy << "," << nul << ")"; foreach (GraphVertex *source, m_triad) { // qDebug() << " vertex " << source->number() ; 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())) { // qDebug() << " vertex " << source->number() << " is IN linked from " <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-app-39db829/src/graph/cohesion/000077500000000000000000000000001517721000100200105ustar00rootroot00000000000000socnetv-app-39db829/src/graph/cohesion/graph_cliques.cpp000066400000000000000000000206711517721000100233500ustar00rootroot00000000000000/** * @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); qDebug() << "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]; qDebug() << "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)); qDebug() << "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 an undirected (?) graph. * Implements the Bron–Kerbosch algorithm, a recursive backtracking algorithm * that searches for all maximal cliques in a given graph G. * Given three sets R, P, and X, the algorithm finds the maximal cliques that * include all of the vertices in R, some of the vertices in P, and none of * the vertices in X. * In each call to the algorithm, P and X are disjoint sets whose union consists * of those vertices that form cliques when added to R. * In other words, P βˆͺ X is the set of vertices which are joined to every element of R. * When P and X are both empty there are no further elements that can be added to R, * so R is a maximal clique and the algorithm outputs R. * The recursion is initiated by setting R and X to be the empty set and P to be * the vertex set of the graph. * Within each recursive call, the algorithm considers the vertices in P in turn. * if there are no vertices, it either reports R as a maximal clique (if X is empty), * or backtracks. * For each vertex v chosen from P, it makes a recursive call in which v is added to R * and in which P and X are restricted to the neighbor set NBS(v) of v, * which finds and reports all clique extensions of R that contain v. * Then, it moves v from P to X to exclude it from consideration in future cliques * and continues with the next vertex in P. * @param R * @param P * @param X */ void Graph::graphCliques(QSet R, QSet P, QSet X) { csRecDepth++; qDebug() << "Graph::graphCliques() - STARTS HERE. csRecDepth:" << csRecDepth << " - Check if we are at initialization step"; QList myNeightbors; if (R.isEmpty() && P.isEmpty() && X.isEmpty()) { int V = vertices(); P.reserve(V); R.reserve(V); X.reserve(V); P = verticesSet(); qDebug() << "Graph::graphCliques() - initialization step. R, X empty and P=V(G): " << P; CLQM.zeroMatrix(V, V); // co-membership matrix CLQM m_cliques.clear(); VList::const_iterator it; int vertex = 0; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { vertex = (*it)->number(); myNeightbors = (*it)->reciprocalNeighborhoodList(); neighboursHash[vertex] = QSet(myNeightbors.constBegin(), myNeightbors.constEnd()); qDebug() << "Graph::graphCliques() - initialization step. NeighborhoodList of v" << vertex << ": " << neighboursHash[vertex]; (*it)->clearCliques(); } } qDebug() << "Graph::graphCliques() - check if P and X are both empty (which would mean we have a clique in R)..."; if (P.isEmpty() && X.isEmpty()) { qDebug() << "Graph::graphCliques() - P and X are both empty. MAXIMAL clique R=" << R; QList clique = R.values(); graphCliqueAdd(clique); csRecDepth--; return; } int v; QSet NBS; QSet Rnext, Pnext, Xnext; QSet::iterator i = P.begin(); int counter = 0; // Loop over vertices in P, randomly qDebug() << "Graph::graphCliques() - Start looping over vertices in P (randomly)"; while (i != P.end()) { counter++; v = *i; qDebug() << "Graph::graphCliques() - CURRENT v:" << v << " P:" << P << " P.count=" << P.size() << " R:" << R << " X:" << X; NBS = neighboursHash[v]; if (NBS.size() == 1 && NBS.contains(v)) { qDebug() << "Graph::graphCliques() - v:" << v << "has only a tie to itself"; // graphCliques( R, P, X ); ++i; continue; } Rnext = R; Rnext << v; Pnext = P & NBS; Xnext = X & NBS; qDebug() << "Graph::graphCliques() - v:" << v << "RECURSIVE CALL to graphCliques ( R ⋃ {v}, P β‹‚ NB(v), X β‹‚ NBS(v) )" << "\n" << "NBS(v):" << NBS << "\n" << "Rnext = R ⋃ {v}:" << Rnext << "\n" << "Pnext = P β‹‚ NBS(v):" << Pnext << "\n" << "Xnext = X β‹‚ NBS(v):" << Xnext; if (csRecDepth == 1) { progressUpdate(counter); progressStatus(tr("Finding cliques: Recursive backtracking for actor ") + QString::number(v)); if (progressCanceled()) { csRecDepth--; return; } } // find all clique extensions of R that contain v try { graphCliques(Rnext, Pnext, Xnext); } catch (...) { qDebug() << "Graph::graphCliques() - ERROR"; return; } // Set P = P \ v i = P.erase(i); // P-=v; // Set X = X + v X.insert(v); qDebug() << "Graph::graphCliques() - v:" << v << "RETURNED from recursive call - recDepth: " << csRecDepth << " Moved v:" << v << " from P to X to be excluded in the future" << " P=" << P << " P.count:" << P.size() << " R=" << R << " R.count:" << R.size() << " X=" << X << " X.count:" << X.size() << " Continuing with next v in P"; //++i; } // end while loop qDebug() << "Graph::graphCliques() - FINISHED loop over P:" << P << "at csRecDepth:" << csRecDepth; csRecDepth--; } /** Returns the number of maximal cliques which include a given actor */ int Graph::graphCliquesContaining(const int &actor, const int &size) { qDebug() << "*** 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) { qDebug() << "Graph::graphCliquesOfSize()"; return m_cliques.values(size).size(); } socnetv-app-39db829/src/graph/core/000077500000000000000000000000001517721000100171315ustar00rootroot00000000000000socnetv-app-39db829/src/graph/core/graph_metadata.cpp000066400000000000000000000155611517721000100226060ustar00rootroot00000000000000/** * @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) { qDebug() << "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) { qDebug() << "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) { qDebug() << "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. qDebug() << "This is a empty new network. Will not change status."; emit signalGraphModified(isDirected(), 0, 0, 0, false); return; } else if (graphNewStatus == ModStatus::NewNet) { qDebug() << "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 qDebug() << "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 // qDebug()<<"Major changes, invalidating computations, setting graph as changed..."; 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; calculatedDP = false; calculatedDC = false; calculatedPP = false; calculatedIRCC = false; calculatedIC = false; calculatedEVC = false; calculatedPRP = false; if (signalMW) { // qDebug() << "signaling to MW that the graph is modified..."; 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; } // qDebug()<<"minor changes but needs saving..."; 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) { qDebug() << "Graph::isModified() - isModified: true"; return true; } qDebug() << "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) { qDebug() << "isLoaded: true "; return true; } qDebug() << "isLoaded: false "; return false; } /** * @brief Returns true if the graph is saved. * @return */ bool Graph::isSaved() const { if (m_graphModStatus == ModStatus::NewNet) { qDebug() << "isSaved: true (new net)"; return true; } else if (m_graphModStatus == ModStatus::SavedUnchanged) { qDebug() << "isSaved: true"; return true; } qDebug() << "isSaved: false"; return false; } socnetv-app-39db829/src/graph/core/graph_state_flags.cpp000066400000000000000000000204461517721000100233200ustar00rootroot00000000000000/** * @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 graph is weighted (valued), * i.e. if any e in |E| has value not 0 or 1 * Complexity: O(n^2) * @return */ bool Graph::isWeighted() { if (calculatedGraphWeighted) { qDebug() << "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; qDebug() << "graph is weighted:" << m_graphIsWeighted; return m_graphIsWeighted; } /** * @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() { qDebug() << "Graph::isSymmetric() "; if (calculatedGraphSymmetry) { qDebug() << "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; // qDebug() <<"Graph::isSymmetric() - " // << " graph not symmetric because " // << v1 << "->" << v2 << " weight " << weight // << " differs from " << v2 << "->" << v1 ; break; } ++hit; } } // delete enabledOutEdges; qDebug() << "Graph: isSymmetric() - Finished. Result:" << m_graphIsSymmetric; calculatedGraphSymmetry = true; return m_graphIsSymmetric; } /** * @brief Transforms the graph to symmetric (all edges reciprocal) */ void Graph::setSymmetric() { qDebug() << "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(); // qDebug() << "iterate over edges of v1 " << v1; enabledOutEdges = (*it)->outEdgesEnabledHash(); it1 = enabledOutEdges.cbegin(); while (it1 != enabledOutEdges.cend()) { v2 = it1.key(); weight = it1.value(); // qDebug() << "v1" << v1 << "outLinked to" << v2 << ", weight:" << weight; invertWeight = edgeExists(v2, v1); if (invertWeight == 0) { // qDebug() << "v1" << v1 << "is NOT inLinked from v2" << v2 ; edgeCreate(v2, v1, weight, initEdgeColor, false, true, false, QString(), false); } else { // qDebug() << "v1" << v1 << "is inLinked from v2" << v2 ; 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) { qDebug() << "Graph::setDirected - Setting graph directed to:" << toggle; if (!toggle) { setUndirected(true, signalMW); return; } if (toggle == isDirected()) { qDebug() << "Graph::setDirected - Same as now, nothing to do."; return; } m_graphIsDirected = 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) { qDebug() << "Graph::setUndirected - Toggling graph undirected to" << toggle; qDebug() << "Graph::setUndirected - m_graphIsSymmetric:" << m_graphIsSymmetric << "m_graphIsDirected:" << m_graphIsDirected << "m_totalEdges:" << m_totalEdges << "calculatedEdges:" << calculatedEdges; if (!toggle) { setDirected(true); return; } if (toggle == isUndirected()) { qDebug() << "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; // 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(); qDebug() << "Graph::setUndirected - Iterating over edges of v1 " << v1; enabledOutEdges = (*it)->outEdgesEnabledHash(); it1 = enabledOutEdges.cbegin(); while (it1 != enabledOutEdges.cend()) { v2 = it1.key(); weight = it1.value(); qDebug() << "edge" << "v1" << v1 << "->" << v2 << " = " << "weight" << weight; edgeTypeSet(v1, v2, weight, EdgeType::Undirected); ++it1; } } } else { qDebug() << "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() { qDebug() << "Graph::isDirected m_graphIsDirected" << m_graphIsDirected; return m_graphIsDirected; } /** * @brief Returns true if graph is undirected * * @return bool */ bool Graph::isUndirected() { // qDebug() << "isUndirected: " << !m_graphIsDirected; return !m_graphIsDirected; } socnetv-app-39db829/src/graph/core/graph_structure_metrics.cpp000066400000000000000000000310431517721000100246050ustar00rootroot00000000000000/** * @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) { qDebug() << "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) { qDebug() << "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() { qDebug() << "Graph::graphReciprocity()"; if (calculatedGraphReciprocity) { qDebug() << "Graph::graphReciprocity() - graph not modified and " "already calculated reciprocity. Returning previous result: " << m_graphReciprocityArc; return m_graphReciprocityArc; } qDebug() << "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(); 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; } qDebug() << 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; } qDebug() << 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; qDebug() << "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) { qDebug() << "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(); qDebug() << "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(); qDebug() << v1 << "->" << v2 << "=" << weight << "Checking opposite."; if (weight > threshold) { if (!binaryTies->contains(QString::number(v1) + "--" + QString::number(v2))) { qDebug() << v1 << "--" << v2 << " over threshold. Adding"; binaryTies->insert(QString::number(v1) + "--" + QString::number(v2), 1); } else { qDebug() << v1 << "--" << v2 << " exists. Binary Tie already found. Continue"; } } ++it1; } } relationAdd("Binary-" + QString::number(threshold), true); QHash::const_iterator it2; it2 = binaryTies->constBegin(); QStringList vertices; qDebug() << "creating binary tie edges"; while (it2 != binaryTies->constEnd()) { vertices = it2.key().split("--"); qDebug() << "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(); qDebug() << "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); qDebug() << "final relations" << relations(); } socnetv-app-39db829/src/graph/crawler/000077500000000000000000000000001517721000100176405ustar00rootroot00000000000000socnetv-app-39db829/src/graph/crawler/graph_crawler.cpp000066400000000000000000000160561517721000100231740ustar00rootroot00000000000000/** * @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) { qDebug() << "Terminating webCrawler threads - reason " << reason << "Checking webcrawlerThread..."; while (webcrawlerThread.isRunning()) { qDebug() << "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) { qDebug() << "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); qDebug() << "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()) { qDebug() << "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); qDebug() << "WebCrawler created and moved to its own thread:" << web_crawler->thread(); // Connect signals and slots qDebug() << "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... qDebug() << "Starting WebCrawler thread..."; webcrawlerThread.start(); // Create the initial vertex for the starting url qDebug() << "Creating initial node 1, initialUrlStr:" << startUrl.toString(); vertexCreateAtPosRandomWithLabel(1, startUrl.toString(), false); // Call the spider to download the html code of the starting url . qDebug() << "Calling webSpider()..."; this->webSpider(); qDebug("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) { qDebug() << "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) { qDebug() << "webSpider - reached m_crawler_max_urls. Break."; break; } // Take the first url awaiting in the queue qDebug() << "webSpider - urlQueue size: " << urlQueue->size() << " - Taking the first url from the urlQueue "; QUrl currentUrl = urlQueue->dequeue(); qDebug() << "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() { qDebug() << "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-app-39db829/src/graph/distances/000077500000000000000000000000001517721000100201565ustar00rootroot00000000000000socnetv-app-39db829/src/graph/distances/graph_distance_cache.cpp000066400000000000000000000256611517721000100247720ustar00rootroot00000000000000/** * @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 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) { qDebug() << "Graph::graphMatrixShortestPathsCreate()"; graphDistancesGeodesic(false, considerWeights, inverseWeights, dropIsolates); if (progressCanceled()) { calculatedDistances = false; return; } VList::const_iterator it, jt; int N = vertices(dropIsolates, false, true); int progressCounter = 0; int source = 0, target = 0; int i = 0, j = 0; qDebug() << "Graph::graphMatrixShortestPathsCreate() - Resizing matrix to hold " << N << " vertices"; SIGMA.resize(N, N); QString pMsg = tr("Creating shortest paths matrix. \nPlease wait "); progressStatus(pMsg); progressCreate(N, pMsg); qDebug() << "Graph::graphMatrixShortestPathsCreate() - Writing shortest paths matrix..."; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); if (progressCanceled()) { calculatedDistances = false; progressFinish(); return; } source = (*it)->number(); if ((*it)->isIsolated() && dropIsolates) { qDebug() << "Graph::graphMatrixShortestPathsCreate() - " << source << "isolated. SKIP"; continue; } if (!(*it)->isEnabled()) { qDebug() << "Graph::graphMatrixShortestPathsCreate() - " << source << "disabled. SKIP"; continue; } qDebug() << "Graph::graphMatrixShortestPathsCreate() - source" << source << "i" << i; for (jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { target = (*jt)->number(); if ((*jt)->isIsolated() && dropIsolates) { qDebug() << "Graph::graphMatrixShortestPathsCreate() - " << target << "isolated. SKIP"; continue; } if (!(*jt)->isEnabled()) { qDebug() << "Graph::graphMatrixShortestPathsCreate() - " << target << "disabled. SKIP"; continue; } qDebug() << "Graph::graphMatrixShortestPathsCreate() - " << "target" << target << "j" << j; qDebug() << "Graph::graphMatrixShortestPathsCreate() - setting SIGMA (" << i << "," << j << ") =" << (*it)->shortestPaths(target); SIGMA.setItem(i, j, (*it)->shortestPaths(target)); j++; } j = 0; i++; } progressFinish(); } /** * @brief Creates the matrix DM of geodesic distances between vertices. * * Phase 1: calls graphDistancesGeodesic() which runs the DistanceEngine. * The engine owns its own progress dialog β€” it creates it, updates it, * and destroys it before returning. The dialog stack is empty on return. * * Phase 2: fills the DM matrix from the cached per-vertex distances. * This is an O(NΒ²) memory-write pass β€” fast enough to need no progress * dialog of its own. No progressUpdate or progressFinish is called here; * the caller owns the dialog lifecycle for any subsequent phase. * * @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) { qDebug() << "Graph::graphMatrixDistanceGeodesicCreate()"; // Phase 1: compute all geodesic distances via DistanceEngine. // The engine owns its own progress dialog for this phase. 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; qDebug() << "Graph::graphMatrixDistanceGeodesicCreate() - " "Resizing distance matrix to hold " << N << " vertices"; DM.resize(N, N); // Phase 2: fill DM from cached per-vertex distances. // No progressUpdate here β€” the DistanceEngine dialog is already destroyed // by this point. The matrix-fill is O(NΒ²) memory writes and needs no // progress reporting of its own. 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; } // qDebug() << "Graph: graphMatrixDistanceGeodesicCreate() - " // << "target" << target << "j" << j; // qDebug() << "Graph: graphMatrixDistanceGeodesicCreate() - setting DM (" // << i << "," << j << ") =" << (*it)->distance(target); DM.setItem(i, j, (*it)->distance(target)); j++; } j = 0; i++; } // No progressFinish() here β€” the caller owns the outer dialog lifecycle. return true; } /** * @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 * - Stress: SC(u) = Sum ( sigma(i,j) ) for every s,t in V * - Eccentricity: EC(u) = 1/maxDistance(u,t) for some t in V * - Closeness: CC(u) = 1 / Sum( d(u,t) ) for every t in V * - Power: * @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); } // // SSSP helpers // void Graph::ssspStackClear() { while (!Stack.empty()) { Stack.pop(); } } bool Graph::ssspStackEmpty() const { return Stack.empty(); } int Graph::ssspStackTop() const { return Stack.top(); } void Graph::ssspStackPop() { Stack.pop(); } int Graph::ssspStackSize() const { return static_cast(Stack.size()); } void Graph::ssspStackPush(int v) { Stack.push(v); } void Graph::ssspNthOrderClear() { sizeOfNthOrderNeighborhood.clear(); } H_f_i::const_iterator Graph::ssspNthOrderBegin() const { return sizeOfNthOrderNeighborhood.constBegin(); } H_f_i::const_iterator Graph::ssspNthOrderEnd() const { return sizeOfNthOrderNeighborhood.constEnd(); } int Graph::ssspNthOrderValue(qreal dist) const { return sizeOfNthOrderNeighborhood.value(dist, 0); } void Graph::ssspNthOrderIncrement(int dist) { sizeOfNthOrderNeighborhood.insert( dist, sizeOfNthOrderNeighborhood.value(dist, 0) + 1); } void Graph::ssspNthOrderIncrement(qreal dist) { sizeOfNthOrderNeighborhood.insert( dist, sizeOfNthOrderNeighborhood.value(dist, 0) + 1); } void Graph::ssspComponentReset(int value) { sizeOfComponent = value; } void Graph::ssspComponentAdd(int delta) { sizeOfComponent += delta; } int Graph::ssspComponentSize() const { return sizeOfComponent; } // // 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; } void Graph::setSymmetricCached(bool v) { m_graphIsSymmetric = v; } bool Graph::symmetricCached() const { return m_graphIsSymmetric; } void Graph::setConnectedCached(bool v) { m_graphIsConnected = v; } 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::setAverageDistanceCached(qreal v) { m_graphAverageDistance = v; } socnetv-app-39db829/src/graph/distances/graph_distance_facade.cpp000066400000000000000000000106451517721000100251260ustar00rootroot00000000000000/** * @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 // 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) { qDebug() << "Graph::graphDistanceGeodesic()"; graphDistancesGeodesic(false, considerWeights, inverseWeights, false); return m_graph[vpos[v1]]->distance(v2); } /** * @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) { qDebug() << "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) { qDebug() << "Graph::graphDistanceGeodesicAverage() - Computing distances..."; graphDistancesGeodesic(false, considerWeights, inverseWeights, dropIsolates); qDebug() << "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() { qDebug() << "Graph::getGeodesicsCount()"; graphDistancesGeodesic(false, false, false, false); qDebug() << "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() { qDebug() << "Graph::isConnected() "; if (calculatedDistances) { qDebug() << "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 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-app-39db829/src/graph/filters/000077500000000000000000000000001517721000100176515ustar00rootroot00000000000000socnetv-app-39db829/src/graph/filters/filter_condition.h000066400000000000000000000035661517721000100233670ustar00rootroot00000000000000/** * @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; /** 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-app-39db829/src/graph/filters/graph_edge_filters.cpp000066400000000000000000000325461517721000100242040ustar00rootroot00000000000000/** * @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" /** * @brief Filters (disables) edges according the specified threshold weight * * * @param m_threshold * @param overThreshold */ void Graph::edgeFilterByWeight(const qreal m_threshold, const bool overThreshold) { QString words; if (overThreshold) { qDebug() << "filtering edges with weight over or equal" << m_threshold; words = "equal or over"; } else { qDebug() << "Filtering edges with weight below or equal" << m_threshold; words = "equal or under"; } 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 reverseEdgeWeight = (*it)->hasEdgeFrom(target); if (reverseEdgeWeight != 0 && reverseEdgeWeight >= m_threshold) { // reverse edge exists and doesn't match. It must be preserved. preserveReverseEdge = true; } // qDebug() << source << "->" << target << "weight:" << weight << "will be disabled - preserveReverseEdge:" << preserveReverseEdge << ". Emitting signal..."; // 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) // qDebug() << "disabling the inedge of the target vertex: " << target << "<-" << source; this->edgeInboundStatusSet(target, source, false); emit signalSetEdgeVisibility(m_curRelation, source, target, false, preserveReverseEdge, weight, reverseEdgeWeight); } else { // qDebug() << source << "->" << target << "weight:" << weight << "will be enabled. Emitting signal..."; // 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) // qDebug() << "enabling the inedge of the target vertex: " << target << "<-" << source; 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 reverseEdgeWeight = (*it)->hasEdgeFrom(target); if (reverseEdgeWeight != 0 && reverseEdgeWeight <= m_threshold) { // reverse edge exists and doesn't match. It must be preserved. preserveReverseEdge = true; } // qDebug() << source << "->" << target << "weight:" << weight << "will be disabled - preserveReverseEdge:" << preserveReverseEdge << ". Emitting signal..."; // 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) // qDebug() << "disabling the inedge of the target vertex: " << target << "<-" << source; this->edgeInboundStatusSet(target, source, false); emit signalSetEdgeVisibility(m_curRelation, source, target, false, preserveReverseEdge, weight, reverseEdgeWeight); } else { // qDebug() << source << "->" << target << "weight:" << weight << "will be enabled. Emitting signal..."; // 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) // qDebug() << "enabling the inedge of the target vertex: " << target << "<-" << source; 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() { qDebug() << "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()); const bool preserveReverse = (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 Toggles (enables or disables) all edges of the given relation * * Calls the homonymous method of GraphVertex class. * * @param relation * @param status */ void Graph::edgeFilterByRelation(int relation, bool status) { qDebug() << "toggling all edges in relation" << relation << "to status" << status; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) { // Skip if the node is disabled. continue; } (*it)->setEnabledEdgesByRelation(relation, status); } } /** * @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) { qDebug() << "Toggling unilateral edges:" << toggle; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { (*it)->setEnabledUnilateralEdges(toggle); } 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. Call vertexFilterRestoreAll() to undo. * * 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) { qDebug() << "Graph::edgeFilterByAttribute() key:" << cond.key << "op:" << static_cast(cond.op) << "value:" << cond.value; // Build visible set: (source, target) pairs whose custom attribute satisfies cond. // We use matchesCondition from graph_node_filters.cpp via a local lambda here. auto matches = [&](const QString &attrVal) -> bool { switch (cond.op) { case FilterCondition::Op::Eq: return attrVal == cond.value; case FilterCondition::Op::Neq: return attrVal != cond.value; case FilterCondition::Op::Contains: return attrVal.contains(cond.value, Qt::CaseInsensitive); default: break; } bool okA = false, okB = false; const double a = attrVal.toDouble(&okA); const double b = cond.value.toDouble(&okB); if (okA && okB) { switch (cond.op) { case FilterCondition::Op::Gt: return a > b; case FilterCondition::Op::Lt: return a < b; case FilterCondition::Op::Gte: return a >= b; case FilterCondition::Op::Lte: return a <= b; default: break; } } switch (cond.op) { case FilterCondition::Op::Gt: return attrVal > cond.value; case FilterCondition::Op::Lt: return attrVal < cond.value; case FilterCondition::Op::Gte: return attrVal >= cond.value; case FilterCondition::Op::Lte: return attrVal <= cond.value; default: break; } return false; }; // 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) && 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; 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); const bool preserveReverse = (reverseWeight != 0); const QHash attrs = (*vi)->outEdgeCustomAttributes(target); const bool condMet = attrs.contains(cond.key) && 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-app-39db829/src/graph/filters/graph_node_filters.cpp000066400000000000000000000641301517721000100242170ustar00rootroot00000000000000/** * @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 /** * @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) { qDebug() << "Graph::vertexFilterByCentrality()" << "index:" << static_cast(centralityIndex) << "threshold:" << threshold << "overThreshold:" << overThreshold; if (!isCentralityIndexComputed(centralityIndex)) { qDebug() << "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; } } 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. // --------------------------------------------------------------- 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; default: score = (*it)->DC(); break; } const bool shouldDisable = overThreshold ? (score >= thresh) : (score <= thresh); const bool currentlyEnabled = (*it)->isEnabled(); qDebug() << "PASS 1 - vertex" << (*it)->number() << "score=" << score << condition << threshold << "shouldDisable:" << shouldDisable << "currentlyEnabled:" << currentlyEnabled; if (currentlyEnabled == !shouldDisable) continue; // already in correct state (*it)->setEnabled(!shouldDisable); setModStatus(ModStatus::VertexCount); 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); const bool preserveReverse = (reverseEdgeWeight != 0); const bool sourceEnabled = (*it)->isEnabled(); const bool targetEnabled = vertexAtIndex(vertexIndexByNumber(target))->isEnabled(); const bool edgeVisible = sourceEnabled && targetEnabled; qDebug() << "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); } } } 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 qDebug() << "Graph::vertexFilterByEgoNetwork() - ego:" << v1; if (!vertexExists(v1)) { qDebug() << "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; } qDebug() << "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; } } 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. // 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); const bool preserveReverse = (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; } } 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) { qDebug() << "Graph::vertexFilterBySelection() - selection:" << selectedVertices; if (selectedVertices.isEmpty()) { qDebug() << "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.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. // ------------------------------------------------------------------ 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. // 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); const bool preserveReverse = (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; } } progressStatus(tr("Showing %1 selected node(s) and edges between them.") .arg(visibleSet.size())); } // --------------------------------------------------------------------------- // Internal helper // --------------------------------------------------------------------------- /** * @brief Returns true if @p attrValue satisfies the condition described by * @p cond. * * Numeric operators (>, <, β‰₯, ≀) attempt a double conversion of both sides * and fall back to lexicographic comparison when either side is not numeric. * Eq / Neq always use exact string comparison. * Contains uses case-insensitive substring search. */ static bool matchesCondition(const QString &attrValue, const FilterCondition &cond) { switch (cond.op) { case FilterCondition::Op::Eq: return attrValue == cond.value; case FilterCondition::Op::Neq: return attrValue != cond.value; case FilterCondition::Op::Contains: return attrValue.contains(cond.value, Qt::CaseInsensitive); default: break; } // Numeric branch bool okA = false, okB = false; const double a = attrValue.toDouble(&okA); const double b = cond.value.toDouble(&okB); if (okA && okB) { switch (cond.op) { case FilterCondition::Op::Gt: return a > b; case FilterCondition::Op::Lt: return a < b; case FilterCondition::Op::Gte: return a >= b; case FilterCondition::Op::Lte: return a <= b; default: break; } } // Lexicographic fallback switch (cond.op) { case FilterCondition::Op::Gt: return attrValue > cond.value; case FilterCondition::Op::Lt: return attrValue < cond.value; case FilterCondition::Op::Gte: return attrValue >= cond.value; case FilterCondition::Op::Lte: return attrValue <= cond.value; default: break; } return false; } // --------------------------------------------------------------------------- // 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) { qDebug() << "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) && matchesCondition(attrs.value(cond.key), cond)) 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; 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); const bool preserveReverse = (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; } } 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() { qDebug() << "Graph::vertexFilterRestoreAll()"; if (m_visibilityHistory.isEmpty()) { qDebug() << "Graph::vertexFilterRestoreAll() - history stack empty, nothing to restore."; progressStatus(tr("No active filter to restore.")); return; } const GraphVisibilitySnapshot snapshot = m_visibilityHistory.pop(); // ------------------------------------------------------------------ // PASS 1: Restore vertex visibility. // ------------------------------------------------------------------ VList::const_iterator vi; for (vi = m_graph.cbegin(); vi != m_graph.cend(); ++vi) { const int vnum = (*vi)->number(); const bool wasVisible = snapshot.nodeVisible.value(vnum, true); if ((*vi)->isEnabled() != wasVisible) { (*vi)->setEnabled(wasVisible); setModStatus(ModStatus::VertexCount); 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); const bool preserveReverse = (reverseWeight != 0); const bool wasVisible = snapshot.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; } } progressStatus(tr("Graph visibility restored.")); } /** * @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-app-39db829/src/graph/generators/000077500000000000000000000000001517721000100203525ustar00rootroot00000000000000socnetv-app-39db829/src/graph/generators/graph_random_networks.cpp000066400000000000000000000702631517721000100254630ustar00rootroot00000000000000/** * @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) { qDebug() << "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 progressCounter = 0; int edgeCount = 0; QString pMsg = tr("Creating Erdos-Renyi Random Network. \n" "Please wait..."); // Progress max: M edges for G(n,M), N nodes for G(n,p) progressCreate((m != 0 ? m : N), pMsg); // 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); } qDebug() << "Nodes created. Creating edges using model" << model; if (model == "G(n,p)") { // Bernoulli trials: each pair (i,j) gets an edge with probability p for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (!diag && i == j) continue; if ((rand() % 100 + 1) / 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); } } } progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); 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); } progressUpdate(++progressCounter); if (progressCanceled()) { cancelled = true; break; } } while (edgeCount != m); if (cancelled) { progressFinish(); return false; } } relationCurrentRename(tr("erdos-renyi"), true); progressUpdate((m != 0 ? m : N)); progressFinish(); setModStatus(ModStatus::VertexEdgeCount); 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) { qDebug() << "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; int progressCounter = 0; vpos.reserve(N); QString pMsg = tr("Creating Scale-Free Random Network. \n" "Please wait..."); progressStatus(pMsg); progressCreate(N, pMsg); // Phase 1: create the initial seed clique of m0 nodes qDebug() << "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); } } progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); return false; } } // Phase 2: grow the network to N nodes via preferential attachment qDebug() << "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); progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); 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; } qDebug() << "Graph::randomNetScaleFreeCreate() -" << m << "edges attached for node" << i + 1; } relationCurrentRename(tr("scale-free"), true); qDebug() << "Graph::randomNetScaleFreeCreate() - finished."; setModStatus(ModStatus::VertexEdgeCount); progressFinish(); 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) { qDebug() << "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); progressCreate(N, pMsg); qDebug() << "Rewiring starts..."; int candidate = 0; int progressCounter = 1; 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; } } } } progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); return false; } } relationCurrentRename(tr("small-world"), true); progressFinish(); setModStatus(ModStatus::VertexEdgeCount); 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) { qDebug() << "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)); } } } qDebug() << "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]); } // Now set progress max to actual edge count for accurate progress reporting progressCreate(m_edges.size(), pMsg); // 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) { progressUpdate((int)progressCounter); if (progressCanceled()) { progressFinish(); return false; } } } relationCurrentRename(tr("d-regular"), true); progressFinish(); setModStatus(ModStatus::VertexEdgeCount); 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) { qDebug() << "Creating ring lattice random network..."; int x = 0; int y = 0; int progressCounter = 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); if (updateProgress) progressCreate(N, 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 (updateProgress) { progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); return false; } } } // Always rename the relation, regardless of updateProgress (fix: was inside updateProgress guard) relationCurrentRename(tr("ring-lattice"), true); if (updateProgress) progressFinish(); setModStatus(ModStatus::VertexEdgeCount, updateProgress); 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) { qDebug() << "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); qDebug() << "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 qDebug() << "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 qDebug() << "Drawing" << latticeEdges.size() << "edges..."; progressFraction = (latticeEdges.size() > 0) ? 1.0 / (qreal)latticeEdges.size() : 1.0; progressCreate(latticeEdges.size(), pMsg); 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) { progressUpdate((int)progressCounter); if (progressCanceled()) { progressFinish(); return false; } } } relationCurrentRename(tr("lattice"), true); progressFinish(); setModStatus(ModStatus::VertexEdgeCount); 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-app-39db829/src/graph/io/000077500000000000000000000000001517721000100166105ustar00rootroot00000000000000socnetv-app-39db829/src/graph/io/graph_io.cpp000066400000000000000000001164131517721000100211120ustar00rootroot00000000000000/** * @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 "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) { qDebug() << "Loading the file:" << fileName; qDebug() << "First, clearing current relations..."; relationsClear(); qDebug() << "Next, creating new file_parser -- we are on thread:" << this->thread(); file_parser = new Parser(); qDebug() << " moving parser to her own new thread..."; file_parser->moveToThread(&file_parserThread); qDebug() << "file_parser thread now: " << file_parser->thread(); qDebug() << "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); qDebug() << "Starting parser thread..."; file_parserThread.start(); qDebug() << "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) { qDebug() << "Terminating parser threads - reason " << reason << " Checking if file_parserThread is running..."; if (file_parserThread.isRunning()) { qDebug() << "deleting file_parser pointer"; delete file_parser; file_parser = 0; // see why here: https://goo.gl/tQxpGA qDebug() << "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; } qDebug() << "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); qDebug() << "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) { qDebug() << "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) { qDebug() << "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; } 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) { qDebug() << "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)) { qDebug() << "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) { qDebug() << "Saving graph to adjacency-formatted file:" << fileName; QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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 TODO Saves the active graph to a UCINET-formatted file * * @param fileName * * @return bool */ bool Graph::saveToDotFormat(QString fileName) { Q_UNUSED(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) { qDebug() << "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()) { qDebug() << "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)) { qDebug() << "created icons subdir" << iconsDirPath; } else { qDebug() << "ERROR creating subdir!"; } } else { qDebug() << "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; qDebug() << "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)) { qDebug() << "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(); qDebug() << "Using default codec for saving stream:" << outTextEncoding; qDebug() << " writing xml version..."; outText << " \n"; outText << " \n"; outText << "" "\n"; qDebug() << "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)) { qDebug() << "default iconFile saved to:" << copyIconFileNamePath; } else { qDebug() << "ERROR saving default iconFile to:" << copyIconFileNamePath; } } else { qDebug() << "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()) { qDebug() << "saving defaults for vertexCustomAttributesList:" << vertexCustomAttributesList; QString customVertexAttrId; for (qsizetype i = 0; i < vertexCustomAttributesList.size(); ++i) { customVertexAttrId = 'd' + QString::number(1000 + i); qDebug() << "customVertexAttrId:" << customVertexAttrId << "customVertexAttr" << vertexCustomAttributesList.at(i); outText << " \n" " \n" " \n"; } } // Save custom edge attribute key definitions, if any (IDs: d2000+). if (!edgeCustomAttributesList.isEmpty()) { qDebug() << "saving defaults for edgeCustomAttributesList:" << edgeCustomAttributesList; QString customEdgeAttrId; for (qsizetype i = 0; i < edgeCustomAttributesList.size(); ++i) { customEdgeAttrId = 'd' + QString::number(2000 + i); qDebug() << "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); qDebug() << "writing graph tag. Relation:" << relationName; if (isUndirected()) outText << " \n"; else outText << " \n"; qDebug() << "writing nodes data..."; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { if (!(*it)->isEnabled()) continue; qDebug() << "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); // qDebug()<<"Rel coordinates: " // << rel_coord_x // << "," // << rel_coord_y; 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)) { qDebug() << "iconFile for node:" << (*it)->number() << "saved to:" << copyIconFileNamePath; } else { qDebug() << "ERROR saving iconFile for" << (*it)->number() << "saved to: " << copyIconFileNamePath; } } else { qDebug() << "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"; } qDebug() << "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"; } qDebug() << "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); // qDebug()<< "edge no:" // << edgeCount // << "from n1=" << source << "to n2=" << target // << "with weight" << weight // << "and color" << m_color.toUtf8() ; 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; } socnetv-app-39db829/src/graph/io/graph_parse_sink.h000066400000000000000000000077461517721000100223160ustar00rootroot00000000000000/** * @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-app-39db829/src/graph/io/graph_parse_sink_graph.cpp000066400000000000000000000115051517721000100240160ustar00rootroot00000000000000/** * @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-app-39db829/src/graph/io/graph_parse_sink_graph.h000066400000000000000000000062671517721000100234740ustar00rootroot00000000000000/** * @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-app-39db829/src/graph/io/table_export.cpp000066400000000000000000000060171517721000100220100ustar00rootroot00000000000000/** * @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 { // Returns a CSV-safe quoted field: wraps in "" if the value contains a // comma, double-quote, or newline; escapes embedded double-quotes as "". static 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-app-39db829/src/graph/io/table_export.h000066400000000000000000000032311517721000100214500ustar00rootroot00000000000000/** * @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 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-app-39db829/src/graph/io/table_import.cpp000066400000000000000000000075141517721000100220040ustar00rootroot00000000000000/** * @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-app-39db829/src/graph/io/table_import.h000066400000000000000000000057061517721000100214520ustar00rootroot00000000000000/** * @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-app-39db829/src/graph/layouts/000077500000000000000000000000001517721000100177015ustar00rootroot00000000000000socnetv-app-39db829/src/graph/layouts/graph_layouts_basic.cpp000066400000000000000000000513071517721000100244350ustar00rootroot00000000000000/** * @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() { qDebug() << "Graph::layoutRandom() "; double new_x = 0, new_y = 0; VList::const_iterator it; int N = vertices(); int progressCounter = 0; QString pMsg = tr("Embedding Random Layout. \n" "Please wait..."); progressStatus(pMsg); progressCreate(N, pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); new_x = canvasRandomX(); new_y = canvasRandomY(); (*it)->setX(new_x); (*it)->setY(new_y); qDebug() << "Graph::layoutRandom() - " << " vertex " << (*it)->number() << " emitting setNodePos to new pos " << new_x << " , " << new_y; emit setNodePos((*it)->number(), new_x, new_y); } progressFinish(); setModStatus(ModStatus::VertexPositions); } /** * @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) { qDebug() << "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; int progressCounter = 0; VList::const_iterator it; int N = vertices(); QString pMsg = tr("Embedding Random Radial layout. \n" "Please wait ...."); progressStatus(pMsg); progressCreate(N, pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); randomDecimal = (qreal)(rand() % 100) / 100.0; new_radius = (maxRadius - (randomDecimal - offset) * maxRadius); qDebug() << "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); qDebug("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); } } progressFinish(); setModStatus(ModStatus::VertexPositions); } /** * @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() { qDebug() << "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) { qDebug() << "Graph::layoutCircular - "; double rad = 0, new_x = 0, new_y = 0; double i = 0; VList::const_iterator it; int N = vertices(); int progressCounter = 0; QString pMsg = tr("Applying circular layout. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); if (!(*it)->isEnabled()) { qDebug() << " 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); qDebug("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); } } progressFinish(); 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) { qDebug() << "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 progressCounter = 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, 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 { 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); progressCreate(N, 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; } }; norm = stdC / maxC; progressUpdate(++progressCounter); switch (layoutType) { case 0: { // radial qDebug() << "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: { qDebug("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); qDebug() << "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 qDebug() << "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: { qDebug("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)); qDebug() << "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 qDebug() << "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: { qDebug() << "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 qDebug() << "Finished calculation. " << "new vertex size " << new_size << " call setSize()"; (*it)->setSize(new_size); emit setNodeSize((*it)->number(), new_size); break; } case 3: { // node color qDebug() << "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: { qDebug() << "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 qDebug() << "new vertex color " << new_color << " call setSize()"; (*it)->setColor(new_color.name()); emit setNodeColor((*it)->number(), new_color.name()); break; } }; } progressFinish(); setModStatus(ModStatus::VertexPositions); prominenceDistribution(prominenceIndex, m_reportsChartType); } /** * @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) { qDebug() << "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); qDebug() << "Graph::layoutEgoRadial() - neighbors:" << neighbors.size() << "other nodes:" << (N - 1 - neighbors.size()); QString pMsg = tr("Embedding Ego Radial layout. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, pMsg); int progressCounter = 0; // --- Place ego at center --- m_graph[vpos[egoVertex]]->setX(x0); m_graph[vpos[egoVertex]]->setY(y0); emit setNodePos(egoVertex, x0, y0); progressUpdate(++progressCounter); // --- 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); progressUpdate(++progressCounter); ++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); progressUpdate(++progressCounter); } emit addGuideCircle(x0, y0, ring2Radius); } progressFinish(); setModStatus(ModStatus::VertexPositions); } socnetv-app-39db829/src/graph/layouts/graph_layouts_force.cpp000066400000000000000000001333011517721000100244450ustar00rootroot00000000000000/** * @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; int progressCounter = 0; 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); qDebug() << "\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)); } } qDebug() << "layoutForceDirectedSpringEmbedder() - adjCache size" << adjCache.size(); /* Apply an initial random layout */ layoutRandomInMemory(); QString pMsg = tr("Embedding Eades Spring-Gravitational model. \n" "Please wait ...."); progressStatus(pMsg); progressCreate(maxIterations, 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(); // qDebug() << "********* Calculate forces for source s" << s // << " pos" << (*v1)->x() << "," << (*v1)->y(); 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; // qDebug() << " s=" << s << " pushed away from t=" << t // << " dist" << dist << " f_rep=" << 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; // qDebug() << " s=" << s << " attracted by t=" << t // << " dist" << dist << " f_att=" << f_att // << " disp_s=(" << (*v1)->disp().rx() << "," << (*v1)->disp().ry() << ")" // << " disp_t=(" << (*v2)->disp().rx() << "," << (*v2)->disp().ry() << ")"; } } // end for v2 // qDebug() << " >>> final s=" << s // << " disp=(" << (*v1)->disp().rx() << "," << (*v1)->disp().ry() << ")"; } // end for v1 // Apply displacements with current cooling factor and track max displacement // for convergence detection. const qreal maxDisp = layoutForceDirected_Eades_moveNodes(c4); progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); setModStatus(ModStatus::VertexPositions); return; } qDebug() << " iteration" << iteration << " c4=" << c4 << " maxDisp=" << maxDisp; // Early exit: layout has converged when no node moved more than epsilon pixels. if (maxDisp < epsilon) { qDebug() << "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()); } progressFinish(); setModStatus(ModStatus::VertexPositions); } /** * @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) { int progressCounter = 0; 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(); qDebug() << "Graph: layoutForceDirectedFruchtermanReingold() "; qDebug() << "Graph: Setting optimalDistance = " << optimalDistance << "...following Fruchterman-Reingold (1991) formula "; qDebug() << "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)); } } qDebug() << "layoutForceDirectedFruchtermanReingold() - adjCache size" << adjCache.size(); QString pMsg = tr("Embedding Fruchterman & Reingold forces model. \n" "Please wait ..."); progressStatus(pMsg); progressCreate(maxIterations, 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; // qDebug() << " 0000 s " << (*v1)->number() << " zeroing rx/ry"; } 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)); progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); setModStatus(ModStatus::VertexPositions); return; } qDebug() << " iteration" << iteration << " maxDisp=" << maxDisp; // Early exit: layout has converged when no node moved more than epsilon pixels. if (maxDisp < epsilon) { qDebug() << "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()); } progressFinish(); setModStatus(ModStatus::VertexPositions); // was missing on normal exit path } /** * @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) { qDebug() << "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 qDebug() << "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) { qDebug() << "KK layout: graph diameter is 0 (degenerate graph). Aborting."; progressFinish(); 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; qDebug() << "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); // qDebug()<< "Graph::layoutForceDirectedKamadaKawai() - 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 qDebug() << "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)); } } // qDebug()<< "Graph::layoutForceDirectedKamadaKawai() - k=" ; // 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 qDebug() << "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); progressCreate(maxIterations, pMsg); // while ( max_D_i > e ) while (Delta_max > epsilon) { couldNotSolveLinearSystem = false; progressCounter++; progressUpdate(progressCounter); if (progressCanceled()) { progressFinish(); setModStatus(ModStatus::VertexPositions); return; } if (progressCounter == maxIterations) { // qDebug()<< "Reached maxIterations. BREAK"; 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 qDebug() << "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(); // qDebug()<< "Compute partial derivatives E for particle" << pn // << " kkIdx m" << m // << " pos"<< xm << ", "<< ym; if (!(*v1)->isEnabled()) { // qDebug() << " particle " << pn // << " vpos m " << m << " disabled. Continue"; 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()) { qDebug() << " 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); // qDebug()<< "m" << m << " Delta_m" << Delta_m; if (Delta_m > Delta_max) { // qDebug()<< "m" << m << " Delta_m > Delta_max. Setting new Delta_max = "<< Delta_m; 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) { // qDebug () << "No particle left with Delta_m > epsilon -- BREAK"; break; } // let pm the particle satisfying D_m = max_D_i m = pm; mVpos = pmVpos; xm = xpm; ym = ypm; // qDebug () << "m"<< m<< "has max Delta_m"<< Delta_max // << " Starting minimizing Delta_m - " // << " initial m pos " << xm << ym; minimizationIterations = 0; // while ( D_m > e) do { if (minimizationIterations > 10) { qDebug() << "Reached minimizationIterations threshold. BREAK"; break; } minimizationIterations++; // qDebug () << "Started minimizing Delta_m for m"<< m // << "First compute dx and dy by solving equations 11 and 12 "; // 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()) { qDebug() << " 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); // qDebug () << "m"<< m << " new Delta_m" << Delta_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); // qDebug()<< "Jacobian Matrix of coefficients for linear system (eq. 11 & 12) is:"; // LIN_EQ_COEF.printMatrixConsole(); b[0] = -partDrvtEx_m; b[1] = -partDrvtEy_m; // qDebug()<< "right hand vector is: \n" << b[0] << " \n" << b[1]; // qDebug()<< "solving linear system..."; if (!LIN_EQ_COEF.solve(b)) { couldNotSolveLinearSystem = true; continue; } // qDebug()<< "solved linear system."; dx = b[0]; dy = b[1]; // qDebug()<< "Solution \n b[0] = dx =" << dx << "\n b[1] = dy =" << dy; // qDebug () << "m"<< m << " current m pos " << xm << ym << " new m pos " << xm +dx << ym+dy; // 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); qDebug() << "m" << m << " new m pos " << xm << ym; // TODO CHECK IF WE HAVE REACHED A FIXED POINT LOOP } while (Delta_m > epsilon); if (couldNotSolveLinearSystem) { qDebug() << "Could not solve linear system for particle " << pnm << "vpos" << m; } qDebug() << "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) { qDebug() << "Delta_max =< epsilon -- RETURN"; for (v1 = m_graph.cbegin(); v1 != m_graph.cend(); ++v1) { emit setNodePos((*v1)->number(), (*v1)->pos().x(), (*v1)->pos().y()); } progressFinish(); setModStatus(ModStatus::VertexPositions); } /** * @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; qDebug() << "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; } qDebug() << "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 qDebug() << "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) { qDebug() << "\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); qDebug() << " ##### 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); qDebug() << " 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()); qDebug() << " 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) { qDebug() << "\n\n ***** layoutForceDirected_FR_moveNodes() \n\n "; qDebug() << " 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); qDebug() << " 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-app-39db829/src/graph/matrices/000077500000000000000000000000001517721000100200105ustar00rootroot00000000000000socnetv-app-39db829/src/graph/matrices/graph_matrix_adjacency.cpp000066400000000000000000000131221517721000100252010ustar00rootroot00000000000000/** * @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) { qDebug() << "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), progressCounter = 0; VList::const_iterator it, jt; qDebug() << "Graph::createMatrixAdjacency() -resizing AM to" << N; AM.resize(N, N); QString pMsg = tr("Creating Adjacency Matrix. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { qDebug() << "Graph::createMatrixAdjacency() - i" << i << "name" << (*it)->number(); progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); return; } if (!(*it)->isEnabled() || ((*it)->isIsolated() && dropIsolates)) { qDebug() << "Graph::createMatrixAdjacency() - SKIP i" << i << "name" << (*it)->number(); continue; } j = i; for (jt = it; jt != m_graph.end(); jt++) { qDebug() << "Graph::createMatrixAdjacency() - j" << j << "name" << (*jt)->number(); if (!(*jt)->isEnabled() || ((*jt)->isIsolated() && dropIsolates)) { qDebug() << "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); } qDebug() << " 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)); } qDebug() << " AM(" << j << "," << i << ") = " << AM.item(j, i); } j++; } i++; } calculatedAdjacencyMatrix = true; progressFinish(); } /** * @brief Computes the inverse of the current adjacency matrix * @param method * @return */ bool Graph::createMatrixAdjacencyInverse(const QString &method) { qDebug() << "Graph::createMatrixAdjacencyInverse() "; bool considerWeights = false; int i = 0, j = 0; bool isSingular = true; 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") { invAM.inverseByGaussJordanElimination(AM); } else { invAM.inverse(AM); } 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; } socnetv-app-39db829/src/graph/prominence/000077500000000000000000000000001517721000100203405ustar00rootroot00000000000000socnetv-app-39db829/src/graph/prominence/graph_prominence_distribution.cpp000066400000000000000000000306711517721000100271720ustar00rootroot00000000000000/** * @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) { qDebug() << "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("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 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) { qDebug() << "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; qDebug() << "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; } } qDebug() << "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) { qDebug() << "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) { qDebug() << "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) { qDebug() << "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-app-39db829/src/graph/reachability/000077500000000000000000000000001517721000100206415ustar00rootroot00000000000000socnetv-app-39db829/src/graph/reachability/graph_reachability_walks.cpp000066400000000000000000000242171517721000100263750ustar00rootroot00000000000000/** * @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) { qDebug() << "Graph::reachable()"; graphDistancesGeodesic(false); return (m_graph[vpos[v1]]->distance(v2) != RAND_MAX) ? true : false; } /** * @brief Creates the reachability matrix XRM */ void Graph::createMatrixReachability() { qDebug() << "Creating the Reachability Matrix..."; graphDistancesGeodesic(false); if (progressCanceled()) { return; } VList::const_iterator it, jt; int N = vertices(false, false, true); int progressCounter = 0; 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); progressCreate(N, pMsg); qDebug() << "Writing Reachability matrix..."; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); return; } source = (*it)->number(); if (!(*it)->isEnabled()) { qDebug() << "source vertex" << source << "disabled. SKIP"; continue; } qDebug() << "source vertex" << source << "i" << i; for (jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { target = (*jt)->number(); if (!(*jt)->isEnabled()) { qDebug() << "target vertex" << target << "disabled. SKIP"; continue; } qDebug() << "target vertex" << target << "j" << j; reachVal = ((*it)->distance(target) != RAND_MAX) ? 1 : 0; qDebug() << "Setting XRM (" << i << "," << j << ") =" << reachVal; XRM.setItem(i, j, reachVal); j++; } j = 0; i++; } progressFinish(); } /** * @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 updateProgress = false; const bool considerWeights = false; // counting walks, not weight-products const bool inverseWeights = false; const bool dropIsolates = false; const bool symmetrize = false; graphWalksMatrixCreate(vertices(), length, updateProgress, considerWeights, inverseWeights, dropIsolates, symmetrize); return XM.item(v1 - 1, v2 - 1); } /** * @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. * @param updateProgress */ // src/graph/reachability/graph_reachability_walks.cpp void Graph::graphWalksMatrixCreate(const int &N, const int &length, const bool &updateProgress, 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()) { if (updateProgress) progressFinish(); return; } if (length > 0) { qDebug() << "Graph::graphWalksMatrixCreate() - " "Calculating sociomatrix power" << length; QString pMsg = tr("Computing walks of length %1. \nPlease wait...").arg(length); progressStatus(pMsg); if (updateProgress) progressCreate(length, pMsg); XM = AM.pow(length, false); if (updateProgress) progressUpdate(length); } else { qDebug() << "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); if (updateProgress) progressCreate(N - 1, 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 (updateProgress) { progressUpdate(i); if (progressCanceled()) { progressFinish(); return; } } } if (updateProgress) progressUpdate(N - 1); } if (updateProgress) progressFinish(); } /** * @brief Returns the influence range of vertex v1, namely the set of nodes who are * reachable from v1 (See Wasserman and Faust, pp.200-201, based on Lin, 1976). * The Influence Range of vertex v can also be defined as: * Ji = Sum [ D(v,j), iff D(v,j) != inf ] for every j in V, where j!=v and D the distance matrix * This function is for digraphs only * @param v1 * @return */ QList Graph::vertexinfluenceRange(int v1) { qDebug() << "Graph::vertexinfluenceRange() - vertex:" << v1; graphDistancesGeodesic(false); if (progressCanceled()) { return influenceRanges.values(v1); } VList::const_iterator jt; int N = vertices(false, false, true); int progressCounter = 0; int target = 0; influenceRanges.clear(); influenceRanges.reserve(N); QString pMsg = tr("Creating Influence Range List. \nPlease wait "); progressStatus(pMsg); progressCreate(N, pMsg); for (jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); return influenceRanges.values(v1); } target = (*jt)->number(); if (!(*jt)->isEnabled()) { qDebug() << "Graph::vertexinfluenceRange() - target:" << target << "disabled. SKIP"; continue; } if (graphDistanceGeodesic(v1, target) != RAND_MAX) { qDebug() << "Graph::vertexinfluenceRange() - v1 can reach:" << target; influenceRanges.insert(v1, target); } } progressFinish(); return influenceRanges.values(v1); } /** * @brief Returns the influence domain of vertex v1, namely the set of nodes who can * reach v1 * The Influence Domain Ii of vertex v can also be defined as: * Ii = Sum [ D(i,v), iff D(i,v) != inf ] for every in V, where i!=v and D the distance matrix * This function applies to digraphs only * @param v1 * @return */ QList Graph::vertexinfluenceDomain(int v1) { qDebug() << "Graph::vertexinfluenceDomain() - vertex:" << v1; graphDistancesGeodesic(false); if (progressCanceled()) { return influenceDomains.values(v1); } VList::const_iterator it; int N = vertices(false, false, true); int progressCounter = 0; int source = 0; influenceDomains.clear(); influenceDomains.reserve(N); QString pMsg = tr("Creating Influence Domain List. \nPlease wait "); progressStatus(pMsg); progressCreate(N, pMsg); for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); return influenceDomains.values(v1); } source = (*it)->number(); if (!(*it)->isEnabled()) { qDebug() << "Graph::vertexinfluenceDomain() - " << source << "disabled. SKIP"; continue; } if ((*it)->distance(v1) != RAND_MAX) { qDebug() << "Graph::vertexinfluenceDomain() - v1 reachable from:" << source; influenceDomains.insert(v1, source); } } progressFinish(); return influenceDomains.values(v1); } /** 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-app-39db829/src/graph/relations/000077500000000000000000000000001517721000100202015ustar00rootroot00000000000000socnetv-app-39db829/src/graph/relations/graph_relations.cpp000066400000000000000000000304211517721000100240660ustar00rootroot00000000000000/** * @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) { qDebug() << "++ 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 qDebug() << "++ Requested relation is the current one - END"; return; } if (relNum < 0) { // negative, don't do nothing qDebug() << "++ 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 qDebug() << "++ Invalid relation - END "; return; } // // Force enabled vertices to disable all edges // in the old relation and enable edges in the new relation // VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { qDebug() << "++ changing relation of vertex" << (*it)->number() << "to" << relNum; if (!(*it)->isEnabled()) continue; (*it)->setRelation(relNum); } // // now store the selected relation // m_curRelation = relNum; // // 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; // // Check if isWeighted so that multiple-relation networks are properly loaded. // isWeighted(); // // Check if we need to update the UI // if (updateUI) { qDebug() << "++ 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 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() { qDebug() << "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() { qDebug() << "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) { qDebug() << "Adding new relation named:" << relName; // Add the new relation to our relations list m_relationsList << relName; // 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 { // qDebug() << "Returning the current relation name..."; 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]) { qDebug() << "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()) { qDebug() << "The new name of the relation is empty. Nothing to do. Returning."; return; } // // Rename current relation to newName // qDebug() << "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() { // qDebug () << " relations count " << m_relationsList.size(); return m_relationsList.size(); } /** * @brief Clears relationships in this Graph */ void Graph::relationsClear() { int oldRelationsCounter = m_relationsList.size(); m_relationsList.clear(); m_curRelation = 0; qDebug() << "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) { qDebug() << "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(); qDebug() << "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]; qDebug() << "" << v1 << "->" << v2 << "=" << weight << "Checking opposite."; invertWeight = m_graph[y]->hasEdgeTo(v1, allRelations); if (invertWeight == 0) { qDebug() << 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))) { qDebug() << v1 << "--" << v2 << " exists. Strong Tie. Adding"; strongTies->insert(QString::number(v1) + "--" + QString::number(v2), 1); } else { qDebug() << v1 << "--" << v2 << " exists. Strong Tie already found. Continue"; } } ++it1; } } relationAdd("Strong Ties", true); QHash::const_iterator it2; it2 = strongTies->constBegin(); QStringList vertices; qDebug() << "creating strong tie edges..."; while (it2 != strongTies->constEnd()) { vertices = it2.key().split("--"); // qDebug() << "tie " < 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() { qDebug() << "Graph::relationAddCocitation()" << "initial relations" << relations(); int v1 = 0, v2 = 0, i = 0, j = 0, weight; bool dropIsolates = false; createMatrixAdjacency(); if (progressCanceled()) { progressFinish(); 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++) { qDebug() << "Graph::relationAddCocitation() - (i,j)" << i + 1 << j + 1; if (!(*it1)->isEnabled() || ((*it1)->isIsolated() && dropIsolates)) { continue; } v2 = (*it1)->number(); if (v1 == v2) { j++; qDebug() << "Graph::relationAddCocitation() - skipping self loop" << v1 << v2; continue; } if ((weight = CT->item(i, j)) != 0) { qDebug() << "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); qDebug() << "Graph::relationAddCocitation()" << "final relations" << relations(); } socnetv-app-39db829/src/graph/reporting/000077500000000000000000000000001517721000100202125ustar00rootroot00000000000000socnetv-app-39db829/src/graph/reporting/graph_reports.cpp000066400000000000000000006743621517721000100236170ustar00rootroot00000000000000/** * @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 #include #include #include #include #include /** * @brief Writes reciprocity report to filename * @param fileName * @param considerWeights */ void Graph::writeReciprocity(const QString fileName, const bool considerWeights) { qDebug() << "Writing reciprocity report to file:" << fileName; Q_UNUSED(considerWeights); QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return; } QTextStream outText(&file); m_graphReciprocityArc = graphReciprocity(); int rowCount = 0; int progressCounter = 0; int N = vertices(); qreal tiesSym = 0; qreal tiesNonSym = 0; qreal tiesOutNonSym = 0; qreal tiesInNonSym = 0; qreal tiesOutNonSymTotalOut = 0; qreal tiesInNonSymTotalIn = 0; QString pMsg = tr("Writing Reciprocity to file. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, 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: ") << "" << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << "" << "" << "" << "" << ""; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; qDebug() << "Graph::writeReciprocity outnon - innon - rec" << (*it)->outEdgesNonSym() << (*it)->inEdgesNonSym() << (*it)->outEdgesReciprocated(); // Symmetric: Total number of reciprocated ties involving this actor divided by the number of ties to and from her. tiesSym = (qreal)(*it)->outEdgesReciprocated() / (qreal)((*it)->outEdgesCount() + (*it)->inEdgesCount()); // non Symmetric: One minus symmetric tiesNonSym = 1 - tiesSym; // nonSym Out/NonSym. Proportion of non-symmetric outgoing ties to the total non-symmetric ties. tiesOutNonSym = ((*it)->outEdgesNonSym() || (*it)->inEdgesNonSym()) ? (qreal)(*it)->outEdgesNonSym() / (qreal)((*it)->outEdgesNonSym() + (*it)->inEdgesNonSym()) : 0; // nonSym In/NonSym. Proportion of non-symmetric incoming ties to the total non-symmetric ties. tiesInNonSym = ((*it)->outEdgesNonSym() || (*it)->inEdgesNonSym()) ? (qreal)(*it)->inEdgesNonSym() / (qreal)((*it)->outEdgesNonSym() + (*it)->inEdgesNonSym()) : 0; // nonSym Out/Out. Proportion of non-symmetric outgoing ties to the total outgoing ties. tiesOutNonSymTotalOut = ((*it)->outEdgesCount() != 0) ? (qreal)(*it)->outEdgesNonSym() / (qreal)(*it)->outEdgesCount() : 0; // nonSym In/In. Proportion of non-symmetric incoming ties to the total incoming ties. tiesInNonSymTotalIn = ((*it)->inEdgesCount() != 0) ? (qreal)(*it)->inEdgesNonSym() / (qreal)(*it)->inEdgesCount() : 0; outText << "" << "" << ""; } outText << "
" << tr("Actor") << "" << tr("Label") << "" << tr("Symmetric") << "" << tr("nonSymmetric") << "" << tr("nsym out/nsym") << "" << tr("nsym in/nsym") << "" << tr("nsym out/out") << "" << tr("nsym in/in") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << tiesSym //<< ((eccentr == 0) ? "\xE2\x88\x9E" : QString::number(eccentr) ) << "" << tiesNonSym //<< ((eccentr == 0) ? "\xE2\x88\x9E" : QString::number(eccentr) ) << "" << tiesOutNonSym << "" << tiesInNonSym << "" << tiesOutNonSymTotalOut << "" << tiesInNonSymTotalIn << "
"; 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(); progressFinish(); } /** * @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) { qDebug() << "Writing Eccentricity report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } int progressCounter = 0; int rowCount = 0; int N = vertices(); qreal eccentr = 0; QString pMsg = tr("Writing Eccentricity scores to file. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, 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") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << ""; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; eccentr = (*it)->eccentricity(); qDebug() << "Graph::writeEccentricity() - actor " << (*it)->number() << "eccentricity" << eccentr; if (!(*it)->isEnabled()) { qDebug() << "Graph::writeEccentricity() - actor disabled. SKIP."; continue; // do not print disabled nodes } outText << "" << "" << ""; } outText << "
" << tr("Actor") << "" << tr("Label") << "" << tr("e") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << ((eccentr == 0 || eccentr == RAND_MAX) ? "\xE2\x88\x9E" : QString::number(eccentr)) << "
"; 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(); progressFinish(); return true; } /** * @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) { qDebug() << "Writing Information Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::IC, m_reportsChartType, distImageFileName); VList::const_iterator it; bool dropIsolates = true; // by default IC needs to exclude isolates int rowCount = 0; int N = vertices(dropIsolates, false, true); int progressCounter = 0; QString pMsg = tr("Writing Information Centralities to file. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, 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") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; outText << Qt::fixed; if ((*it)->isIsolated()) { outText << "" << "" << ""; } else { outText << "" << "" << ""; } } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("IC") << "" << tr("IC'") << "" << tr("%IC") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << "--" << "" << "--" << "" << "--" << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->IC() << "" << (*it)->SIC() << "" << (100 * ((*it)->SIC())) << "
"; 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(); progressFinish(); 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) { qDebug() << "Writing Eigenvector Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::EVC, m_reportsChartType, distImageFileName); VList::const_iterator it; int rowCount = 0; int N = vertices(); int progressCounter = 0; QString pMsg = tr("Writing Eigenvector Centrality scores to file. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, 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") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; outText << Qt::fixed; outText << "" << "" << ""; } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("EVC") << "" << tr("EVC'") << "" << tr("EVC''") << "" << tr("%EVC'") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->EVC() << "" << (*it)->SEVC() << "" << (*it)->EVC() / (sumEVC ? sumEVC : 1) << "" << (100 * ((*it)->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(); progressFinish(); 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) { qDebug() << "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)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } 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 rowCount = 0; int N = vertices(); int progressCounter = 0; VList::const_iterator it; outText << htmlHead; outText.setRealNumberPrecision(m_reportsRealPrecision); QString pMsg = tr("Writing out-Degree Centralities. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, 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") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { rowCount++; progressUpdate(++progressCounter); outText << Qt::fixed; if (dropIsolates && (*it)->isIsolated()) { outText << "" << "" << ""; } else { outText << "" << "" << ""; } } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("DC") << "" << tr("DC'") << "" << tr("%DC'") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << "--" << "" << "--" << "" << "--" << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->DC() << "" << (*it)->SDC() << "" << (100 * ((*it)->SDC())) << "
"; 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(); progressFinish(); 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) { QElapsedTimer computationTimer; computationTimer.start(); qDebug() << "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)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::CC, m_reportsChartType, distImageFileName); int rowCount = 0; int N = vertices(); int progressCounter = 0; QString pMsg = tr("Writing Closeness Centrality scores to file. \nPlease wait ..."); progressStatus(pMsg); progressCreate(N, 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)") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << "" << ""; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; outText << Qt::fixed; if (dropIsolates && (*it)->isIsolated()) { outText << "" << "" << ""; } else { outText << "" << "" << ""; } } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("CC") << "" << tr("CC'") << "" << tr("%CC'") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << "--" << "" << "--" << "" << "--" << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->CC() << "" << (*it)->SCC() << "" << (100 * ((*it)->SCC())) << "
"; 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(); progressFinish(); 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) { qDebug() << "Writing IR Closeness Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::IRCC, m_reportsChartType, distImageFileName); int rowCount = 0; int N = vertices(); int progressCounter = 0; QString pMsg = tr("Writing Influence Range Centrality scores. \n" "Please wait"); progressStatus(pMsg); progressCreate(N, 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)") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << ""; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; outText << Qt::fixed; if (dropIsolates && (*it)->isIsolated()) { outText << "" << "" << ""; } else { outText << "" << "" << ""; } } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("IRCC") << "" << tr("%IRCC'") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << "--" << "" << "--" << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->IRCC() << "" << (100 * ((*it)->SIRCC())) << "
"; 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(); progressFinish(); 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) { qDebug() << "Writing Betweenness Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::BC, m_reportsChartType, distImageFileName); int rowCount = 0, progressCounter = 0; int N = vertices(); QString pMsg = tr("Writing Betweenness Centrality scores to file. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, 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)") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << "" << ""; VList::const_iterator it; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; outText << Qt::fixed; if (dropIsolates && (*it)->isIsolated()) { outText << "" << "" << ""; } else { outText << "" << "" << ""; } } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("BC") << "" << tr("BC'") << "" << tr("%BC'") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << "--" << "" << "--" << "" << "--" << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->BC() << "" << (*it)->SBC() << "" << (100 * ((*it)->SBC())) << "
"; 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(); progressFinish(); 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) { qDebug() << "Writing Stress Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::SC, m_reportsChartType, distImageFileName); VList::const_iterator it; int rowCount = 0; int N = vertices(); int progressCounter = 0; QString pMsg = tr("Writing Stress Centralities. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, 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)") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; outText << Qt::fixed; if (dropIsolates && (*it)->isIsolated()) { outText << "" << "" << ""; } else { outText << "" << "" << ""; } } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("SC") << "" << tr("SC'") << "" << tr("%SC'") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << "--" << "" << "--" << "" << "--" << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->SC() << "" << (*it)->SSC() << "" << (100 * ((*it)->SSC())) << "
"; 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(); progressFinish(); 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) { qDebug() << "Writing Eccentricity Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::EC, m_reportsChartType, distImageFileName); VList::const_iterator it; int rowCount = 0; int N = vertices(); int progressCounter = 0; QString pMsg = tr("Writing Eccentricity Centralities to file. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, 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)") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; outText << Qt::fixed; if (dropIsolates && (*it)->isIsolated()) { outText << "" << "" << ""; } else { outText << "" << "" << ""; } } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("EC=EC'") << "" << tr("%EC'") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << "--" << "" << "--" << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->EC() << "" << (100 * ((*it)->SEC())) << "
"; 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(); progressFinish(); 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) { qDebug() << "Writing Power Centrality report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::PC, m_reportsChartType, distImageFileName); VList::const_iterator it; int rowCount = 0; int N = vertices(); int progressCounter = 0; QString pMsg = tr("Writing Gil-Schmidt Power Centralities to file. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, 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).)") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; outText << Qt::fixed; if (dropIsolates && (*it)->isIsolated()) { outText << "" << "" << ""; } else { outText << "" << "" << ""; } } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("PC") << "" << tr("PC'") << "" << tr("%PC'") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << "--" << "" << "--" << "" << "--" << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->PC() << "" << (*it)->SPC() << "" << (100 * ((*it)->SPC())) << "
"; 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(); progressFinish(); 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) { qDebug() << "Writing Degree Prestige report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::DP, m_reportsChartType, distImageFileName); VList::const_iterator it; int N = vertices(); qreal maxIndexDP = N - 1.0; int rowCount = 0; int progressCounter = 0; QString pMsg = tr("Writing Degree Prestige (in-Degree) scores to file. \nPlease wait ..."); progressStatus(pMsg); progressCreate(N, 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") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; outText << Qt::fixed; if (dropIsolates && (*it)->isIsolated()) { outText << "" << "" << ""; } else { outText << "" << "" << ""; } } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("DP") << "" << tr("DP'") << "" << tr("%DP'") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << "--" << "" << "--" << "" << "--" << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->DP() << "" << (*it)->SDP() << "" << (100 * ((*it)->SDP())) << "
"; 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(); progressFinish(); 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) { qDebug() << "Writing Proximity Prestige report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::PP, m_reportsChartType, distImageFileName); VList::const_iterator it; int rowCount = 0; int N = vertices(); int progressCounter = 0; QString pMsg = tr("Writing Proximity Prestige scores to file. \nPlease wait ..."); progressStatus(pMsg); progressCreate(N, 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)") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; outText << Qt::fixed; if (dropIsolates && (*it)->isIsolated()) { outText << "" << "" << ""; } else { outText << "" << "" << ""; } } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("PP=PP'") << "" << tr("%PP") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << "--" << "" << "--" << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->PP() << "" << (100 * ((*it)->SPP())) << "
"; 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(); progressFinish(); 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) { qDebug() << "Writing PageRank Prestige report to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } QString distImageFileName; if (m_reportsChartType != ChartType::None) { distImageFileName = QFileInfo(fileName).canonicalPath() + QDir::separator() + QFileInfo(fileName).completeBaseName() + ".png"; } prominenceDistribution(IndexType::PRP, m_reportsChartType, distImageFileName); VList::const_iterator it; int rowCount = 0; int N = vertices(); int progressCounter = 0; QString pMsg = tr("Writing PageRank scores to file. \nPlease wait ..."); progressStatus(pMsg); progressCreate(N, 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") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); rowCount++; outText << Qt::fixed; if (dropIsolates && (*it)->isIsolated()) { outText << "" << "" << ""; } else { outText << "" << "" << ""; } } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("PRP") << "" << tr("PRP'") << "" << tr("%PRP'") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << "--" << "" << "--" << "" << "--" << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->PRP() << "" << (*it)->SPRP() << "" << (100 * ((*it)->SPRP())) << "
"; 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(); progressFinish(); 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 */ void Graph::writeMatrixWalks(const QString &fn, const int &length, const bool &simpler) { qDebug() << "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)) { qDebug() << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fn); return; } int N = vertices(); progressStatus(tr("Computing Walks...")); graphWalksMatrixCreate(N, length, true); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return; } QTextStream outText(&file); 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); qDebug() << "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(); } /** Writes the reachability matrix X^R of the graph to a file */ void Graph::writeReachabilityMatrixPlainText(const QString &fn, const bool &dropIsolates) { qDebug() << "Writing Reachability Matrix plain text to file:" << fn; QFile file(fn); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fn); return; } QTextStream outText(&file); outText << "-Social Network Visualizer " << VERSION << "\n"; outText << tr("Network name: ") << getName() << "\n\n"; outText << tr("Reachability Matrix (XR)") << "\n"; outText << tr("Two nodes are reachable if there is a walk between them (their geodesic distance is non-zero).") << "\n"; outText << tr("If nodes i and j are reachable then XR(i,j)=1 otherwise XR(i,j)=0.") << "\n\n"; graphDistancesGeodesic(false, false, false, dropIsolates); outText << XRM; file.close(); } /** * @brief Writes the clustering coefficients to a file * @param fileName * @param considerWeights */ bool Graph::writeClusteringCoefficient(const QString fileName, const bool considerWeights) { QElapsedTimer computationTimer; computationTimer.start(); Q_UNUSED(considerWeights); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "Could not open file for writing. Abort."; progressStatus(tr("Error. Could not write to ") + fileName); return false; } QTextStream outText(&file); int rowCount = 0; int N = vertices(); int progressCounter = 0; VList::const_iterator it; averageCLC = clusteringCoefficient(true); if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } QString pMsg = tr("Writing Clustering Coefficients to file. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, 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 ") << "

"; outText << ""; outText << "" << "" << "" << "" << "" << "" << "" << "" << "" << ""; for (it = m_graph.cbegin(); it != m_graph.cend(); ++it) { progressUpdate(++progressCounter); if (progressCanceled()) { file.close(); progressFinish(); progressStatus(tr("Computation canceled.")); return false; } rowCount++; outText << Qt::fixed; outText << "" << "" << ""; } outText << "
" << tr("Node") << "" << tr("Label") << "" << tr("CLC") << "" << tr("CLC'") << "" << tr("%CLC'") << "
" << (*it)->number() << "" << ((!((*it)->label().simplified()).isEmpty()) ? (*it)->label().simplified().left(m_reportsLabelLength) : "-") << "" << (*it)->CLC() << "" << (*it)->CLC() / maxCLC << "" << 100 * (*it)->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(); progressFinish(); return true; } // Writes the triad census to a file bool Graph::writeTriadCensus(const QString fileName, const bool considerWeights) { qDebug() << "Graph::writeTriadCensus()"; QElapsedTimer computationTimer; computationTimer.start(); Q_UNUSED(considerWeights); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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()) { qDebug() << "Error in graphTriadCensus(). Exiting..."; file.close(); return false; } if (progressCanceled()) { file.close(); progressStatus(tr("Computation canceled.")); return false; } } int rowCount = 0; int N = vertices(); int progressCounter = 0; 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"; QString pMsg = tr("Writing Triad Census to file. \nPlease wait..."); progressStatus(pMsg); progressCreate(16, 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++) { progressUpdate(++progressCounter); 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(); progressFinish(); 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(); qDebug() << "Graph::writeCliqueCensus() "; Q_UNUSED(considerWeights); QString varLocation = "Both"; bool dendrogram = true; QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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); progressCreate(2 * N, pMsg); // compute clique census pMsg = tr("Computing Clique Census. Please wait.."); progressStatus(pMsg); qDebug() << "Graph::writeCliqueCensus() - calling graphCliques"; csRecDepth = 0; // Call graphCliques() to compute all cliques (maximal connected subgraphs) of the network. graphCliques(); if (progressCanceled()) { file.close(); progressFinish(); 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"); progressFinish(); 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") << "

"; progressUpdate(2 * N); 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(); progressFinish(); 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(); qDebug() << "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)) { qDebug() << "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)) { qDebug() << "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); progressCreate(N, 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: ") << "" << "

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

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

"; progressUpdate(2 * N / 3); if (progressCanceled()) { file.close(); progressFinish(); 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(); qDebug() << "Graph::writeClusteringHierarchical() - finished"; progressUpdate(N); progressFinish(); 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) { qDebug() << "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) { qDebug() << "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(); qDebug() << "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); qDebug() << "seq" << pit.key() << "level" << level << "cluster pair" << pit.value(); for (int i = 0; i < pit.value().size(); ++i) { clusterName = pit.value().at(i); qDebug() << "clusterName" << clusterName; if (i == 0) { endPoint1 = clusterEndPoint.value(clusterName, QPoint()); qDebug() << "endPoint1" << endPoint1; } else { endPoint2 = clusterEndPoint.value(clusterName, QPoint()); qDebug() << "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); qDebug() << "(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) { qDebug() << "Graph::writeMatrixDissimilarities()" << "metric" << metricStr << "varLocation" << varLocation << "diagonal" << diagonal; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); return false; } progressStatus((tr("Examining pair-wise tie profile dissimilarities of actors..."))); int metric = graphMetricStrToType(metricStr); createMatrixDissimilarities(AM, DSM, metric, varLocation, diagonal, considerWeights); progressStatus(tr("Writing tie profile dissimilarities to file: ") + fileName); 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) { qDebug() << "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)) { qDebug() << "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(); progressFinish(); 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; } QString pMsg = tr("Writing Similarity coefficients to file. \nPlease wait..."); progressStatus(pMsg); progressCreate(1, pMsg); 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 ") << "" << "

"; progressUpdate(0); 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(); progressUpdate(1); progressFinish(); 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) { qDebug() << "Writing Pearson Correlation coefficients to file:" << fileName; QElapsedTimer computationTimer; computationTimer.start(); Q_UNUSED(considerWeights); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); progressFinish(); return false; } createMatrixSimilarityPearson(AM, PCC, varLocation, diagonal); } else if (matrix == "Distances") { graphDistancesGeodesic(); if (progressCanceled()) { file.close(); progressFinish(); return false; } createMatrixSimilarityPearson(DM, PCC, varLocation, diagonal); } else { file.close(); return false; } progressStatus(tr("Writing Pearson coefficients to file: ") + fileName); 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) { qDebug() << "Writing famous dataset to file:" << dir + fileName; QFile file(dir + fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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(); qDebug() << " ... writing dataset "; if (fileName == "Campnet.paj") { qDebug() << " ... 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") { qDebug() << " ... 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") { qDebug() << " ... 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. "); outText << "0 0 1 1 0 0 0 0 1 0 0 0 0 0 0" << "\n" << "0 0 1 0 1 0 1 0 0 0 0 0 0 0 0" << "\n" << "0 0 1 0 0 0 0 0 0 0 0 1 0 0 0" << "\n" << "0 1 1 0 0 0 0 0 0 0 0 0 0 0 1" << "\n" << "0 0 1 0 0 0 0 0 0 0 0 0 1 1 0" << "\n" << "0 1 1 0 0 0 0 0 0 0 0 0 0 1 0" << "\n" << "0 0 1 1 0 0 0 0 0 1 1 0 0 0 0" << "\n" << "0 0 0 1 0 0 1 0 0 1 0 0 0 0 0" << "\n" << "1 0 0 1 0 0 0 1 0 1 0 0 0 0 0" << "\n" << "0 0 1 0 0 0 0 0 1 0 0 0 0 0 0" << "\n" << "0 1 1 0 0 0 0 0 1 0 0 0 0 0 0" << "\n" << "0 0 0 1 0 0 1 0 0 0 0 0 0 0 0" << "\n" << "0 0 1 1 1 0 0 0 1 0 0 0 0 0 0" << "\n" << "0 1 1 1 0 0 0 0 0 0 1 1 1 0 1" << "\n" << "0 1 1 0 0 1 0 0 0 0 0 0 1 0 1" << "\n" << "0 1 1 0 0 1 0 1 0 0 0 0 0 1 0" << "\n" << "0 1 1 0 1 0 0 0 0 0 1 1 0 0 1" << "\n" << "0 0 0 1 0 0 0 0 1 0 0 1 1 0 1" << "\n" << "1 0 1 1 0 0 1 0 1 0 0 0 0 0 0" << "\n" << "0 1 1 1 0 0 0 0 0 0 1 0 0 0 1" << "\n" << "0 0 1 1 0 0 0 1 0 0 0 0 0 0 0" << "\n" << "0 0 1 0 0 0 0 1 0 0 0 0 0 0 1" << "\n" << "0 1 1 0 0 1 0 0 0 0 0 0 0 0 1" << "\n" << "1 0 1 1 0 1 0 0 0 0 0 0 0 0 1" << "\n" << "0 1 1 0 0 0 0 0 0 0 0 0 1 0 0" << "\n" << "0 1 1 0 0 0 0 0 0 0 0 1 0 0 0"; } 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") { qDebug() << " ... 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") { qDebug() << " ... to " << fileName; datasetDescription = tr("Freeman's EIES network (Acquaintanceship) at time 1"); } else if (fileName == "Freeman_EIES_network_48actors_Acquaintanceship_at_time_2.dl") { qDebug() << " ... 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)"); qDebug() << " ... 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") { qDebug() << "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") { qDebug() << "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") { qDebug() << "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"); qDebug() << " Wasserman_Faust_Countries_Trade_Data_Basic_Manufactured_Goods.pajek written... "; } else if (fileName == "Petersen_Graph.paj") { qDebug() << " 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) { qDebug() << "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)) { qDebug() << "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); 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(); progressFinish(); 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); qDebug() << "Graph::writeMatrixHTMLTable() -" << "markDiag" << markDiag << "plain" << plain << " dropIsolates " << dropIsolates; int rowCount = 0, i = 0, j = 0; int N = vertices(); qreal maxVal, minVal, element; bool hasRealNumbers = false; VList::const_iterator it, jt; QString pMsg = tr("Writing matrix to file. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, pMsg); M.findMinMaxValues(minVal, maxVal, hasRealNumbers); outText << ((hasRealNumbers) ? qSetRealNumberPrecision(3) : qSetRealNumberPrecision(0)); qDebug() << "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++; progressUpdate(rowCount); outText << ""; outText << ""; for (jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt) { if (!(*jt)->isEnabled() || (dropIsolates && (*jt)->isIsolated())) { continue; } outText << Qt::fixed << Qt::right; outText << "number() == (*jt)->number()) ? " class=\"diag\">" : ">"); element = M.item(i, j); qDebug() << "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 { outText << 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)) << "

"; progressFinish(); } /** Exports the adjacency matrix to a given textstream */ void Graph::writeMatrixAdjacencyTo(QTextStream &os, const bool &saveEdgeWeights) { qDebug("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 */ void Graph::writeMatrixAdjacency(const QString fn, const bool &markDiag) { qDebug() << "Writing adjacency matrix to file:" << fn; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fn); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "Could not open (for writing) file:" << fn; progressStatus(tr("Error. Could not write to ") + fn); return; } 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); progressCreate(N, pMsg); 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++; progressUpdate(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() << "
"; qDebug("Graph: Found a total of %i edge", sum); if (sum != edgesEnabled()) qDebug("Error in edge count found!!!"); else qDebug("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(); progressFinish(); } /** * @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) { qDebug() << "Writing adjacency matrix plot to file:" << fn; QElapsedTimer computationTimer; computationTimer.start(); QFile file(fn); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug() << "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; int progressCounter = 0; QString pMsg = tr("Plotting Adjacency Matrix. \nPlease wait..."); progressStatus(pMsg); progressCreate(N, 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) { progressUpdate(++progressCounter); 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) { progressUpdate(++progressCounter); 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 << "

"; } qDebug("Graph: Found a total of %i edge", sum); if (sum != edgesEnabled()) qDebug("Error in edge count found!!!"); else qDebug("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(); progressFinish(); } socnetv-app-39db829/src/graph/reporting/graph_reports_settings.cpp000066400000000000000000000035101517721000100255140ustar00rootroot00000000000000 /** * @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) { qDebug() << "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; } } socnetv-app-39db829/src/graph/similarity/000077500000000000000000000000001517721000100203675ustar00rootroot00000000000000socnetv-app-39db829/src/graph/similarity/graph_similarity_matrices.cpp000066400000000000000000000107441517721000100263370ustar00rootroot00000000000000/** * @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) { qDebug() << "Graph::createMatrixDissimilarities() -metric" << metric; DSM = INPUT_MATRIX.distancesMatrix(metric, varLocation, diagonal, considerWeights); // qDebug()<<"Graph::createMatrixDissimilarities() - matrix DSM:"; // 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) { qDebug() << "Graph::createMatrixSimilarityMatching()"; QString pMsg = tr("Computing Similarity coefficients matrix. \nPlease wait..."); progressCreate(1, pMsg); SCM.similarityMatrix(AM, measure, varLocation, diagonal, considerWeights); progressUpdate(1); progressFinish(); } /** * @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) { qDebug() << "Graph::createMatrixSimilarityPearson()"; PCC.pearsonCorrelationCoefficients(AM, varLocation, diagonal); qDebug() << "Graph::createMatrixSimilarityPearson() - matrix PCC"; // PCC.printMatrixConsole(true); } socnetv-app-39db829/src/graph/storage/000077500000000000000000000000001517721000100176455ustar00rootroot00000000000000socnetv-app-39db829/src/graph/storage/graph_edges.cpp000066400000000000000000000610231517721000100226230ustar00rootroot00000000000000/** * @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 * createEdgeName(targetNum, sourceNum). Since createEdgeName() is order-sensitive * ("rel:v1>v2"), passing (v2,v1) makes targetNum=v1, sourceNum=v2, so the lookup * resolves to createEdgeName(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) { // // 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 createEdgeName(targetNum,sourceNum) // resolves to createEdgeName(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. // // qDebug() << "edgeCreate(): v1->v2 exists as directed arc but request" // " is Undirected and reverse v2->v1 is missing." // " Storing reverse arc and upgrading visual to Reciprocated:" // << v1 << "<->" << v2; // Store the reverse arc in the data model. edgeAdd(v2, v1, weight, EdgeType::Directed, label, ((weight == 0) ? "blue" : 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) ? "blue" : 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. // qDebug() << "edgeCreate(): Creating new UNDIRECTED edge:" // << v1 << "-" << v2 // << "weight" << weight // << "label" << label; edgeAdd(v1, v2, weight, type, label, ((weight == 0) ? "blue" : color)); emit signalDrawEdge(v1, v2, weight, label, ((weight == 0) ? "blue" : 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. // qDebug() << "edgeCreate(): Creating new RECIPROCAL edge:" // << v1 << "->" << v2 // << "weight" << weight // << "label" << label // << "(reverse v2->v1 already exists)"; 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. // qDebug() << "edgeCreate(): Creating new DIRECTED edge:" // << v1 << "->" << v2 // << "weight" << weight // << "label" << label; edgeAdd(v1, v2, weight, EdgeType::Directed, label, ((weight == 0) ? "blue" : color)); emit signalDrawEdge(v1, v2, weight, label, ((weight == 0) ? "blue" : 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) { // qDebug()<< " will create edge from" << source << "to" << 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 special "null" edge (drawn in blue by convention). * * @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]; // qDebug() << "edgeAdd(): Adding arc from vertex" << v1 << "[idx" << source << "]" // << "to vertex" << v2 << "[idx" << target << "]" // << "weight" << weight << "type" << type << "label" << label; // 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. // qDebug() << "edgeAdd(): Edge is Undirected β€” also adding reverse arc" // << v2 << "->" << v1; 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 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) { qDebug() << "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; } } emit signalRemoveEdge(v1, v2, (isDirected() || removeReverse)); setModStatus(ModStatus::EdgeCount); } /** * @brief Removes a SelectedEdge * @param selectedEdge * @param removeReverse */ void Graph::edgeRemoveSelected(SelectedEdge &selectedEdge, const bool &removeReverse) { qDebug() << "Graph::edgeRemoveSelected()" << selectedEdge; edgeRemove(selectedEdge.first, selectedEdge.second, removeReverse); } /** * @brief Removes all selected edges */ void Graph::edgeRemoveSelectedAll() { qDebug() << "Graph::edgeRemoveSelectedAll()"; foreach (SelectedEdge edgeToRemove, getSelectedEdges()) { qDebug() << "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); // qDebug() << "Checking if edge exists:" << v1 << "->" << v2 << "=" << edgeWeightTemp ; if (!checkReciprocal) { return edgeWeightTemp; } else if (edgeWeightTemp != 0) { edgeReverseWeightTemp = m_graph[vpos[v2]]->hasEdgeTo(v1); // qDebug() << "Checking if reverse edge exists: " << v2 << "->" << v1 << "=" << edgeWeightTemp ; 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) { qDebug() << "Edge" << v1 << "->" << v2 << "is symmetric"; return true; } else { qDebug() << "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) { qDebug("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) { qDebug("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) { qDebug() << "Changing the weight of edge" << v1 << "[" << vpos[v1] << "]->" << v2 << "[" << vpos[v2] << "]" << " to new weight " << weight; m_graph[vpos[v1]]->setOutEdgeWeight(v2, weight); if (undirected) { qDebug() << "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) { qDebug() << "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 qDebug() << "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? qDebug() << "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-app-39db829/src/graph/storage/graph_vertices.cpp000066400000000000000000001001131517721000100233520ustar00rootroot00000000000000/** * @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) { qDebug() << "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); qDebug() << "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; qDebug() << "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()); qDebug() << "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) { qDebug() << "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) { qDebug() << "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) { qDebug() << "another vertex" << (*it)->number() << " has outbound Edge to " << v1 << ". Removing it."; (*it)->removeOutEdge(v1); } if (qAbs((*it)->hasEdgeFrom(v1)) > 0) { qDebug() << "another vertex" << (*it)->number() << " has inbound Edge from " << v1 << ". Removing it."; (*it)->removeInEdge(v1); } } qDebug() << "Finished with vertices. " "Update the vpos which maps vertices inside m_graph "; int prevIndex = doomedPos; qDebug() << "Updating vpos of all subsequent vertices "; H_Int::const_iterator it1 = vpos.cbegin(); while (it1 != vpos.cend()) { if (it1.value() > doomedPos) { prevIndex = it1.value(); qDebug() << "vertex" << it1.key() << "had prevIndex:" << prevIndex << " > doomedPos" << doomedPos << "Setting new vpos. vpos size was: " << vpos.size(); vpos.insert(it1.key(), --prevIndex); qDebug() << "vertex" << it1.key() << "new vpos:" << vpos.value(it1.key(), -666) << "vpos size now:" << vpos.size(); } else { qDebug() << "vertex" << it1.key() << "with vpos" << it1.value() << " =< doomedPos. CONTINUE"; } ++it1; } // Now remove vertex Doomed from m_graph qDebug() << "graph vertices=size=" << vertices() << "=" << m_graph.size() << "removing vertex at vpos " << doomedPos; m_graph.removeAt(doomedPos); m_totalVertices--; qDebug() << "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) { qDebug() << "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) { qDebug() << "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(); qDebug() << "Graph::verticesListIsolated() - node " << (*it)->number() << " is isolated. Marking it."; } } qDebug() << "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() { qDebug() << "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() { qDebug() << "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; } qDebug() << "Graph::verticesCreateSubgraph() - type:" << type << "vList:" << vList; int progressCounter = 0; QString pMsg = tr("Creating subgraph. \nPlease wait..."); progressStatus(pMsg); progressCreate(vList.size(), 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) { progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); 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) { progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); 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) { progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); 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) { progressUpdate(++progressCounter); if (progressCanceled()) { progressFinish(); 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 { progressFinish(); return; } progressFinish(); } // // 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 * * @param dropIsolates * @param countAll * @return */ int Graph::vertices(const bool &dropIsolates, const bool &countAll, const bool &recount) { if (m_totalVertices != 0 && calculatedVertices && !recount) { qDebug() << "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()) { qDebug() << "Skipping isolated vertex:" << (*it)->number(); continue; } if (!(*it)->isEnabled()) { qDebug() << "Skipping disabled vertex:" << (*it)->number(); continue; } ++m_totalVertices; } } qDebug() << "Graph size:" << m_graph.size() << "vertices" << m_totalVertices; calculatedVertices = true; 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. * * Returns the vpos or -1 * * Complexity: O(logN) for vpos retrieval * * @param vertex number * @return vertex pos or -1 */ int Graph::vertexExists(const int &v1) { // qDebug () << "Checking if vertex exists, with number:" << 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 exists in the graph * * Returns the vpos or -1 * * Complexity: O(N) * * @param label * @return vpos or -1 */ int Graph::vertexExists(const QString &label) { qDebug() << "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) { qDebug() << "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) != -1) { qDebug() << "vertex number" << v << "exists. Adding it to found list"; foundList << v; } else { qDebug() << "vertex number" << v << "does not exist. Adding it to notFound list"; notFound << vStr; } } else { qDebug() << "cannot read" << vStr; } } if (!foundList.isEmpty()) { searchResult = true; qDebug() << "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 { qDebug() << "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) { qDebug() << "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 = vertexExists(vLabel)) != -1) { qDebug() << "vertex with label" << vLabel << "exists. Adding it to found list"; foundList << m_graph[vFoundPos]->number(); } else { qDebug() << "vertex with label" << vLabel << "does not exist. Adding it to notFound list "; notFound << vLabel; } } if (!foundList.isEmpty()) { searchResult = true; qDebug() << "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 { qDebug() << "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) { qDebug() << "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; } 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("=>"); qDebug() << "thresholdStr starts with >="; } else if (thresholdStr.startsWith(">")) { gtThan = true; thresholdStr.remove(">"); qDebug() << "thresholdStr starts with > "; } else if (thresholdStr.startsWith("<=") || thresholdStr.startsWith("=<")) { lsEqual = true; thresholdStr.remove("<="); thresholdStr.remove("=<"); qDebug() << "thresholdStr starts with <="; } else if (thresholdStr.startsWith("<")) { lsThan = true; thresholdStr.remove("<"); qDebug() << "thresholdStr starts with < "; } else { qDebug() << "thresholdStr does not start with > or <"; continue; } // Parse score threshold threshold = thresholdStr.toDouble(&convertedOk); if (!convertedOk) { qDebug() << "cannot convert thresholdStr to float"; continue; } else { qDebug() << "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; } } if (gtThan) { if (score > threshold) { qDebug() << "matching vertex" << (*it)->number() << "score" << score; foundList << (*it)->number(); } } else if (gtEqual) { if (score >= threshold) { qDebug() << "matching vertex" << (*it)->number() << "score" << score; foundList << (*it)->number(); } } else if (lsThan) { if (score < threshold) { qDebug() << "matching vertex" << (*it)->number() << "score" << score; foundList << (*it)->number(); } } else if (lsEqual) { if (score <= threshold) { qDebug() << "matching vertex" << (*it)->number() << "score" << score; foundList << (*it)->number(); } } } } if (!foundList.isEmpty()) { searchResult = true; qDebug() << "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 { qDebug() << "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) { qDebug() << "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 { qDebug() << "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()) { qDebug() << "vertex:" << v1 << "is isolated"; return true; } qDebug() << "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-app-39db829/src/graph/ui/000077500000000000000000000000001517721000100166165ustar00rootroot00000000000000socnetv-app-39db829/src/graph/ui/graph_canvas.cpp000066400000000000000000000063651517721000100217700ustar00rootroot00000000000000/** * @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; qDebug() << "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); qDebug() << "Finished resizing."; } /** * @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-app-39db829/src/graph/ui/graph_edge_style.cpp000066400000000000000000000222271517721000100226340ustar00rootroot00000000000000/** * @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; } /** * @brief Changes the color of all enabled edges. * @param color * @return */ bool Graph::edgeColorAllSet(const QString &color, const int &threshold) { qDebug() << "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) { qDebug() << " 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) { qDebug() << " Graph::edgeColorAllSet() below weight threshold " << threshold << " - edge " << source << "->" << target << " new color " << color; (*it)->setOutLinkColor(target, color); emit setEdgeColor(source, target, color); } } else { qDebug() << " 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) { qDebug() << "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) { qDebug() << "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; } qDebug() << "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-app-39db829/src/graph/ui/graph_selection.cpp000066400000000000000000000116701517721000100224750ustar00rootroot00000000000000/** * @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) { qDebug() << "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; qDebug() << "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) { qDebug() << "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); qDebug() << "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) { qDebug() << "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-app-39db829/src/graph/ui/graph_ui_facade.cpp000066400000000000000000000044761517721000100224160ustar00rootroot00000000000000/** * @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 QThread *Graph::getThread() const { return thread(); } void Graph::moveToThreadFacade(QThread *t) { moveToThread(t); } // 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() { qDebug() << "Graph::resetProgressCanceled() - resetting flag"; m_progressCanceled = false; } /** * @brief Emits a signal to create a progress box in the UI with the given maximum value and message. * @param max The maximum value for the progress box. * @param msg The message to be shown in the progress box. */ void Graph::progressCreate(int max, const QString &msg) { resetProgressCanceled(); emit signalProgressBoxCreate(max, msg); } /** * @brief Emits a signal to update the progress box in the UI with the given value. * @param value The current value to update the progress box with. */ void Graph::progressUpdate(int value) { emit signalProgressBoxUpdate(value); } /** * @brief Emits a signal to kill the progress box in the UI, indicating that the operation is complete. */ void Graph::progressFinish() { emit signalProgressBoxKill(); } /** * @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() { qDebug() << "Graph::slotCancelComputation() - setting flag from thread:" << QThread::currentThreadId(); m_progressCanceled = true; } socnetv-app-39db829/src/graph/ui/graph_ui_prominence_distribution.cpp000066400000000000000000000230431517721000100261400ustar00rootroot00000000000000/** * @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) { qDebug() << "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()) { qDebug() << "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 } qDebug() << "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) { 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()) { qDebug() << "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; } qDebug() << "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) { qDebug() << "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()) { qDebug() << "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; } qDebug() << "emitting signal to MW update the prominence distribution bar chart"; emit signalPromininenceDistributionChartUpdate(series, axisX, min, max, axisY, minF, maxF); }socnetv-app-39db829/src/graph/ui/graph_vertex_style.cpp000066400000000000000000000463151517721000100232510ustar00rootroot00000000000000/** * @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) { qDebug() << "Changing size of vertex" << v << "new size" << size; m_graph[vpos[v]]->setSize(size); emit setNodeSize(v, size); } else { qDebug() << "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) { qDebug() << "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 { qDebug() << "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) { qDebug() << "Setting vertex" << v1 << "new color" << color; m_graph[vpos[v1]]->setColor(color); emit setNodeColor(m_graph[vpos[v1]]->number(), color); } else { qDebug() << "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 { qDebug() << "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) { qDebug() << "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 { qDebug() << "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) { qDebug() << "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 { qDebug() << "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 { qDebug() << "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) { qDebug() << "Changing number distance for vertex" << v << "new number distance" << newDistance; m_graph[vpos[v]]->setNumberDistance(newDistance); emit setNodeNumberDistance(v, newDistance); } else { qDebug() << "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) { qDebug() << "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) { qDebug() << "Changing the label size of vertex" << v1 << "new label size:" << labelSize; m_graph[vpos[v1]]->setLabelSize(labelSize); emit setNodeLabelSize(v1, labelSize); } else { qDebug() << "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 { qDebug() << "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) { qDebug() << "Changing the label color of vertex" << v1 << "new label color" << color; m_graph[vpos[v1]]->setLabelColor(color); emit setNodeLabelColor(v1, color); } else { qDebug() << "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 { qDebug() << "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) { qDebug() << "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 { qDebug() << "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) { // qDebug() << "Setting custom attributes for vertex" << v1 << ":"<< 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 = vertexExists(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) != -1) 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; } qDebug() << "vertexAttributesImport: matched" << matched << "of" << rows.size() << "rows"; return matched; } socnetv-app-39db829/src/graph/util/000077500000000000000000000000001517721000100171565ustar00rootroot00000000000000socnetv-app-39db829/src/graph/util/graph_type_strings.cpp000066400000000000000000000174001517721000100235770ustar00rootroot00000000000000/** * @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-app-39db829/src/graphicsedge.cpp000077500000000000000000000530141517721000100202370ustar00rootroot00000000000000/** * @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) : 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=4; // controls the width of the edge arrow 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 // qDebug()<< "Constructed graphics edge:" // << 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); adjust(); } /** * @brief Toggles displaying edge arrow * @param drawArrows */ void GraphicsEdge::showArrows(const bool &drawArrows){ prepareGeometryChange(); m_drawArrows=drawArrows; adjust(); } /** * @brief Removes any references to this edge in source and target nodes. */ void GraphicsEdge::removeRefs(){ // qDebug() << "Removing edge refs..."; source->removeOutEdge(this); target->removeInEdge(this); } /** * @brief Sets the edge color * @param str */ void GraphicsEdge::setColor( const QString &str) { m_color=QColor(str); prepareGeometryChange(); } /** * @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) { // qDebug() << "Setting edge weight:" << 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) ; } 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) { // qDebug() << "Setting graphics edge label:" << 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 Leaves some empty space (offset) from node - * make the edge weight appear on the centre of the edge */ void GraphicsEdge::adjust(){ // qDebug() << "GraphicsEdge::adjust()"; if (!source || !target) { return; } // QLineF line(source->x(), source->y(), target->x(), target->y()); //QPointF edgeOffset; //line_length = line.length(); // line_dx = line.dx(); // line_dy = line.dy(); line_length = length(); line_dx = dx(); line_dy = dy(); if (source!=target) { edgeOffset = QPointF( (line_dx * m_offsetFromTargetNode) / line_length, (line_dy * m_offsetFromTargetNode) / line_length); } else edgeOffset = QPointF(0, 0); prepareGeometryChange(); // sourcePoint = line.p1() + edgeOffset ; // targetPoint = line.p2() - edgeOffset ; sourcePoint = source->pos() + edgeOffset ; targetPoint = target->pos() - edgeOffset ; 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 ); } //Define the path upon which we' ll draw the line QPainterPath path(sourcePoint); //Construct the path if (source!=target) { if ( !m_Bezier){ // qDebug()<< "*** GraphicsEdge::paint(). Constructing a line"; path.lineTo(targetPoint); } else { qDebug() << "*** GraphicsEdge::paint(). Constructing a bezier curve"; QPointF c = QPointF( targetPoint.x() - sourcePoint.x(), targetPoint.y() - targetPoint.y()); path.cubicTo( sourcePoint, c, targetPoint); } } else { //self-link QPointF c1 = QPointF( targetPoint.x() -30, targetPoint.y() -30 ); QPointF c2 = QPointF( targetPoint.x() +30, targetPoint.y() -30 ); // qDebug()<< "*** GraphicsEdge::paint(). Constructing a bezier self curve c1 " // < 10) { angle = 0; if ( line_length > 0 ) angle = ::acos( line_dx / line_length ); // qDebug() << " acos() " << ::acos( line_dx / line_length ) ; if ( line_dy >= 0) angle = M_PI_X_2 - angle; // qDebug() << "*** GraphicsEdge::paint(). Constructing arrows. " // "First Arrow at target node" // << "target-source: " << line_dx // << " length: " << line_length // << " angle: "<< angle; QPointF destArrowP1 = targetPoint + QPointF(sin(angle - M_PI_3) * m_arrowSize, cos(angle - M_PI_3) * m_arrowSize); QPointF destArrowP2 = targetPoint + QPointF(sin(angle - M_PI + M_PI_3) * m_arrowSize, cos(angle - M_PI + M_PI_3) * m_arrowSize); // qDebug() << "*** GraphicsEdge::paint() destArrowP1 " // << destArrowP1.x() << "," << destArrowP1.y() // << " destArrowP2 " << destArrowP2.x() << "," << destArrowP2.y(); path.addPolygon ( QPolygonF() << targetPoint << destArrowP1 << destArrowP2 << targetPoint ); if (m_edgeDirType == EdgeType::Undirected || m_edgeDirType == EdgeType::Reciprocated ) { // qDebug() << "**** GraphicsEdge::paint() This edge is SYMMETRIC! " // << " So, we need to create Arrow at src node as well"; QPointF srcArrowP1 = sourcePoint + QPointF(sin(angle +M_PI_3) * m_arrowSize, cos(angle +M_PI_3) * m_arrowSize); QPointF srcArrowP2 = sourcePoint + QPointF(sin(angle +M_PI - M_PI_3) * m_arrowSize, cos(angle +M_PI - M_PI_3) * m_arrowSize); path.addPolygon ( QPolygonF() << sourcePoint << srcArrowP1 << srcArrowP2 <B */ void GraphicsEdge::setDirectionType(const int &dirType){ // qDebug()<< "Edge" // << 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; } /** * @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 { //qDebug() << "GraphicsEdge::pen() - returning pen " ; switch (m_state) { case EDGE_STATE_REGULAR: //qDebug() << "GraphicsEdge::pen() - returning pen for 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 //qDebug() << "GraphicsEdge::pen() - returning pen for state HIGHLIGHTED" ; return QPen( QColor("red"), m_width, m_style, Qt::RoundCap, Qt::RoundJoin); case EDGE_STATE_HOVER: // hover //qDebug() << "GraphicsEdge::pen() - returning pen for state HOVER" ; return QPen(QColor("red"), m_width+1, m_style, Qt::RoundCap, Qt::RoundJoin); default: //qDebug() << "GraphicsEdge::pen() - returning pen for state 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 Pains the edge * * @param painter * @param option */ void GraphicsEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *){ if (!source || !target) return; //qDebug() <<"@@@ GraphicsEdge::paint() on" << painter->paintEngine()->type(); //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); } // set painter pen to correct edge pen painter->setPen(pen()); // set painter brush to paint inside the arrow painter->setBrush( m_color ); painter->drawPath(m_path); } /** * @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) { //qDebug()<< "GraphicsEdge::setHighlighted() - " << 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) { // qDebug() << "GraphicsEdge::mousePressEvent() - click on an edge "; // //setClicked(); // QGraphicsItem::mousePressEvent(e); //} GraphicsEdge::~GraphicsEdge(){ // qDebug() << "self-destructing edge:" // << sourceNodeNumber()<< "->" << targetNodeNumber() // << "will remove refs first..."; removeRefs(); // qDebug() << "removing edge weight number, if any..."; if (m_drawWeightNumber) graphicsWidget->removeItem(weightNumber); // qDebug() << "removing edge label, if any..."; if (m_drawLabel) graphicsWidget->removeItem(edgeLabel); this->hide(); // qDebug() << "calling GW removeItem for this edge"; graphicsWidget->removeItem(this); } socnetv-app-39db829/src/graphicsedge.h000077500000000000000000000077351517721000100177150ustar00rootroot00000000000000/** * @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 //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); ~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 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; 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, line_length, line_dx, line_dy; bool m_Bezier, m_drawArrows, m_drawWeightNumber; bool m_drawLabel, m_hoverHighlighting; bool m_isClicked; }; #endif socnetv-app-39db829/src/graphicsedgelabel.cpp000077500000000000000000000022131517721000100212320ustar00rootroot00000000000000/** * @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 #include GraphicsEdgeLabel::GraphicsEdgeLabel( GraphicsEdge *link , int size, QString labelText) : QGraphicsTextItem( 0) { qDebug()<< "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-app-39db829/src/graphicsedgelabel.h000077500000000000000000000020671517721000100207060ustar00rootroot00000000000000/** * @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-app-39db829/src/graphicsedgeweight.cpp000077500000000000000000000022431517721000100214450ustar00rootroot00000000000000/** * @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 #include GraphicsEdgeWeight::GraphicsEdgeWeight( GraphicsEdge *link , int size, QString labelText) : QGraphicsTextItem( 0) { qDebug()<< "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-app-39db829/src/graphicsedgeweight.h000077500000000000000000000021011517721000100211030ustar00rootroot00000000000000/** * @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-app-39db829/src/graphicsguide.cpp000077500000000000000000000050051517721000100204250ustar00rootroot00000000000000/** * @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-app-39db829/src/graphicsguide.h000077500000000000000000000031461517721000100200760ustar00rootroot00000000000000/** * @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-app-39db829/src/graphicsnode.cpp000077500000000000000000000567671517721000100203020ustar00rootroot00000000000000/** * @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_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); qDebug()<< "Constructed new node" << nodeNumber() << "at pos:" << x()<<"x"<setTargetNodeSize(size); } for (GraphicsEdge *edge: outEdgeList) { // qDebug()<< "Informing oubound edges"; edge->setSourceNodeSize(size); } // qDebug()<< "calling setShape()"; 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; // qDebug()<< "Setting shape for node:" << nodeNumber() // << "shape:" << m_shape // << "iconPath" << iconPath // << "pos:"<< x() << "," << y(); 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; 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 { //qDebug()<< "GraphicsNode::boundingRect() " << m_path.controlPointRect(); 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 ); // qDebug()<< "GraphicsNode::paint() - m_col:" << m_col << "color name:" << m_col.name(); if (option->state & QStyle::State_MouseOver) { // qDebug()<< "Highlighting node because of mouse hover "; painter->setBrush(m_col.darker(120)); setZValue(ZValueNodeHighlighted); } else { // qDebug()<< "GraphicsNode::paint() - no mouse over " << m_col; painter->setBrush(m_col); setZValue(ZValueNode); } if (m_shape == "custom") { QPixmap pix(m_iconPath); painter->drawPixmap(-m_size, -m_size, 2*m_size, 2*m_size, pix); } 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); QPixmap pix(m_iconPath); painter->drawPixmap(-m_size, -m_size, 2*m_size, 2*m_size, pix); } else { painter->setPen(QPen(m_col_outline, 0)); 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) { // qDebug()<<"GraphicsNode::itemChange - change:" << change<< "value:" << value << "m_col" << m_col.name(); 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:{ // qDebug() << "Node item has been enabled"; break; } case ItemSelectedHasChanged:{ // qDebug() << "Node ItemSelectedHasChanged"; if (value.toBool()) { setZValue(ZValueNodeHighlighted); m_size_orig = m_size; setSize(m_size * 2 - 1); 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 ) { // qDebug() << "adding inEdge"<< nodeNumber() << "<-" << edge->sourceNodeNumber(); inEdgeList.push_back( edge); } /** * @brief Removes the inbound edge from this node * @param link */ void GraphicsNode::removeInEdge( GraphicsEdge *edge ){ // qDebug () << "node" << nodeNumber() // << "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 ) { // qDebug() << "adding outEdge"<< nodeNumber() << "->" << edge->targetNodeNumber(); outEdgeList.push_back(edge); } /** * @brief Removes an outbound edge from this node * * @param edge */ void GraphicsNode::removeOutEdge(GraphicsEdge *edge){ // qDebug () << "node" << nodeNumber() // << "Removing outEdge"<< nodeNumber() << "->" << edge->targetNodeNumber() // << "outEdgeList size:" << outEdgeList.size(); outEdgeList.remove(edge); } /** * @brief Creates a graphics label to this node. */ void GraphicsNode::addLabel () { // qDebug()<< "Creating a graphics label for this node." ; 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(){ // qDebug()<< "Deleting the graphics label of this node"; 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) { // qDebug()<< "Changing the label of this node to:" << 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) { // qDebug()<< "Changing the color of node label to:" << 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 () { // qDebug()<<"Adding node graphics number... " ; 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( ){ // qDebug () << "Deleting the graphics node number"; 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) { // qDebug() << "Changing visibility of node number to:" << 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){ // qDebug()<<"GraphicsNode::setNumberInside() " << 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(){ // qDebug() << "Destructing node "<< nodeNumber() // << "- inEdgeList:" << inEdgeList.size() // << "- outEdgeList: " << outEdgeList.size(); // 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(); temp_inEdgeList.clear(); outEdgeList.clear(); temp_outEdgeList.clear(); // qDebug() << "node" << nodeNumber() << "hiding node..."; this->hide(); // qDebug() << "node" << nodeNumber() << "calling GW removeItem..."; graphicsWidget->removeItem(this); } socnetv-app-39db829/src/graphicsnode.h000077500000000000000000000110071517721000100177210ustar00rootroot00000000000000/** * @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 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; 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-app-39db829/src/graphicsnodelabel.cpp000077500000000000000000000026341517721000100212620ustar00rootroot00000000000000/** * @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 #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); qDebug() << "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-app-39db829/src/graphicsnodelabel.h000077500000000000000000000022731517721000100207260ustar00rootroot00000000000000/** * @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-app-39db829/src/graphicsnodenumber.cpp000077500000000000000000000026411517721000100214710ustar00rootroot00000000000000/** * @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 #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); qDebug() << "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-app-39db829/src/graphicsnodenumber.h000077500000000000000000000022651517721000100211400ustar00rootroot00000000000000/** * @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-app-39db829/src/graphicswidget.cpp000077500000000000000000001452321517721000100206220ustar00rootroot00000000000000/** * @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 #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" /** * @brief Constructs a GraphicsWidget object * @param sc * @param m_parent */ GraphicsWidget::GraphicsWidget(QGraphicsScene *sc, MainWindow* m_parent) : QGraphicsView ( sc,m_parent) { qDebug() << "Constructing GraphicsWidget..."; qRegisterMetaType("SelectedEdge"); qRegisterMetaType >(); qRegisterMetaType >(); // Set defaults hasDoubleClickedNode=false; m_isTransformationActive = false; m_nodeLabel=""; // Note: Should be changed by a call to setMaxZoomIndex() m_zoomIndex=250; m_currentScaleFactor = 1; m_currentRotationAngle = 0; clickedEdge=0; edgesHash.reserve(500000); nodeHash.reserve(10000); m_edgeHighlighting = true; m_edgeMinOffsetFromNode=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()->setItemIndexMethod(QGraphicsScene::BspTreeIndex); //NoIndex (for anime) // 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); qDebug () << "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); qDebug() << "Enabled openGL in GraphicsWidget."; } else { setViewport(new QWidget); qDebug() << "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 Creates a QString with the edge name. * * Edge names are used in edgesHash * * @param v1 * @param v2 * @param relation * @return */ QString GraphicsWidget::createEdgeName(const int &v1, const int &v2, const int &relation) { edgeName = QString::number((relation != -1) ? relation : m_curRelation) + QString(":") + QString::number(v1) + QString(">")+ QString::number(v2); return edgeName; } /** * @brief Clears the scene and all hashes, lists, var etc */ void GraphicsWidget::clear() { qDebug() << "Clearing graphics widget data..."; nodeHash.clear(); edgesHash.clear(); m_selectedNodes.clear(); m_selectedEdges.clear(); qDebug() << "Clearing GW scene..."; scene()->clear(); m_curRelation=0; clickedEdge=0; firstDoubleClickedNode=0; secondDoubleClickedNode=0; hasDoubleClickedNode=false; qDebug() << "Finished clearing graphics widget"; } /** * @brief Changes the current relation * * @param relation */ void GraphicsWidget::relationSet(int relation) { qDebug() << "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 ) { // qDebug()<< "Will draw new node:" << nodeNum // << " at:" << p.x() << ", "<< p.y() // << "shape"<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){ // qDebug()<< "Got second consecutive double click. " // "signaling to MW to create a new edge..."; secondDoubleClickedNode=node; emit userMiddleClicked(firstDoubleClickedNode->nodeNumber(), secondDoubleClickedNode->nodeNumber() ); emit setCursor(Qt::ArrowCursor); hasDoubleClickedNode=false; } else{ // qDebug()<<"Got first double click to create a new edge..."; 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 ) { // qDebug () << "Emitting userClickedNode()"; if (clickedEdge) { // qDebug () << "Also unsetting previously clicked edge"; 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) { // qDebug() << "Setting new clicked edge" // << edge->sourceNode()->nodeNumber() // << "->" // << edge->targetNode()->nodeNumber() // << "signalling..."; clickedEdge=edge; emit userClickedEdge(edge->sourceNode()->nodeNumber(), edge->targetNode()->nodeNumber(), openMenu); } else { // qDebug() <<"Empty edge parameter. Unsetting clickedEdge..."; 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){ // qDebug() << "Moving node" << nodeNum << "to pos:" << x << "," << 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 ( nodeHash.contains(nodeNum) ) { qDebug() << "nodeHash size:" << nodeHash.size(); delete nodeHash.value(nodeNum); qDebug() << "Removed node with number:" << nodeNum; } qDebug() << "nodeHash size now:" << nodeHash.size() << "nodeHash contains node?" << nodeHash.contains(nodeNum); } /** * @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){ edgeName = createEdgeName(sourceNum, targetNum); if ( edgesHash.contains(edgeName) ) { int directionType = edgesHash.value(edgeName)->directionType(); delete edgesHash.value(edgeName); // qDebug() << "Removed edge" << edgeName; // Check if it was reciprocated if (directionType == EdgeType::Reciprocated) { // The deleted edge was reciprocated, draw the reverse edge. if (!removeReverse) { qDebug() << "Drawing the reverse edge."; drawEdge(targetNum, sourceNum, 1,""); } } } else { // Check opposite edge. If it exists, then transform it to directed edgeName = createEdgeName(targetNum, sourceNum); qDebug() << "Edge did not exist, checking for opposite:" << edgeName; if ( edgesHash.contains(edgeName) ) { qDebug() << "Opposite edge exists. Check if it is reciprocated"; if ( edgesHash.value(edgeName)->directionType() == EdgeType::Reciprocated ) { edgesHash.value(edgeName)->setDirectionType(EdgeType::Directed); return; } } qDebug() << "No such edge to remove"; } } /** * @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(); // qDebug() << "Removing node with number: " << i; if (firstDoubleClickedNode == node) { // qDebug() << "Node" << i // << "previously set as source node for a new edge. Unsetting."; hasDoubleClickedNode = false; emit setCursor(Qt::ArrowCursor); } nodeHash.remove(i); scene()->removeItem(node); node->deleteLater (); // qDebug() << "Node removed. "; } /** * @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){ edgeName = createEdgeName(edge->sourceNodeNumber(), edge->targetNodeNumber() ) ; // qDebug() << "Removing edge"<< edgeName << "Calling edgeClicked(0)" ; setEdgeClicked(0); // qDebug() << "removing edge from edges hash" ; edgesHash.remove(edgeName); // qDebug() << "removing edge from scene" ; scene()->removeItem(edge); // qDebug() << "Calling edge->deleteLater()" ; edge->deleteLater(); // qDebug() << "Edge removed.Finished"; } /** * @brief Removes an edge weight number item from the scene. * * @param edgeWeight */ void GraphicsWidget::removeItem( GraphicsEdgeWeight *edgeWeight){ // qDebug() << "Removing edge weight"; scene()->removeItem(edgeWeight); edgeWeight->deleteLater(); } /** * @brief Removes an edge label item from the scene. * * @param edgeLabel */ void GraphicsWidget::removeItem( GraphicsEdgeLabel *edgeLabel){ // qDebug() << "Removing edgeLabel"; scene()->removeItem(edgeLabel); edgeLabel->deleteLater(); } /** * @brief Removes a node label item from the scene. * * @param nodeLabel */ void GraphicsWidget::removeItem( GraphicsNodeLabel *nodeLabel){ // qDebug() << "Removing label of node" << nodeLabel->node()->nodeNumber(); scene()->removeItem(nodeLabel); nodeLabel->deleteLater(); } /** * @brief Removes a node number item from the scene. * @param nodeNumber */ void GraphicsWidget::removeItem( GraphicsNodeNumber *nodeNumber){ // qDebug() << "removing number of node " << nodeNumber->node()->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){ // qDebug() << "Setting node"<< nodeNum << "new color to:" << 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){ // qDebug() << "Setting node"<< nodeNum << "new shape to:" << shape; nodeHash.value(nodeNum)->setShape(shape,iconPath); return true; \ } /** * @brief Toggles the visibility of all node labels. * * @param toggle */ void GraphicsWidget::setNodeLabelsVisibility (const bool &toggle){ // qDebug()<< "Toggling node labels visibility to:" << toggle; foreach ( GraphicsNode *m_node, nodeHash) { m_node->setLabelVisibility(toggle); } m_nodeLabelVisibility = toggle; } /** * @brief Sets the label of an node. * * @param nodeNumber * @param label * @return */ bool GraphicsWidget::setNodeLabel(const int &nodeNumber, const QString &label){ // qDebug() << "Setting node"<< nodeNumber << "new label to:" << 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){ // qDebug()<< "Toggling node numbers inside:" << toggle; foreach ( GraphicsNode *m_node, nodeHash) { m_node->setNumberInside(toggle); } 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){ // qDebug()<< "Setting default node size to" << 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 ( nodeHash.contains (nodeNum) ) { // qDebug() << "Setting visibility of node" // << nodeNum << "to" << toggle; nodeHash.value(nodeNum)->setVisible(toggle); nodeHash.value(nodeNum)->setEnabled(toggle); } } /** * @brief Changes the size of a node * * @param nodeNum * @param size * @return bool */ bool GraphicsWidget::setNodeSize(const int &nodeNum, const int &size ){ // qDebug() << "Setting size of node" // << nodeNum << "to" << size; if ( nodeHash.contains (nodeNum) ) { if (size>0){ nodeHash.value(nodeNum)->setSize(size); return true; } else { // qDebug() << "Setting size of node" // << nodeNum << "to default" << m_nodeSize; nodeHash.value(nodeNum)->setSize(m_nodeSize); return true; } } return false; } /** * @brief Changes the size of all nodes. * * @param size */ void GraphicsWidget::setNodeSizeAll(const int &size ){ // qDebug() << "Changing all nodes size... "; foreach ( GraphicsNode *m_node, nodeHash ) { m_node->setSize(size); } } /** * @brief Toggles the visibility of node numbers * * @param toggle */ void GraphicsWidget::setNodeNumberVisibility(const bool &toggle){ // qDebug()<< "toggling visibility of all node numbers to" << toggle; foreach ( GraphicsNode *m_node, nodeHash) { m_node->setNumberVisibility(toggle); } m_nodeNumberVisibility = toggle; } /** * @brief Changes the color of a node number * * @param nodeNumber * @param color */ void GraphicsWidget::setNodeNumberColor(const int &nodeNum, const QString &color) { // qDebug() << "Setting color of node" // << nodeNum << "to" << color; if ( nodeHash.contains (nodeNum) ) { if (!color.isNull()){ nodeHash.value(nodeNum)->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){ // qDebug() << "Setting number size of node" // << nodeNum << "to" << size; if ( nodeHash.contains (nodeNum) ) { if (size>0){ nodeHash.value(nodeNum)->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 ){ // qDebug() << "Setting number distance of node" // << nodeNum << "to" << distance; if ( nodeHash.contains (nodeNum) ) { if (distance>=0){ nodeHash.value(nodeNum)->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){ // qDebug() << "Setting label color of node" // << nodeNum << "to" << color; if ( nodeHash.contains (nodeNum) ) { nodeHash.value(nodeNum)->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){ // qDebug() << "Setting label size of node" // << nodeNum << "to" << size; if ( nodeHash.contains (nodeNum) ) { if (size>0){ nodeHash.value(nodeNum)->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 ){ // qDebug() << "Setting label distance of node" // << nodeNum << "to" << distance; if ( nodeHash.contains (nodeNum) ) { if (distance>=0){ nodeHash.value(nodeNum)->setLabelDistance(distance) ; return true; } } return false; } /** * @brief Checks if a node with label or nodeNum 'text' exists and returns it * * @param text * @return */ GraphicsNode* GraphicsWidget::hasNode( QString text ){ bool ok = false; foreach ( GraphicsNode *candidate, nodeHash) { if ( candidate->nodeNumber()==text.toInt(&ok, 10) || ( candidate->labelText() == text) ) { // qDebug() << "Node" << text << " found!"; return candidate; break; } } return 0; //dummy return. } /** * @brief Selects all nodes in the given list * * @param list */ void GraphicsWidget::setSelectedNodes(QList list){ // qDebug() << "Setting nodes:" << list.size() << "as selected"; foreach ( int nodeNum, list) { if ( nodeHash.contains (nodeNum) ) { // qDebug() << "Selecting node"<< nodeNum; nodeHash.value(nodeNum)->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){ edgeName = createEdgeName( source, target ); // qDebug()<<"Setting label for edge" << edgeName << "new label" << label; if ( edgesHash.contains (edgeName) ) { edgesHash.value(edgeName)->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){ edgeName = createEdgeName( source, target ); // qDebug()<<"Setting color of edge" << edgeName << "new color:" << color; if ( edgesHash.contains (edgeName) ) { edgesHash.value(edgeName)->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){ // qDebug() << "Changing the direction type of edge: " // << source // << "->" << target // << "to type:" << dirType; edgeName = createEdgeName( source, target ); if ( edgesHash.contains (edgeName) ) { // qDebug()<<" Found edge in edgesHash. "; edgesHash.value(edgeName)->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){ edgeName = createEdgeName( source, target ); // qDebug()<<"Setting weight of edge:" << edgeName << " new weight " << weight; if ( edgesHash.contains (edgeName) ) { edgesHash.value(edgeName)->setWeight(weight); return true; } else { //check opposite edge. If it exists, then transform it to directed edgeName = createEdgeName(target, source); // qDebug() << "Edge did not exist, checking for opposite:" << edgeName; if ( edgesHash.contains(edgeName) ) { // qDebug() << "Opposite edge exists. Check if it is reciprocated"; edgesHash.value(edgeName)->setWeight(weight); return true; } } return false; } /** * @brief Toggles the visibility of all edge arrows * * @param toggle */ void GraphicsWidget::setEdgeArrowsVisibility(const bool &toggle){ // qDebug()<< "Setting visibility of all edge arrows to:" << toggle; foreach ( GraphicsEdge *m_edge, edgesHash) { m_edge->showArrows(toggle); } } /** * @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){ // qDebug() << "Changing offset of edge: " << source << "->" << target // << ", new offset" << offset; if (source && target) { QString edgeName = QString::number(m_curRelation) + QString(":") + QString::number( source ) + QString(">")+ QString::number( target ); if ( edgesHash.contains (edgeName) ) { edgesHash.value(edgeName)->setMinimumOffsetFromNode(offset); return; } } // if source == target == 0, then we change all edges'offset. else { QList list = scene()->items(); for (QList::iterator item=list.begin();item!=list.end(); item++) { if ( (*item)->type() ==TypeEdge){ GraphicsEdge *edge = (GraphicsEdge*) (*item); edge->setMinimumOffsetFromNode(offset); } } } } /** * @brief Toggles all edge weight numbers visibility * @param toggle */ void GraphicsWidget::setEdgeWeightNumbersVisibility (const bool &toggle){ // qDebug()<< "Toggling all edge weight numbers' visibility" << toggle; foreach ( GraphicsEdge *m_edge, edgesHash) { m_edge->setWeightNumberVisibility(toggle); } } /** * @brief Toggles all edge labels visibility * @param toggle */ void GraphicsWidget::setEdgeLabelsVisibility (const bool &toggle){ // qDebug()<< "GW::setEdgeLabelsVisibility() " << toggle // << "for edgesHash.count: " << edgesHash.size(); foreach ( GraphicsEdge *m_edge, edgesHash) { m_edge->setLabelVisibility(toggle); } } /** * @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){ // qDebug()<< "Toggling edge highlighting" << toggle; foreach ( GraphicsNode *m_node, nodeHash) { m_node->setEdgeHighLighting(toggle); } foreach ( GraphicsEdge *m_edge, edgesHash) { m_edge->setHighlighting(toggle); } m_edgeHighlighting = toggle; } /** * @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) { QString reverseEdgeName; edgeName = createEdgeName(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 (edgesHash.contains(edgeName)) { // Edge does exist if (!visible && preserveReverseEdge) { // This edge must be disabled and the reverse must be preserved/created delete edgesHash.value(edgeName); } else { edgesHash.value(edgeName)->setVisible(visible); edgesHash.value(edgeName)->setEnabled(visible); } } else { // Edge does not exist yet if (visible) { // The reverse may exist. Check reverseEdgeName = createEdgeName(targetNum, sourceNum, relation); if (edgesHash.contains(reverseEdgeName)) { edgesHash.value(reverseEdgeName)->setVisible(true); edgesHash.value(reverseEdgeName)->setEnabled(true); edgesHash.value(reverseEdgeName)->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 reverseEdgeName = createEdgeName(targetNum, sourceNum, relation); if (edgesHash.contains(reverseEdgeName)) { edgesHash.value(reverseEdgeName)->setVisible(true); edgesHash.value(reverseEdgeName)->setEnabled(true); edgesHash.value(reverseEdgeName)->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 Toggles the visibility of all items of the given type * * @param type * @param visible */ void GraphicsWidget::setAllItemsVisibility(int type, bool visible){ QList list = scene()->items(); for (QList::iterator item=list.begin();item!=list.end(); item++) { qDebug()<< "GW::setAllItemsVisibility. item type is " << (*item)->type(); 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(); } /** * @brief Adds a horizonal guideline * @param y0 */ void GraphicsWidget::addGuideHLine(const double &y0){ GraphicsGuide *line=new GraphicsGuide (this, y0, width()); line->show(); } /** * @brief Removes all items of the given type * @param type */ void GraphicsWidget::removeAllItems(int type){ qDebug()<< "Removing all GW items..."; QList list = scene()->items(); for (QList::iterator item=list.begin();item!=list.end(); item++) { if ( (*item)->type() == type){ GraphicsGuide *guide = qgraphicsitem_cast (*item); qDebug()<< "located element"; guide->die(); guide->deleteLater (); delete *item; } } } void GraphicsWidget::clearGuides(){ qDebug()<< "Clearing/Removing guides..."; this->removeAllItems(TypeGuide); } /** * @brief Forces the scene to select all items. Also signals that no node is clicked. */ void GraphicsWidget::selectAll(){ QPainterPath path; qDebug() << "Selecting all scene items... " << "Widget dimensions: " << width()<<"x"<width()<<"x"<height(); path.addRect(0, 0, width(),height()); scene()->setSelectionArea(path); emit userClickedNode(0, QPointF(0,0)); qDebug() << "Selected scene items now: " << selectedItems().size(); } /** * @brief Clears any item selection from the scene. Also signals that no node is clicked. */ void GraphicsWidget::selectNone(){ qDebug() << "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. * * Emits selected nodes and edges to Graph * */ void GraphicsWidget::handleSelectionChanged() { qDebug() <<"Scene selection has been changed. Getting selected nodes/edges and passing them to Graph..."; emit userSelectedItems(selectedNodes(), selectedEdges()); } /** * @brief Returns a list of all selected QGraphicsItem(s) * * @return QList */ QList GraphicsWidget::selectedItems(){ qDebug() <<"GW::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()); } } qDebug() <<"Selected nodes count:" << m_selectedNodes.size(); 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; } } qDebug() <<"Selected edges count:" << m_selectedEdges.size(); 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)) { qDebug() << "Double-click on a node. Starting new edge..."; handleDoubleClickOnNode(node); QGraphicsView::mouseDoubleClickEvent(e); return; } else if ( (*item).type() == TypeLabel){ QGraphicsView::mouseDoubleClickEvent(e); return; } qDebug() << "Double-click on something (not node)"; } QPointF p = mapToScene(e->pos()); qDebug() << "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 // qDebug() << "Clicked on a node at:" << e->pos() << "~"<< p << "Setting it as clicked node..."; setNodeClicked(node); if ( e->button()==Qt::RightButton ) { qDebug() << "This was a right-click on node. Signaling to open node menu"; if ( !node->isSelected() ) node->setSelected(true); emit openNodeMenu(); } if ( e->button()==Qt::MiddleButton) { qDebug() << "This was a middle-click on node. Calling to start or conclude a new edge..."; handleDoubleClickOnNode(node); } QGraphicsView::mousePressEvent(e); return; } if (GraphicsEdge *edge = qgraphicsitem_cast(item)) { // // User clicked on an edge // qDebug() << "Clicked on an edge at:" << e->pos() << "~"<< p; if ( e->button()==Qt::LeftButton ) { qDebug() << "This was a left click on the edge. Setting clicked edge..."; setEdgeClicked(edge); } else if ( e->button()==Qt::RightButton ) { qDebug() << "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 // qDebug() << "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. // qDebug() << "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)) { qDebug() << "Mouse released at:" << e->pos() << "~"<< p << "on a node. Signalling to move all selected nodes"; Q_UNUSED(node); foreach (QGraphicsItem *item, scene()->selectedItems()) { if (GraphicsNode *nodeSelected = qgraphicsitem_cast(item) ) { emit userNodeMoved(nodeSelected->nodeNumber(), nodeSelected->x(), nodeSelected->y()); } } QGraphicsView::mouseReleaseEvent(e); } if (GraphicsEdge *edge= qgraphicsitem_cast(item)) { Q_UNUSED(edge); qDebug() << "Mouse released at:" << e->pos() << "~"<< p << "on an edge."; QGraphicsView::mouseReleaseEvent(e); return; } } else{ qDebug() << "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; qDebug() << "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){ qDebug() << "Zooming out from "<< m_zoomIndex << "-" << step ; m_zoomIndex-=step; if (m_zoomIndex <= 0) { m_zoomIndex = 0; } 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){ qDebug() << "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; } 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) { qDebug () << "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) ); qDebug() << "Scaling view transformation by new scale factor:" << m_currentScaleFactor << "rotation unchanged:" << m_currentRotationAngle; resetTransform(); scale(m_currentScaleFactor, m_currentScaleFactor); rotate(m_currentRotationAngle); qDebug () << "Finished scaling the view." << " - GW dimensions: " << width() << "x" << height() << " Scene dimensions:" << scene()->width() << "x" << scene()->height(); } /** * @brief Decreases the numerical rotation Rotates the view to the left, by a fixed angle */ void GraphicsWidget::rotateLeft(){ m_currentRotationAngle-=5; emit rotationChanged(m_currentRotationAngle); } /** * @brief Rotates the view to the right, by a fixed angle */ void GraphicsWidget::rotateRight() { m_currentRotationAngle+=5; 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; qDebug() << "Rotating clockwise by angle" << angle << " m_currentRotationAngle " << m_currentRotationAngle << " m_currentScaleFactor " << m_currentScaleFactor; resetTransform(); scale(m_currentScaleFactor, m_currentScaleFactor); rotate(angle); } /** * @brief Resets to default rotation, zoom and scale */ void GraphicsWidget::reset() { m_currentRotationAngle=0; m_currentScaleFactor = 1; m_zoomIndex=m_zoomIndexInit; this->ensureVisible(QRectF(0, 0, 0, 0)); 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 ) { qDebug () << "GW resized:" << e->oldSize().width() << "x" << e->oldSize().height() << "-->" << e->size().width() << "x" << e->size().height() << "widget dimensions:" << width() << "x"<size().width())/(double)(e->oldSize().width()); fY = (double)(e->size().height())/(double)(e->oldSize().height()); // Reposition guides foreach (QGraphicsItem *item, scene()->items()) { if ( (item)->type() == TypeGuide ){ if (GraphicsGuide *guide = qgraphicsitem_cast (item) ) { if (guide->isCircle()) { guide->die(); guide->deleteLater (); delete item; } else { qDebug()<< "Horizontal GraphicsGuide " << " original position (" << guide->x() << "," << guide->y() << ") - width " << guide->width() << ") - will move to (" << guide->x()*fX << ", " << guide->y()*fY << ")" << " new width " << (int) ceil( (guide->width() *fX )); guide->setHorizontalLine( mapToScene(guide->pos().x()*fX, guide->pos().y()*fY), (int) ceil( (guide->width() *fX ))); } } } } qDebug () << "Scene dimensions now:" << scene()->width() << "x" << scene()->height(); emit resized(e->size().width(), e->size().height()); QGraphicsView::resizeEvent(e); } /** * @brief Destructor. Calls the method to clear the data. */ GraphicsWidget::~GraphicsWidget(){ qDebug() << "Terminating. Calling clear()"; clear(); } socnetv-app-39db829/src/graphicswidget.h000077500000000000000000000200361517721000100202610ustar00rootroot00000000000000/** * @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 "global.h" SOCNETV_USE_NAMESPACE class MainWindow; class GraphicsNode; class GraphicsEdge; class GraphicsNodeNumber; class GraphicsNodeLabel; class GraphicsGuide; class GraphicsEdgeWeight; class GraphicsEdgeLabel; typedef QHash H_StrToEdge; typedef QHash H_NumToNode; class GraphicsWidget : public QGraphicsView { Q_OBJECT public: GraphicsWidget(QGraphicsScene *m_scene, MainWindow *m_parent); ~GraphicsWidget(); void clear(); QString createEdgeName(const int &v1, const int &v2, const int &relation=-1); void setInitNodeSize(int); void setInitZoomIndex (const int &); void setMaxZoomIndex (const int &); GraphicsNode* hasNode(QString text); void setSelectedNodes(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); void removeAllItems(int); 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); void setNodeSizeAll(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); 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 setEdgeWeightNumbersVisibility (const bool &toggle); void setEdgeLabelsVisibility(const bool &toggle); void setEdgeHighlighting(const bool &toggle); 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 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); signals: void userDoubleClickNewNode(const QPointF &); void userMiddleClicked(const int &sourceNum, const int &targetNum, const qreal &weight=1); void userClickOnEmptySpace(const QPointF &p); void openNodeMenu(); void openContextMenu(const QPointF p); void userNodeMoved(const int &, const int &, const int &); //void userSelectedItems(const int nodes, const int edges); void userSelectedItems(const QList selectedNodes, const QList selectedEdges); void userClickedNode(const int &nodeNumber, const QPointF &p); void userClickedEdge(const int &source, const int &target, const bool &openMenu=false); void zoomChanged(const int); void rotationChanged(const int); void resized(const int, const int); void setCursor(Qt::CursorShape); private: H_NumToNode nodeHash; //Our basic hash table for node items H_StrToEdge 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; double m_currentScaleFactor; qreal fX,fY, factor; QString m_nodeLabel, m_numberColor, m_labelColor; QString edgeName; bool m_isTransformationActive; bool hasDoubleClickedNode, clickedEdgeExists; bool m_nodeNumbersInside, m_nodeNumberVisibility, m_nodeLabelVisibility; bool m_edgeHighlighting; GraphicsNode *firstDoubleClickedNode, *secondDoubleClickedNode; GraphicsNode *markedEdgeSource; GraphicsNode *markedEdgeTarget; GraphicsEdge *clickedEdge; }; #endif socnetv-app-39db829/src/graphvertex.cpp000077500000000000000000001073231517721000100201540ustar00rootroot00000000000000/** * @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) { qDebug() << "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_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_CLC=0; m_hasCLC=false; m_curRelation=relation; m_enabled = true; // Connect signal for edge visibility directly to the parent signal! connect( this, &GraphVertex::signalSetEdgeVisibility, m_graph, &Graph::signalSetEdgeVisibility); } /** * @brief constructor with default values * @param name */ GraphVertex::GraphVertex(const int &name) { qDebug() << "name"<< name << "initializing with default values"; m_number=name; m_size=9; m_color="black"; m_label=""; m_labelColor="black"; m_shape="circle"; m_outEdgesCounter=0; m_inEdgesCounter=0; m_Eccentricity=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_IRCC=0; m_SIRCC=0; m_SC=0; m_SSC=0; m_curRelation=0; m_customAttributes = QHash(); } /** * @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 { // qDebug() << "GraphVertex::customAttributes()" << m_customAttributes; 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 */ void GraphVertex::setRelation(int newRel) { // qDebug() << "vertex" << number() << "current rel:" << m_curRelation << "new rel:" << newRel; // first make false all edges of current relation setEnabledEdgesByRelation(m_curRelation, false); // then make true all edges of new relation setEnabledEdgesByRelation(newRel, true); // update current relation m_curRelation=newRel; } /** * @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) { // qDebug() << "vertex" << number() << "adding new outbound edge"<< "->"<< v2 // << "weight"<< weight<< "relation" << m_curRelation; // 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) { // qDebug() << "vertex" << number() << "removing outEdge to" << 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 ) { // qDebug() << " *** vertex " << m_number << " connected to " // << it1.key() << " relation " << it1.value().first // << " weight " << it1.value().second.first // << " enabled ? " << it1.value().second.second // << " Erasing outEdge from m_outEdges "; 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){ // qDebug() << "vertex" << number() << "setting outEdge to" << target << "new status" << 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; // qDebug() << " *** vertex " << m_number << " connected to " // << linkTarget << " relation " << relation // << " weight " << weight // << " status " << it1.value().second.second; it1.value() = pair_i_fb(m_curRelation, pair_f_b(weight, status) ); emit signalSetEdgeVisibility (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){ // qDebug() << "vertex" << number() << "changing weight of outEdge to" << target << "new weight" << 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) { // qDebug() << "vertex" << number() << "adding new inbound edge"<< "<-"<< v1 // << "weight"<< weight<< "relation" << m_curRelation; 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){ // qDebug() << "vertex" << number() << "removing inEdge from" << 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 ) { // qDebug() << " *** vertex " << m_number << " connected from " // << it.key() << " relation " << it.value().first // << " weight " << it.value().second.first // << " enabled ? " << it.value().second.second // << " Erasing inEdge from m_inEdges "; 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){ // qDebug() << "vertex" << number() << ", toggling status of inEdge:" << number() << "<-" << source << "new status:" << 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; // qDebug() << " *** vertex " << m_number << " connected to " // << linkTarget << " relation " << relation // << " weight " << weight // << " status " << it1.value().second.second; it1.value() = pair_i_fb(m_curRelation, pair_f_b(weight, status) ); emit signalSetEdgeVisibility (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){ // qDebug() << "vertex" << number() << "changing weight of inEdge from" << source << "new weight" << 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; if (edgeStatus) { 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){ // qDebug() << "vertex " << number(); 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; } // qDebug() << "vertex" << number() << "outEdges count:"<< outEdgesAll->size(); 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; } // qDebug() << "vertex" << number() << "reciprocalEdges count:" << m_reciprocalEdges.size(); 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; } // qDebug() << "vertex" << number() << "enabled inEdges count:"<< enabledInEdges->size(); return enabledInEdges; } /** * @brief Returns the outDegree (the sum of all enabled outEdges weights) of this vertex * * @return int */ int GraphVertex::degreeOut() { // qDebug() << "vertex" << number(); 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() { // qDebug() << "vertex" << number(); 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; } // qDebug() << "vertex" << number() << "localDegree:" << m_localDegree; return m_localDegree; } /** * @brief Changes the status of all unilateral (non-reciprocal) outbound edges, in current relation * * @param status */ void GraphVertex::setEnabledUnilateralEdges(const bool &status){ // qDebug() << "vertex:" << number() << "setting unilateral edges of relation" << relation << "to" << status; 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) { // qDebug() << "vertex:" << number() << "Changing the status of unilateral outbound edge to" << target << "new status" << status << "and emitting signal to Graph...."; it.value() = pair_i_fb(m_curRelation, pair_f_b(weight, status) ); emit signalSetEdgeVisibility (m_curRelation, m_number, target, status ); } } } } /** * @brief Changes the status of all edges in the given relation * * @param relation * @param status */ void GraphVertex::setEnabledEdgesByRelation(const int relation, const bool status ){ // qDebug() << "vertex:" << number() << "setting edges of relation" << relation << "to" << status; 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) ); emit signalSetEdgeVisibility ( relation, m_number, target, status ); } } } /** * @brief Stores the geodesic distance to vertex v1 * * @param v1 * @param dist */ void GraphVertex::setDistance (const int &v1, const qreal &d) { m_distance.insert( v1, pair_i_f(m_curRelation, d ) ); } /** * @brief Reserves N items for the distance hash. See QHash Algorithmic Complexity * Not to be used on large nets, atm. * @param N */ void GraphVertex::reserveDistance (const int &N) { m_distance.reserve(N); } /** * @brief Returns the geodesic distance to vertex v1. * * If d to v1 has not been set previously, returns RAND_MAX * * @param v1 */ qreal GraphVertex::distance (const int &v1) { qreal d=RAND_MAX; int relation=0; H_distance::const_iterator it1=m_distance.constFind(v1); while (it1 != m_distance.constEnd() && it1.key() == v1 ) { relation = it1.value().first; if ( relation == m_curRelation ) { d = it1.value().second; break; } ++it1; } // qDebug() << "vertex" << number() << "distance to" << v1 << "is" << d; return d; } /** * @brief Removes all items from m_distance hash dictionary */ void GraphVertex::clearDistance() { m_distance.clear(); } /** * @brief Stores the number of shortest paths from this vertex to vertex v1 * * @param v1 * @param sp */ void GraphVertex::setShortestPaths (const int &v1, const int &sp) { // qDebug() << "vertex" << number() << "setting shortest paths count to" << v1 << "equal to" << sp; m_shortestPaths.insert( v1, pair_i_i( m_curRelation, sp ) ); } /** * @brief Returns the stored number of shortest paths to vertex v1 * * If it has not been set previously, then returns 0 * * @param v1 */ int GraphVertex::shortestPaths (const int &v1) { int sp=0; int relation=0; H_shortestPaths::const_iterator it1=m_shortestPaths.constFind(v1); while (it1 != m_shortestPaths.constEnd() && it1.key() == v1 ) { relation = it1.value().first; if ( relation == m_curRelation ) { sp = it1.value().second; break; } ++it1; } // qDebug() << "vertex" << number() << "shortest paths to" << v1 << "count" << sp; return sp; } /** * @brief Reserves N items for the ShortestPaths hash. * * See QHash Algorithmic Complexit. Not to be used on large nets, atm. * * @param N */ void GraphVertex::reserveShortestPaths (const int &N) { m_shortestPaths.reserve(N); } /** * @brief Removes all items from m_shortestPaths hash dictionary */ void GraphVertex::clearShortestPaths() { m_shortestPaths.clear(); } /** * @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 Stores the pair dependency of the vertex * @param c */ void GraphVertex::setDelta (const qreal &c){ m_delta=c; } /** * @brief Returns the stored pair dependency of the vertex * @return */ qreal GraphVertex::delta() { return m_delta; } /** * @brief Clears the list of predecessors of this vertex */ void GraphVertex::clearPs() { myPs.clear(); } /** * @brief Appends a vertex to the list of predecessors of this vertex * @param vertex */ void GraphVertex::appendToPs(const int &vertex ) { // qDebug()<<"vertex"<< number()<< "appending vertex" << vertex << "to myPs"; myPs.append(vertex); } /** * @brief Returns the list of predecessors of this vertex * @return */ L_int GraphVertex::Ps(void) { return myPs; } /** * @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) { // qDebug()<<"vertex"<< number()<< "adding clique with:" << clique; m_cliques.insert(clique.size(), clique); } GraphVertex::~GraphVertex() { // qDebug()<<"vertex"<< number()<< "destroying..."; 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(); clearPs(); m_shortestPaths.clear(); m_shortestPaths.squeeze(); m_distance.clear(); m_distance.squeeze(); m_cliques.clear(); m_cliques.squeeze(); } socnetv-app-39db829/src/graphvertex.h000077500000000000000000000302721517721000100176170ustar00rootroot00000000000000/** * @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 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; typedef QPair pair_i_f; typedef QHash < int, pair_i_f > H_distance; typedef QPair pair_i_i; typedef QHash < int, pair_i_i > H_shortestPaths; class GraphVertex : public QObject{ Q_OBJECT 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(const int &name); ~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(); void 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(); void setEnabledEdgesByRelation(const int relation, const bool status); void setEnabledUnilateralEdges(const bool &status=false); qreal distance(const int &v1) ; void setDistance (const int &v1, const qreal &d) ; void reserveDistance(const int &N); void clearDistance(); int shortestPaths(const int &v1) ; void setShortestPaths(const int &v1, const int &sp) ; void reserveShortestPaths(const int &N); void clearShortestPaths(); void setEccentricity(const qreal &c); qreal eccentricity(); void setDelta(const qreal &c); qreal delta(); void clearPs() ; void appendToPs(const int &vertex ) ; L_int Ps(void); //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*/ 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; //Hash dictionary of this vertex pair-wise distances to all other vertices for each relationship //The key is the relationship //The value is a QPair < int target, qreal weight > H_distance m_distance; H_shortestPaths m_shortestPaths; signals: void signalSetEdgeVisibility (const int &relation, const int &name, const int &target, const bool &visible, const bool &preserveReverseEdge=false, const int &edgeWeight=1, const int &reverseEdgeWeight=1 ); 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_delta, 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_distanceSum; QString m_color, m_numberColor, m_label, m_labelColor, m_shape, m_iconPath; QPointF m_disp; QHash m_customAttributes; QHash m_reciprocalEdges; L_int myPs; QMultiHash m_cliques; H_IntToStr m_outLinkColors, m_outEdgeLabels; QHash> m_outEdgeCustomAttributes; //FIXME vertex coords }; #endif socnetv-app-39db829/src/icon.rc000077500000000000000000000012231517721000100163570ustar00rootroot000000000000001 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-app-39db829/src/images.qrc000077500000000000000000000163561517721000100170720ustar00rootroot00000000000000 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-app-39db829/src/images/000077500000000000000000000000001517721000100163455ustar00rootroot00000000000000socnetv-app-39db829/src/images/about_24px.svg000066400000000000000000000011251517721000100210540ustar00rootroot00000000000000socnetv-app-39db829/src/images/add_48px.svg000066400000000000000000000002161517721000100205000ustar00rootroot00000000000000socnetv-app-39db829/src/images/adjacencyplot.png000066400000000000000000000002541517721000100216740ustar00rootroot00000000000000‰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-app-39db829/src/images/arrow_down_48px.svg000066400000000000000000000002301517721000100221250ustar00rootroot00000000000000socnetv-app-39db829/src/images/assessment_48px.svg000066400000000000000000000003611517721000100221360ustar00rootroot00000000000000socnetv-app-39db829/src/images/avdistance.png000066400000000000000000000024661517721000100212040ustar00rootroot00000000000000‰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-app-39db829/src/images/box.png000066400000000000000000000004411517721000100176420ustar00rootroot00000000000000‰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-app-39db829/src/images/bugs.png000066400000000000000000000033451517721000100200200ustar00rootroot00000000000000‰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-app-39db829/src/images/centrality_48px.svg000066400000000000000000000014411517721000100221270ustar00rootroot00000000000000socnetv-app-39db829/src/images/chevron_left_48px.svg000066400000000000000000000002271517721000100224300ustar00rootroot00000000000000socnetv-app-39db829/src/images/chevron_right_48px.svg000066400000000000000000000002271517721000100226130ustar00rootroot00000000000000socnetv-app-39db829/src/images/circle.png000066400000000000000000000007251517721000100203200ustar00rootroot00000000000000‰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-app-39db829/src/images/circular.png000066400000000000000000000041001517721000100206520ustar00rootroot00000000000000‰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-app-39db829/src/images/clear_48px.svg000066400000000000000000000003221517721000100210340ustar00rootroot00000000000000socnetv-app-39db829/src/images/clique.png000066400000000000000000000026521517721000100203420ustar00rootroot00000000000000‰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-app-39db829/src/images/cliquenew.png000066400000000000000000000027201517721000100210500ustar00rootroot00000000000000‰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-app-39db829/src/images/close_24px.svg000066400000000000000000000003231517721000100210460ustar00rootroot00000000000000socnetv-app-39db829/src/images/clucof.png000066400000000000000000000006501517721000100203270ustar00rootroot00000000000000‰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-app-39db829/src/images/clustering.png000066400000000000000000000003621517721000100212330ustar00rootroot00000000000000‰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-app-39db829/src/images/cocitation.png000066400000000000000000000003541517721000100212110ustar00rootroot00000000000000‰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-app-39db829/src/images/cocitation_48px.svg000066400000000000000000000012261517721000100221060ustar00rootroot00000000000000socnetv-app-39db829/src/images/code_48px.svg000066400000000000000000000003121517721000100206570ustar00rootroot00000000000000socnetv-app-39db829/src/images/color_layout_48px.svg000066400000000000000000000012601517721000100224630ustar00rootroot00000000000000socnetv-app-39db829/src/images/colorize_48px.svg000066400000000000000000000005061517721000100216000ustar00rootroot00000000000000socnetv-app-39db829/src/images/communities_48px.svg000066400000000000000000000046751517721000100223210ustar00rootroot00000000000000socnetv-app-39db829/src/images/connectivity.png000066400000000000000000000002541517721000100215720ustar00rootroot00000000000000‰PNG  IHDR szzτsIDATX…c`ƒ œ’–ώŒιjΊbJA²y4 kόƒ ηSΫ$ƒ‘ηlšΘ΅œVζ‘ θnα¨ε£–Λ±δ끳œξ…Λ¨άΨAWΛFL^Ή–ΣΊ”Γ›€iΕš?κ€w6EΤ²œω0εP_–PΈIENDB`‚socnetv-app-39db829/src/images/controls/000077500000000000000000000000001517721000100202105ustar00rootroot00000000000000socnetv-app-39db829/src/images/controls/checkbox_checked.svg000066400000000000000000000005411517721000100241650ustar00rootroot00000000000000 socnetv-app-39db829/src/images/controls/checkbox_checked_hover.svg000066400000000000000000000005411517721000100253700ustar00rootroot00000000000000 socnetv-app-39db829/src/images/controls/checkbox_checked_pressed.svg000066400000000000000000000005411517721000100257120ustar00rootroot00000000000000 socnetv-app-39db829/src/images/controls/checkbox_unchecked.svg000066400000000000000000000003211517721000100245240ustar00rootroot00000000000000 socnetv-app-39db829/src/images/controls/checkbox_unchecked_hover.svg000066400000000000000000000003211517721000100257270ustar00rootroot00000000000000 socnetv-app-39db829/src/images/controls/checkbox_unchecked_pressed.svg000066400000000000000000000003211517721000100262510ustar00rootroot00000000000000 socnetv-app-39db829/src/images/controls/radio_checked.svg000066400000000000000000000003621517721000100234760ustar00rootroot00000000000000 socnetv-app-39db829/src/images/controls/radio_checked_hover.svg000066400000000000000000000003621517721000100247010ustar00rootroot00000000000000 socnetv-app-39db829/src/images/controls/radio_checked_pressed.svg000066400000000000000000000003621517721000100252230ustar00rootroot00000000000000 socnetv-app-39db829/src/images/controls/radio_unchecked.svg000066400000000000000000000002701517721000100240370ustar00rootroot00000000000000 socnetv-app-39db829/src/images/controls/radio_unchecked_hover.svg000066400000000000000000000002701517721000100252420ustar00rootroot00000000000000 socnetv-app-39db829/src/images/controls/radio_unchecked_pressed.svg000066400000000000000000000002701517721000100255640ustar00rootroot00000000000000 socnetv-app-39db829/src/images/cursor-hand-drag.svg000066400000000000000000000065271517721000100222400ustar00rootroot00000000000000 socnetv-app-39db829/src/images/cursor-hand-scroll.svg000066400000000000000000000022451517721000100226120ustar00rootroot00000000000000 socnetv-app-39db829/src/images/cursor-move.svg000066400000000000000000000024351517721000100213530ustar00rootroot00000000000000 socnetv-app-39db829/src/images/cursor-pointer.svg000066400000000000000000000042371517721000100220670ustar00rootroot00000000000000 socnetv-app-39db829/src/images/cursor.svg000066400000000000000000000023541517721000100204070ustar00rootroot00000000000000 socnetv-app-39db829/src/images/data_table_48px.svg000066400000000000000000000006751517721000100220410ustar00rootroot00000000000000 data-table socnetv-app-39db829/src/images/degreematrix.png000066400000000000000000000012011517721000100215250ustar00rootroot00000000000000‰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-app-39db829/src/images/delete.png000066400000000000000000000014771517721000100203260ustar00rootroot00000000000000‰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-app-39db829/src/images/diameter_48px.svg000066400000000000000000000041721517721000100215470ustar00rootroot00000000000000dsocnetv-app-39db829/src/images/diamond.png000066400000000000000000000003601517721000100204650ustar00rootroot00000000000000‰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-app-39db829/src/images/distance.png000066400000000000000000000025201517721000100206440ustar00rootroot00000000000000‰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-app-39db829/src/images/distances.png000066400000000000000000000004341517721000100210310ustar00rootroot00000000000000‰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-app-39db829/src/images/dm.png000066400000000000000000000103201517721000100174470ustar00rootroot00000000000000‰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-app-39db829/src/images/eccentricity.png000066400000000000000000000004401517721000100215360ustar00rootroot00000000000000‰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-app-39db829/src/images/edge_properties_24px.svg000066400000000000000000000012111517721000100231160ustar00rootroot00000000000000 socnetv-app-39db829/src/images/edge_properties_48px.svg000066400000000000000000000035271517721000100231400ustar00rootroot00000000000000 socnetv-app-39db829/src/images/edge_remove_48px.svg000066400000000000000000000007351517721000100222370ustar00rootroot00000000000000socnetv-app-39db829/src/images/edges_48px.svg000066400000000000000000000010441517721000100210370ustar00rootroot00000000000000socnetv-app-39db829/src/images/ego_radial_layout_48px.svg000066400000000000000000000147441517721000100234460ustar00rootroot00000000000000 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-app-39db829/src/images/ellipse.png000066400000000000000000000007401517721000100205110ustar00rootroot00000000000000‰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-app-39db829/src/images/erdos.png000066400000000000000000000034401517721000100201700ustar00rootroot00000000000000‰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-app-39db829/src/images/exit.png000066400000000000000000000027301517721000100200260ustar00rootroot00000000000000‰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-app-39db829/src/images/exit_24px.svg000066400000000000000000000005021517721000100207110ustar00rootroot00000000000000socnetv-app-39db829/src/images/export_pdf_48px.svg000066400000000000000000000006241517721000100221250ustar00rootroot00000000000000socnetv-app-39db829/src/images/export_photo_48px.svg000066400000000000000000000003411517721000100225010ustar00rootroot00000000000000socnetv-app-39db829/src/images/file_download_48px.svg000066400000000000000000000002301517721000100225520ustar00rootroot00000000000000socnetv-app-39db829/src/images/file_download_48px_notsaved.svg000066400000000000000000000002251517721000100244610ustar00rootroot00000000000000socnetv-app-39db829/src/images/file_upload_48px.svg000066400000000000000000000002171517721000100222340ustar00rootroot00000000000000socnetv-app-39db829/src/images/filter_attribute_48px.svg000066400000000000000000000021261517721000100233220ustar00rootroot00000000000000 Filter by attribute socnetv-app-39db829/src/images/filter_centrality_48px.svg000066400000000000000000000023561517721000100235020ustar00rootroot00000000000000 Filter by centrality socnetv-app-39db829/src/images/filter_close_48.svg000066400000000000000000000011061517721000100220510ustar00rootroot00000000000000socnetv-app-39db829/src/images/filter_edges_48px.svg000066400000000000000000000027561517721000100224170ustar00rootroot00000000000000 Filter edges socnetv-app-39db829/src/images/filter_list_48px.svg000066400000000000000000000002271517721000100222720ustar00rootroot00000000000000socnetv-app-39db829/src/images/filter_restore_edges_48px.svg000066400000000000000000000023011517721000100241440ustar00rootroot00000000000000 Restore all filtered edges socnetv-app-39db829/src/images/filter_restore_nodes_48px.svg000066400000000000000000000020531517721000100241710ustar00rootroot00000000000000 Restore all filtered nodes socnetv-app-39db829/src/images/force.png000066400000000000000000000034171517721000100201560ustar00rootroot00000000000000‰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-app-39db829/src/images/format_color_fill_48px.svg000066400000000000000000000006231517721000100234460ustar00rootroot00000000000000socnetv-app-39db829/src/images/format_color_text_48px.svg000066400000000000000000000003401517721000100235000ustar00rootroot00000000000000socnetv-app-39db829/src/images/format_shapes_48px.svg000066400000000000000000000005451517721000100226100ustar00rootroot00000000000000socnetv-app-39db829/src/images/format_textsize_48px.svg000066400000000000000000000002311517721000100231740ustar00rootroot00000000000000socnetv-app-39db829/src/images/fullscreen_48px.svg000066400000000000000000000003001517721000100221040ustar00rootroot00000000000000socnetv-app-39db829/src/images/gridlines.png000066400000000000000000000020341517721000100210320ustar00rootroot00000000000000‰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-app-39db829/src/images/hand-pointer.svg000066400000000000000000000032731517721000100214630ustar00rootroot00000000000000 socnetv-app-39db829/src/images/heart.svg000066400000000000000000000046751517721000100202050ustar00rootroot00000000000000socnetv-app-39db829/src/images/help-hint.png000066400000000000000000000031511517721000100207430ustar00rootroot00000000000000‰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-app-39db829/src/images/help_48px.svg000066400000000000000000000006001517721000100206750ustar00rootroot00000000000000socnetv-app-39db829/src/images/hierarchical.png000066400000000000000000000002161517721000100214700ustar00rootroot00000000000000‰PNG  IHDR szzτUIDATX…νΣ; DAξi<€…λΚΖζ‘X@γ„OαG /ΈΙ|DχΒι Μ38ՌΤϋœΰΜϋ°ύεΚ':τιMΟ1ueύIENDB`‚socnetv-app-39db829/src/images/invertmatrix.png000066400000000000000000000012061517721000100216060ustar00rootroot00000000000000‰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-app-39db829/src/images/laplacian.png000066400000000000000000000005131517721000100207760ustar00rootroot00000000000000‰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-app-39db829/src/images/layout_levels_24px.svg000066400000000000000000000002401517721000100226260ustar00rootroot00000000000000socnetv-app-39db829/src/images/line_weight_48px.svg000066400000000000000000000002421517721000100222450ustar00rootroot00000000000000socnetv-app-39db829/src/images/net.png000066400000000000000000000024361517721000100176460ustar00rootroot00000000000000‰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-app-39db829/src/images/net1.png000066400000000000000000000026131517721000100177240ustar00rootroot00000000000000‰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-app-39db829/src/images/network_three_48px.svg000066400000000000000000000007351517721000100226360ustar00rootroot00000000000000socnetv-app-39db829/src/images/new.png000066400000000000000000000015241517721000100176460ustar00rootroot00000000000000‰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-app-39db829/src/images/new_folder_48px.svg000066400000000000000000000003651517721000100221010ustar00rootroot00000000000000socnetv-app-39db829/src/images/node_48px.svg000066400000000000000000000002411517721000100206730ustar00rootroot00000000000000socnetv-app-39db829/src/images/node_add_48px.svg000066400000000000000000000004211517721000100215030ustar00rootroot00000000000000socnetv-app-39db829/src/images/node_add_outline_48px.svg000066400000000000000000000004231517721000100232440ustar00rootroot00000000000000socnetv-app-39db829/src/images/node_filter_48px.svg000066400000000000000000000005761517721000100222530ustar00rootroot00000000000000 socnetv-app-39db829/src/images/node_properties_24px.svg000066400000000000000000000010671517721000100231500ustar00rootroot00000000000000socnetv-app-39db829/src/images/node_remove_48px.svg000066400000000000000000000002731517721000100222550ustar00rootroot00000000000000socnetv-app-39db829/src/images/node_size_48px.svg000066400000000000000000000005761517721000100217400ustar00rootroot00000000000000socnetv-app-39db829/src/images/nodenumbersize_48px.svg000066400000000000000000000017221517721000100230040ustar00rootroot0000000000000044socnetv-app-39db829/src/images/open.png000066400000000000000000000040311517721000100200120ustar00rootroot00000000000000‰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-app-39db829/src/images/open_48px.svg000066400000000000000000000003411517721000100207100ustar00rootroot00000000000000socnetv-app-39db829/src/images/pdf.png000066400000000000000000000100661517721000100176270ustar00rootroot00000000000000‰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-app-39db829/src/images/person.svg000066400000000000000000000117271517721000100204040ustar00rootroot00000000000000socnetv-app-39db829/src/images/petersengraph.png000066400000000000000000000042261517721000100217260ustar00rootroot00000000000000‰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-app-39db829/src/images/print.png000066400000000000000000000033041517721000100202070ustar00rootroot00000000000000‰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-app-39db829/src/images/print_48px.svg000066400000000000000000000004071517721000100211060ustar00rootroot00000000000000socnetv-app-39db829/src/images/qt.png000066400000000000000000000047161517721000100175070ustar00rootroot00000000000000‰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-app-39db829/src/images/random.png000066400000000000000000000145061517721000100203410ustar00rootroot00000000000000‰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-app-39db829/src/images/recent_48px.svg000066400000000000000000000005471517721000100212370ustar00rootroot00000000000000socnetv-app-39db829/src/images/refresh_48px.svg000066400000000000000000000005431517721000100214110ustar00rootroot00000000000000socnetv-app-39db829/src/images/relation_edit_48px.svg000066400000000000000000000003641517721000100225760ustar00rootroot00000000000000socnetv-app-39db829/src/images/remove-old.png000066400000000000000000000040611517721000100211250ustar00rootroot00000000000000‰PNG  IHDRΔ΄l;sRGBΞιbKGD ½§“ pHYs νΐ,tIMEΨ#`ŸΠv±IDAT8¦Yψ 6Q 6Q  6Q 6QcdgώtijpU@εϋϋCg ΊυμΰCg§˜πρυξπυαημ›‘Ύšωωϋ©ΊΜφχωϋϊόμοσωϊόΟΦβϋϋόBfΛ±όσιϊχύριώώω VmήάύαέχΖόύφώωςύέΨ*σρυτϊαΣύώωςόΨΡϋβΥύόφλχΜ·ύ5P‹7n2Χ½IENDB`‚socnetv-app-39db829/src/images/resize.svg000066400000000000000000000003311517721000100203640ustar00rootroot00000000000000socnetv-app-39db829/src/images/rotate_left_48px.svg000066400000000000000000000007421517721000100222640ustar00rootroot00000000000000socnetv-app-39db829/src/images/rotate_right_48px.svg000066400000000000000000000007411517721000100224460ustar00rootroot00000000000000socnetv-app-39db829/src/images/save.png000066400000000000000000000022431517721000100200120ustar00rootroot00000000000000‰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-app-39db829/src/images/saveas.png000066400000000000000000000021611517721000100203350ustar00rootroot00000000000000‰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-app-39db829/src/images/science_48px.svg000066400000000000000000000002671517721000100213670ustar00rootroot00000000000000socnetv-app-39db829/src/images/search_48px.svg000066400000000000000000000004751517721000100212240ustar00rootroot00000000000000socnetv-app-39db829/src/images/select_all_48px.svg000066400000000000000000000006711517721000100220640ustar00rootroot00000000000000socnetv-app-39db829/src/images/select_none_48px.svg000066400000000000000000000005161517721000100222510ustar00rootroot00000000000000socnetv-app-39db829/src/images/settings_48px.svg000066400000000000000000000014231517721000100216110ustar00rootroot00000000000000socnetv-app-39db829/src/images/similarity.png000066400000000000000000000036221517721000100212440ustar00rootroot00000000000000‰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-app-39db829/src/images/size_select_24px.svg000066400000000000000000000005571517721000100222630ustar00rootroot00000000000000socnetv-app-39db829/src/images/sociomatrix_48px.svg000066400000000000000000000051031517721000100223110ustar00rootroot00000000000000socnetv-app-39db829/src/images/socnetv-logo.png000066400000000000000000000065741517721000100215060ustar00rootroot00000000000000‰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-app-39db829/src/images/socnetv.icns000066400000000000000000005523611517721000100207200ustar00rootroot00000000000000icnsΤρ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-app-39db829/src/images/socnetv.ico000066400000000000000000000400561517721000100205270ustar00rootroot00000000000000@> @(@| 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-app-39db829/src/images/socnetv.png000066400000000000000000000200411517721000100205310ustar00rootroot00000000000000‰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-app-39db829/src/images/socnetv.xpm000066400000000000000000000036251517721000100205620ustar00rootroot00000000000000/* 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-app-39db829/src/images/socnetv_logo_trans_128px.svg000066400000000000000000000174161517721000100237510ustar00rootroot00000000000000 socnetv-app-39db829/src/images/socnetv_logo_trans_64px.svg000066400000000000000000000177031517721000100236670ustar00rootroot00000000000000 socnetv-app-39db829/src/images/socnetv_logo_white_bg_128px.svg000066400000000000000000000073621517721000100244110ustar00rootroot00000000000000 socnetv-app-39db829/src/images/star.png000066400000000000000000000012761517721000100200320ustar00rootroot00000000000000‰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-app-39db829/src/images/subgraphline.png000066400000000000000000000002151517721000100215340ustar00rootroot00000000000000‰PNG  IHDR szzτTIDATX…ν“I Δό§λ΅ŠJ7ΤCs‘jkπ’rΈ¨%"ΒΛΞ0ά(ήuΌiαΉs:\Ζ{ί"]φšLΗ³T₯;₯μ<₯ΐ#ΈH7@5μIENDB`‚socnetv-app-39db829/src/images/subgraphstar_128px.svg000066400000000000000000000036261517721000100225440ustar00rootroot00000000000000 socnetv-app-39db829/src/images/svg.png000066400000000000000000000103031517721000100176470ustar00rootroot00000000000000‰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-app-39db829/src/images/sw.png000066400000000000000000000102531517721000100175050ustar00rootroot00000000000000‰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-app-39db829/src/images/symmetrize.png000066400000000000000000000015041517721000100212630ustar00rootroot00000000000000‰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-app-39db829/src/images/symmetry-edge.png000066400000000000000000000002051517721000100216430ustar00rootroot00000000000000‰PNG  IHDR szzτLIDATX…νΣA DQοιiŸ¨›ΤA›Μž6{k @Ψ_UΑΓn†²Η¦Š.§βέ4Hη¬ώ‚» ;@YΤπ—ΰ‡yNΌ“UIENDB`‚socnetv-app-39db829/src/images/symmetry_48px.svg000066400000000000000000000042071517721000100216450ustar00rootroot00000000000000 socnetv-app-39db829/src/images/system_update_alt_48px.svg000066400000000000000000000004021517721000100234730ustar00rootroot00000000000000socnetv-app-39db829/src/images/text_edit_48px.svg000066400000000000000000000002711517721000100217420ustar00rootroot00000000000000socnetv-app-39db829/src/images/tip_24px.svg000066400000000000000000000025061517721000100205420ustar00rootroot00000000000000socnetv-app-39db829/src/images/transposematrix.png000066400000000000000000000011611517721000100223150ustar00rootroot00000000000000‰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-app-39db829/src/images/triad.png000066400000000000000000000031531517721000100201600ustar00rootroot00000000000000‰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-app-39db829/src/images/triadcensus.png000066400000000000000000000022041517721000100213750ustar00rootroot00000000000000‰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-app-39db829/src/images/walk.png000066400000000000000000000016371517721000100200200ustar00rootroot00000000000000‰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-app-39db829/src/images/wallpaper_48px.svg000066400000000000000000000005411517721000100217400ustar00rootroot00000000000000socnetv-app-39db829/src/images/webcrawler_48px.svg000066400000000000000000000044441517721000100221140ustar00rootroot00000000000000 socnetv-app-39db829/src/images/zoom_in_24px.svg000066400000000000000000000005601517721000100214160ustar00rootroot00000000000000socnetv-app-39db829/src/images/zoom_out_24px.svg000066400000000000000000000005321517721000100216160ustar00rootroot00000000000000socnetv-app-39db829/src/main.cpp000077500000000000000000000127671517721000100165500ustar00rootroot00000000000000/** * @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); // 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; } } // // Create our MainWindow and exec the app to enter the main event loop. // MainWindow *socnetv = new MainWindow(fileName, showProgress, showMaximized, showFullScreen, debugLevel); // Show the application socnetv->show(); return app.exec(); } socnetv-app-39db829/src/mainwindow.cpp000077500000000000000000023223131517721000100177710ustar00rootroot00000000000000/** * @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 // 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/dialogfilternodesbycentrality.h" #include "forms/dialogfilterbyattribute.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" /** * @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) { qDebug() << "=========== MainWindow (MW) constructor starting on thread:"<< thread(); // // Setup debug messages/level // switch (debugLevel) { case 0: // Debugging disabled by command line parameter // Set messages pattern to trivial qSetMessagePattern(""); // 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; default: // No debug parameter -- set message pattern to bare minimum qSetMessagePattern("%{time} 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; } qDebug() << "primaryScreen: "<setFocus(); // // Load user-provided network file, if any // qDebug() << "Checking if user provided file on startup..."; if (!m_fileName.isEmpty()) { qDebug() << "Loading user provided file" << m_fileName; slotNetworkFileChoose( m_fileName ); } QString welcomeMsg = tr("Welcome to %1, version %2").arg(qApp->applicationName(), VERSION); statusMessage( welcomeMsg ); qDebug() << "@@@@ MW Constructor finished, on thread:" << thread(); } /** * @brief Deletes variables on MW closing */ MainWindow::~MainWindow() { qDebug() << "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(); qDebug() << "Destruct function finished - bye!"; } /** * @brief Called when the application closes. Asks to write any unsaved network data. * @param ce */ void MainWindow::closeEvent( QCloseEvent* ce ) { // // Show a status message // qDebug() << "Received close event. Show a status message to user..."; statusMessage( tr("Closing SocNetV. Bye!") ); // // Check if the graph has been saved // bool userCancelled=false; qDebug() << "Checking if current graph is saved..."; if ( activeGraph->isSaved() ) { ce->accept(); qDebug() << "Graph is already saved. Nothing to do."; } else { qDebug() << "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) { qDebug() << "User canceled (while saving graph). Returning without closing the app."; return; } // // Terminate running threads // qDebug() << "I will terminate any running threads..."; terminateThreads("closeEvent()"); // // Delete other objects and pointers // qDebug() << "Deleting other objects and pointers..."; qDebug() << "Deleting printer"; delete printer; qDebug() << "Deleting printerPDF"; delete printerPDF; qDebug() << "Deleting graphicsWidget"; delete graphicsWidget; qDebug() << "Deleting activeGraph"; delete activeGraph; qDebug() << "Deleting Scene"; delete scene; // delete miniChart; qDebug() << "Clearing and deleting text editors..."; foreach ( TextEditor *ed, m_textEditors) { ed->close(); delete ed; } m_textEditors.clear(); qDebug() <<" Checking if networkManager thread is running..."; if (networkManager->thread()->isRunning()) { qDebug() << "networkManager thread running" << "Calling deleteLater();"; networkManager->deleteLater(); } delete editNodePropertiesAct; delete editNodeRemoveAct; qDebug() << "Clearing codecs..."; codecs.clear(); qDebug() << "Finished. Bye!"; } /** * @brief Terminates any remaining threads. * * @param reason */ void MainWindow::terminateThreads(const QString &reason) { qDebug() << "Terminating threads (those started from MW). Reason:" << reason <<" Checking if graphThread is running..."; if (graphThread.isRunning() ) { qDebug() << "graphThread running." << "Calling graphThread.quit();"; graphThread.quit(); qDebug() << "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(); // qDebug () << "MW resized:" << w0 << "x" << h0 // << "-->" << w << "x" << h; // statusMessage( // tr("Window resized to (%1, %2)px.") // .arg(w).arg(h) // ); } /** * @brief Reads user-defined settings (or uses defaults) and initializes some app settings */ QMap MainWindow::initSettings(const int &debugLevel, const bool &forceProgress) { qDebug() << "Initializing settings - debugLevel"< 0 ) { appSettings["printDebug"] = "true"; } else if (debugLevel==0) { appSettings["printDebug"] = "false"; slotOptionsDebugMessages(false); } else { // do not override appSettings["printDebug"] } 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 // qDebug() << "initializing text codecs list.." ; initNetworkAvailableTextCodecs(); qDebug() << "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() { qDebug() << "Saving app settings to file: "<< settingsFilePath; QFile file(settingsFilePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text ) ) { qDebug () << "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); qDebug()<< "Writing settings to settings file first "; QMap::const_iterator it = appSettings.constBegin(); while (it != appSettings.constEnd()) { // qDebug() << " setting: " << it.key() << " = " << it.value(); 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 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::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::setEdgeArrowsVisibility, this, &MainWindow::slotOptionsEdgeArrowsVisibility); 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); // show settings dialog m_settingsDialog->exec(); } /** * @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() { qDebug()<< "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.

") ); qDebug() << "Finished initialization of graphics widget. Dimensions:" << graphicsWidget->width() << "x" << graphicsWidget->height(); } /** * @brief Initializes the Graph */ void MainWindow::initGraph() { qDebug() << "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); qDebug() << "activeGraph created on thread:" << activeGraph->getThread() << "moving it to new thread "; activeGraph->moveToThreadFacade(&graphThread); qDebug() << "activeGraph moved to thread:" << activeGraph->getThread() << "starting new activeGraph thread..."; graphThread.start(); qDebug() << "activeGraph thread now:" << activeGraph->getThread() << "Finished initialization of graph."; } /** * @brief Initializes all QActions of the application * * Take a breath, the listing below is HUGE. * */ void MainWindow::initActions(){ qDebug()<< "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())); 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())); 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 session)"), this); editNodeColorAll->setStatusTip(tr("Choose a new color for all nodes (in this session only).")); editNodeColorAll->setWhatsThis(tr("Nodes Color\n\n" "Changes all nodes color at once. \n" "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); connect(editNodeColorAll, SIGNAL(triggered()), this, SLOT(slotEditNodeColorAll()) ); editNodeSizeAllAct = new QAction(QIcon(":/images/size_select_24px.svg"), tr("Change All Nodes Size (this session)"), this); editNodeSizeAllAct->setStatusTip(tr("Change the size of all nodes (in this session only)")); editNodeSizeAllAct->setWhatsThis(tr("Change All Nodes Size\n\n" "Click to select and apply a new size for all nodes at once. \n" "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); connect(editNodeSizeAllAct, SIGNAL(triggered()), this, SLOT(slotEditNodeSizeAll()) ); editNodeShapeAll = new QAction(QIcon(":/images/format_shapes_48px.svg"), tr("Change All Nodes Shape (this session)"), this); editNodeShapeAll->setStatusTip(tr("Change the shape of all nodes (this session only)")); editNodeShapeAll->setWhatsThis(tr("Change All Nodes Shape\n\n" "Click to select and apply a new shape for all nodes at once." "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); connect(editNodeShapeAll, SIGNAL(triggered()), this, SLOT(slotEditNodeShape()) ); editNodeNumbersSizeAct = new QAction(QIcon(":/images/nodenumbersize_48px.svg"), tr("Change All Node Numbers Size (this session)"), this); editNodeNumbersSizeAct->setStatusTip(tr("Change the font size of the numbers of all nodes" "(in this session only)")); editNodeNumbersSizeAct->setWhatsThis(tr("Change Node Numbers Size\n\n" "Click to select and apply a new font size for all node numbers" "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); connect(editNodeNumbersSizeAct, SIGNAL(triggered()), this, SLOT( slotEditNodeNumberSize()) ); editNodeNumbersColorAct = new QAction(QIcon(":/images/format_color_text_48px.svg"), tr("Change All Node Numbers Color (this session)"), this); editNodeNumbersColorAct->setStatusTip(tr("Change the color of the numbers of all nodes." "(in this session only)")); editNodeNumbersColorAct->setWhatsThis(tr("Node Numbers Color\n\n" "Click to select and apply a new color " "to all node numbers." "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); connect(editNodeNumbersColorAct, SIGNAL(triggered()), this, SLOT(slotEditNodeNumbersColor())); editNodeLabelsSizeAct = new QAction(QIcon(":/images/format_textsize_48px.svg"), tr("Change All Node Labels Size (this session)"), this); editNodeLabelsSizeAct->setStatusTip(tr("Change the font size of the labels of all nodes" "(this session only)")); editNodeLabelsSizeAct->setWhatsThis(tr("Node Labels Size\n\n" "Click to select and apply a new font-size to all node labels" "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); connect(editNodeLabelsSizeAct, SIGNAL(triggered()), this, SLOT(slotEditNodeLabelSize()) ); editNodeLabelsColorAct = new QAction(QIcon(":/images/format_color_text_48px.svg"), tr("Change All Node Labels Color (this session)"), this); editNodeLabelsColorAct->setStatusTip(tr("Change the color of the labels of all nodes " "(for this session only)")); editNodeLabelsColorAct->setWhatsThis(tr("Labels Color\n\n" "Click to select and apply a new color to all node labels." "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); 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())); filterNodesBySelectionAct = new QAction(tr("Focus on Selection"), this); filterNodesBySelectionAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_X, Qt::CTRL | Qt::Key_S)); 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))); /** 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_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_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_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_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())); 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())); 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())); 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())); 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 (this session only)")); optionsNodeNumbersVisibilityAct->setWhatsThis( tr("Display Node Numbers\n\n" "Enables or disables displaying of node numbers\n" "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); 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 (this session only)")); optionsNodeNumbersInsideAct->setWhatsThis( tr("Display Numbers Inside Nodes\n\n" "Enables or disables displaying node numbers inside nodes.\n" "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); 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 (this session only)")); optionsNodeLabelsVisibilityAct->setWhatsThis( tr("Display Node Labels\n\n" "Enables or disables node labels.\n" "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); 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 (this session only)")); optionsEdgesVisibilityAct->setWhatsThis( tr("Display Edges\n\n" "Enables or disables displaying of edges" "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); 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 (this session only)")); optionsEdgeWeightNumbersAct->setWhatsThis( tr("Display Edge Weights\n\n" "Enables or disables displaying edge weight numbers.\n" "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); 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 (this session only)")); optionsEdgeLabelsAct->setWhatsThis( tr("Display Edge Labes\n\n" "Enables or disables displaying edge labels.\n" "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); 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 (this session only)")); 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).\n" "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); 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" "Enable or disables drawing Edges as Bezier curves." "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); drawEdgesBezier->setCheckable(true); drawEdgesBezier->setChecked ( (appSettings["initEdgeShape"]=="bezier") ? true: false ); drawEdgesBezier->setEnabled(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 session)"), this); backgroundImageAct->setStatusTip( tr("Select and display a custom image in the background" "(for this session only)")); 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." "This setting will apply to this session only. \n" "To permanently change it, go to Settings.")); 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_T)); viewDataTableAct->setStatusTip(tr("Show/hide the node and edge data table panel")); viewDataTableAct->setToolTip(tr("Toggle the node/edge data table panel (Ctrl+T)")); 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() ) ); qDebug()<< "Finished actions initialization."; } /** * @brief Populates the menu bar with our menu items. */ void MainWindow::initMenuBar() { qDebug()<< "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 (networkExportList); //exportSubMenu->addAction (networkExportDL); //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->addSeparator(); // β€” Restore β€” filterMenu->addAction(filterNodesRestoreAllAct); filterMenu->addAction(editFilterEdgesRestoreAllAct); 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->addSeparator(); cohesionMenu->addAction (analyzeMatrixDistancesGeodesicAct); cohesionMenu->addAction (analyzeMatrixGeodesicsAct); cohesionMenu->addSeparator(); cohesionMenu->addAction (analyzeGraphEccentricityAct); cohesionMenu->addAction (analyzeGraphDiameterAct); cohesionMenu->addSeparator(); cohesionMenu->addAction(analyzeGraphConnectednessAct); 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->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); 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); 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); 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); 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); qDebug()<< "Finished menu bar init."; } /** * @brief Initializes the toolbar */ void MainWindow::initToolBar(){ qDebug()<< "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->addSeparator(); // QLabel *labelEditEdges= new QLabel; // labelEditEdges->setText(tr("Edges:")); // toolBar->addWidget (labelEditEdges); toolBar->addAction (editEdgeAddAct); toolBar->addAction (editEdgeRemoveAct); toolBar->addAction(editEdgePropertiesAct); toolBar->addSeparator(); // β€” Filter group β€” toolBar->addAction(filterNodesByCentralityAct); toolBar->addAction(filterNodesByAttributeAct); toolBar->addAction(editFilterEdgesByWeightAct); toolBar->addAction(filterNodesRestoreAllAct); 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)); qDebug()<< "Finished toolbar init."; } /** * @brief Creates docked panels for instant access to main app functionalities * and displaying statistics */ void MainWindow::initPanels(){ qDebug()<< "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(160); //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.

" "

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" << "Distances Matrix" << "Geodesics Matrix" << "Eccentricity" << "Diameter" << "Connectedness" << "Walks of given length" << "Total Walks" << "Reachability Matrix" << "Clustering Coefficient"; 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" << "Degree Prestige" << "PageRank Prestige" << "Proximity Prestige"; QStringList prominenceCommands; prominenceCommands << "Select" << prominenceIndexList; 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(180); 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->setMinimumHeight(20); 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 << "Radial" << "On Levels" << "Node Size"<< "Node Color"; toolBoxLayoutByIndexTypeSelect->addItems(layoutTypes); toolBoxLayoutByIndexTypeSelect->setMinimumHeight(20); toolBoxLayoutByIndexTypeSelect->setMinimumWidth(100); toolBoxLayoutByIndexApplyButton = new QPushButton(tr("Apply")); toolBoxLayoutByIndexApplyButton->setObjectName ("toolBoxLayoutByIndexApplyButton"); toolBoxLayoutByIndexApplyButton->setFocusPolicy(Qt::NoFocus); toolBoxLayoutByIndexApplyButton->setMinimumHeight(20); toolBoxLayoutByIndexApplyButton->setMaximumWidth(60); //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->addWidget(toolBoxLayoutByIndexApplyButton, 2,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(120); 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->setMinimumHeight(20); 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 ); toolBoxLayoutForceDirectedApplyButton = new QPushButton(tr("Apply")); toolBoxLayoutForceDirectedApplyButton->setObjectName ("toolBoxLayoutForceDirectedApplyButton"); toolBoxLayoutForceDirectedApplyButton->setFocusPolicy(Qt::NoFocus); toolBoxLayoutForceDirectedApplyButton->setMinimumHeight(20); toolBoxLayoutForceDirectedApplyButton->setMaximumWidth(60); //create layout for dynamic visualisation QGridLayout *layoutForceDirectedGrid = new QGridLayout(); layoutForceDirectedGrid->addWidget(toolBoxLayoutForceDirectedSelectLabel, 0,0); layoutForceDirectedGrid->addWidget(toolBoxLayoutForceDirectedSelect, 0,1); layoutForceDirectedGrid->addWidget(toolBoxLayoutForceDirectedApplyButton, 1,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 // QLabel *rightPanelNetworkHeader = new QLabel; QFont labelFont = rightPanelNetworkHeader->font(); labelFont.setWeight(QFont::Bold); rightPanelNetworkHeader->setText (tr("Network")); rightPanelNetworkHeader->setFont(labelFont); QLabel *rightPanelNetworkTypeLabel = new QLabel; rightPanelNetworkTypeLabel->setText ("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")); rightPanelNetworkTypeLabel->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" "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.")); 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" "toggle the option Edit->Edges->Undirected")); rightPanelNetworkTypeLCD->setMinimumWidth(75); QLabel *rightPanelNodesLabel = new QLabel; rightPanelNodesLabel->setText(tr("Nodes:")); rightPanelNodesLabel->setStatusTip( tr("Each actor in a social netwok is visualized as a node (aka vertex).")); rightPanelNodesLabel->setToolTip( tr("

Nodes

" "

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.

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

Edges

" "Each link between a pair of actors in a social network is visualized as an undirected edge or a directed edge (aka arc)." ) ); rightPanelEdgesLCD=new QLabel; rightPanelEdgesLCD->setAlignment(Qt::AlignRight); rightPanelEdgesLCD->setStatusTip(tr("The total number of directed edges in the social network.")); rightPanelEdgesLCD->setToolTip(tr("This is the total number of directed edges \n" "(links between actors) in the social network.")); QLabel *rightPanelDensityLabel = new QLabel; rightPanelDensityLabel->setText(tr("Density:")); rightPanelDensityLabel->setStatusTip(tr("The density d is the ratio of existing edges to all possible edges")); helpMessage = tr("

Density

" "

The density d of a social network is the ratio of " "existing edges to all possible edges ( n*(n-1) ) between the " "nodes of the network

."); rightPanelDensityLabel->setToolTip( helpMessage ); rightPanelDensityLabel->setWhatsThis( helpMessage ); rightPanelDensityLCD=new QLabel; rightPanelDensityLCD->setAlignment(Qt::AlignRight); rightPanelDensityLCD->setStatusTip(tr("The network density, the ratio of existing " "edges to all possible edges ( n*(n-1) ) between nodes.")); rightPanelDensityLCD->setToolTip( tr("

This is the density of the network. " "

The density of a network is the ratio of existing " "edges to all possible edges ( n*(n-1) ) between nodes.

")); QLabel *verticalSpaceLabel1 = new QLabel; verticalSpaceLabel1->setText (""); QLabel *rightPanelSelectedHeaderLabel = new QLabel; rightPanelSelectedHeaderLabel->setText (tr("Selection")); rightPanelSelectedHeaderLabel->setFont(labelFont); QLabel *rightPanelSelectedNodesLabel = new QLabel; rightPanelSelectedNodesLabel->setText(tr("Nodes:")); rightPanelSelectedNodesLabel->setStatusTip(tr("Selected nodes.")); rightPanelSelectedNodesLabel->setToolTip(tr("Selected nodes.")); rightPanelSelectedNodesLCD=new QLabel; rightPanelSelectedNodesLCD->setAlignment(Qt::AlignRight); rightPanelSelectedNodesLCD->setText("0"); rightPanelSelectedNodesLCD->setStatusTip(tr("The number of selected nodes (vertices).")); rightPanelSelectedNodesLCD->setToolTip(tr("The number of selected nodes (vertices).")); rightPanelSelectedEdgesLabel = new QLabel; rightPanelSelectedEdgesLabel->setText(tr("Arcs:")); rightPanelSelectedEdgesLabel->setStatusTip(tr("Selected edges.")); rightPanelSelectedEdgesLabel->setToolTip(tr("Selected edges.")); rightPanelSelectedEdgesLCD=new QLabel; rightPanelSelectedEdgesLCD->setText("0"); rightPanelSelectedEdgesLCD->setAlignment(Qt::AlignRight); rightPanelSelectedEdgesLCD->setStatusTip(tr("The number of selected edges.")); rightPanelSelectedEdgesLCD->setToolTip(tr("The number of selected edges.")); QLabel *verticalSpaceLabel2 = new QLabel; verticalSpaceLabel2->setText (""); rightPanelClickedNodeHeaderLabel = new QLabel; rightPanelClickedNodeHeaderLabel->setText (tr("Clicked Node")); rightPanelClickedNodeHeaderLabel->setFont(labelFont); QLabel *rightPanelClickedNodeLabel = new QLabel; rightPanelClickedNodeLabel->setText (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("This is the node number of the last clicked node. \n" "Becomes zero when you click on something other than a node.")); rightPanelClickedNodeLCD->setStatusTip( tr("The node number of the last clicked node. Zero if you clicked something else.")); QLabel *rightPanelClickedNodeInDegreeLabel = new QLabel; rightPanelClickedNodeInDegreeLabel->setText (tr("In-Degree:")); rightPanelClickedNodeInDegreeLabel->setToolTip (tr("The inDegree of a node is the sum of all inbound edge weights.")); rightPanelClickedNodeInDegreeLabel->setStatusTip (tr("The inDegree of a node is the sum of all inbound edge weights.")); rightPanelClickedNodeInDegreeLCD = new QLabel; rightPanelClickedNodeInDegreeLCD->setAlignment(Qt::AlignRight); rightPanelClickedNodeInDegreeLCD->setStatusTip (tr("The sum of all inbound edge weights of the last clicked node. " "Zero if you clicked something else.")); rightPanelClickedNodeInDegreeLCD->setToolTip (tr("This is the sum of all inbound edge weights of last clicked node. \n" "Becomes zero when you click on something other than a node.")); QLabel *rightPanelClickedNodeOutDegreeLabel = new QLabel; rightPanelClickedNodeOutDegreeLabel->setText (tr("Out-Degree:")); rightPanelClickedNodeOutDegreeLabel->setToolTip (tr("The outDegree of a node is the sum of all outbound edge weights.")); rightPanelClickedNodeOutDegreeLabel->setStatusTip (tr("The outDegree of a node is the sum of all outbound edge weights.")); rightPanelClickedNodeOutDegreeLCD=new QLabel; rightPanelClickedNodeOutDegreeLCD->setAlignment(Qt::AlignRight); rightPanelClickedNodeOutDegreeLCD->setStatusTip (tr("The sum of all outbound edge weights of the last clicked node. " "Zero if you clicked something else.")); rightPanelClickedNodeOutDegreeLCD->setToolTip (tr("This is the sum of all outbound edge weights of the last clicked node. \n" "Becomes zero when you click on something other than a node.")); QLabel *verticalSpaceLabel3 = new QLabel; verticalSpaceLabel3->setText (""); QLabel * rightPanelClickedEdgeHeaderLabel = new QLabel; rightPanelClickedEdgeHeaderLabel->setText (tr("Clicked Edge")); rightPanelClickedEdgeHeaderLabel->setFont(labelFont); rightPanelClickedEdgeNameLabel = new QLabel; rightPanelClickedEdgeNameLabel->setText (tr("Name:")); rightPanelClickedEdgeNameLabel->setToolTip (tr("The name of the last clicked edge.")); rightPanelClickedEdgeNameLabel->setStatusTip (tr("The name of the last clicked edge.")); rightPanelClickedEdgeNameLCD = new QLabel; rightPanelClickedEdgeNameLCD->setAlignment(Qt::AlignRight); rightPanelClickedEdgeNameLCD->setToolTip (tr("This is the name of the last clicked edge. \n" "Becomes zero when you click on somethingto other than an edge")); rightPanelClickedEdgeNameLCD->setStatusTip (tr("The name of the last clicked edge." "Zero when you click on something else.")); rightPanelClickedEdgeWeightLabel = new QLabel; rightPanelClickedEdgeWeightLabel->setText (tr("Weight:")); rightPanelClickedEdgeWeightLabel->setStatusTip (tr("The weight of the clicked edge.")); rightPanelClickedEdgeWeightLabel->setToolTip (tr("The weight of the clicked edge.")); rightPanelClickedEdgeWeightLCD =new QLabel; rightPanelClickedEdgeWeightLCD->setAlignment(Qt::AlignRight); rightPanelClickedEdgeWeightLCD->setToolTip (tr("This is the weight of the last clicked edge. \n" "Becomes zero when you click on something other than an edge")); rightPanelClickedEdgeWeightLCD->setStatusTip (tr("The weight of the last clicked edge. " "Zero when you click on something else.")); rightPanelClickedEdgeReciprocalWeightLabel = new QLabel; rightPanelClickedEdgeReciprocalWeightLabel->setText (tr("")); rightPanelClickedEdgeReciprocalWeightLabel->setToolTip (tr("The weight of the reciprocal edge.")); rightPanelClickedEdgeReciprocalWeightLabel->setStatusTip (tr("The weight of the reciprocal edge.")); rightPanelClickedEdgeReciprocalWeightLCD =new QLabel; rightPanelClickedEdgeReciprocalWeightLCD->setAlignment(Qt::AlignRight); rightPanelClickedEdgeReciprocalWeightLCD->setToolTip (tr("This is the reciprocal weight of the last clicked reciprocated edge. \n" "Becomes zero when you click on something other than an edge")); rightPanelClickedEdgeReciprocalWeightLCD->setStatusTip (tr("The reciprocal weight of the last clicked reciprocated edge. \n" "Becomes zero when you click on something other than an edge")); //create a grid layout QGridLayout *propertiesGrid = new QGridLayout(); propertiesGrid->setColumnMinimumWidth(0, 10); propertiesGrid->setColumnMinimumWidth(1, 10); propertiesGrid->addWidget(rightPanelNetworkHeader , 0,0); propertiesGrid->addWidget(rightPanelNetworkTypeLabel , 1,0); propertiesGrid->addWidget(rightPanelNetworkTypeLCD , 1,1); propertiesGrid->addWidget(rightPanelNodesLabel, 2,0); propertiesGrid->addWidget(rightPanelNodesLCD,2,1); propertiesGrid->addWidget(rightPanelEdgesLabel, 3,0); propertiesGrid->addWidget(rightPanelEdgesLCD,3,1); propertiesGrid->addWidget(rightPanelDensityLabel, 4,0); propertiesGrid->addWidget(rightPanelDensityLCD,4,1); propertiesGrid->addWidget(verticalSpaceLabel1, 5,0); propertiesGrid->addWidget(rightPanelSelectedHeaderLabel, 6,0,1,2); propertiesGrid->addWidget(rightPanelSelectedNodesLabel , 7,0); propertiesGrid->addWidget(rightPanelSelectedNodesLCD ,7,1); propertiesGrid->addWidget(rightPanelSelectedEdgesLabel, 8,0); propertiesGrid->addWidget(rightPanelSelectedEdgesLCD, 8,1); propertiesGrid->addWidget(verticalSpaceLabel2, 9,0); propertiesGrid->addWidget(rightPanelClickedNodeHeaderLabel, 10,0,1,2); propertiesGrid->addWidget(rightPanelClickedNodeLabel , 11,0); propertiesGrid->addWidget(rightPanelClickedNodeLCD ,11,1); propertiesGrid->addWidget(rightPanelClickedNodeInDegreeLabel, 12,0); propertiesGrid->addWidget(rightPanelClickedNodeInDegreeLCD,12,1); propertiesGrid->addWidget(rightPanelClickedNodeOutDegreeLabel, 13,0); propertiesGrid->addWidget(rightPanelClickedNodeOutDegreeLCD,13,1); propertiesGrid->addWidget(verticalSpaceLabel3, 15,0); propertiesGrid->addWidget(rightPanelClickedEdgeHeaderLabel, 16,0,1,2); propertiesGrid->addWidget(rightPanelClickedEdgeNameLabel , 17,0); propertiesGrid->addWidget(rightPanelClickedEdgeNameLCD ,17,1); propertiesGrid->addWidget(rightPanelClickedEdgeWeightLabel , 18,0); propertiesGrid->addWidget(rightPanelClickedEdgeWeightLCD ,18,1); propertiesGrid->addWidget(rightPanelClickedEdgeReciprocalWeightLabel , 19,0); propertiesGrid->addWidget(rightPanelClickedEdgeReciprocalWeightLCD ,19,1); // Create our mini miniChart miniChart = new Chart(this); int chartHeight = 140; miniChart->setThemeSmallWidget(chartHeight,chartHeight); // Nothing else to do with miniChart. // MW::initApp() will populate it with a dummy point. propertiesGrid->addWidget(miniChart,20,0,1,2); propertiesGrid->setRowMinimumHeight(20, (int) floor( 1.5 * chartHeight ) ); propertiesGrid->setRowStretch(20,0); // We need some margin form the edge of the miniChart to the messageLabel below, // but setRowStretch is not enough. So, we add a spacer! QSpacerItem *spacer = new QSpacerItem (100, 10, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); propertiesGrid->addItem(spacer, 22,0,3,2); propertiesGrid->setRowStretch(22,1); //allow this row to stretch // Add the message label, this will be displayed in the down-right corner. QLabel *rightPanelMessageLabel = new QLabel; rightPanelMessageLabel->setText ("https://socnetv.org"); propertiesGrid->addWidget(rightPanelMessageLabel, 25, 0, 1, 2); propertiesGrid->setRowStretch(25,0); // stop row from stretching // Create a panel with title rightPanel = new QGroupBox(tr("Statistics Panel")); rightPanel->setMaximumWidth(190); rightPanel->setObjectName("rightPanel"); rightPanel->setLayout (propertiesGrid); qDebug()<< "Finished panels init."; } /** * @brief Initializes the application window layout * * Creates helper widgets and sets the main layout of the MainWindow */ void MainWindow::initWindowLayout() { qDebug() << "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->setShortcut(Qt::SHIFT | Qt::CTRL | Qt::Key_Left); 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->setShortcut(Qt::SHIFT | Qt::CTRL | Qt::Key_Right); 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); // 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(leftPanel, 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_tableDock = new QDockWidget(tr("Data Table"), this); m_tableDock->setObjectName("tableDock"); m_tableDock->setWidget(m_tableWidget); m_tableDock->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea); 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"); } qDebug() << "Finished window layout init."; } /** * @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() { qDebug()<< "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(valueChanged(const int &)), graphicsWidget, SLOT(changeMatrixScale(const int &))); connect( zoomInBtn, SIGNAL(clicked()), graphicsWidget, SLOT( zoomIn() ) ); connect( zoomOutBtn, SIGNAL(clicked()), graphicsWidget, SLOT( zoomOut() ) ); connect( graphicsWidget, SIGNAL(rotationChanged(const int &)), rotateSlider, SLOT( setValue(const int &)) ); 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(m_tableWidget, &GraphTableWidget::nodeSelected, this, [this](int number) { statusMessage(tr("Node %1 selected from data table").arg(number)); }); 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::signalRelationsClear, this, &MainWindow::slotEditRelationsClear ); connect ( activeGraph, &Graph::signalRelationAddToMW, this, &MainWindow::slotEditRelationAdd ); connect ( activeGraph, &Graph::signalRelationRenamedToMW, editRelationChangeCombo, &QComboBox::setCurrentText ); connect ( activeGraph, &Graph::signalProgressBoxCreate, this, &MainWindow::slotProgressBoxCreate); connect ( activeGraph, &Graph::signalProgressBoxKill, this, &MainWindow::slotProgressBoxDestroy); 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::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::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::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](FilterCondition::Scope scope) { if (scope == FilterCondition::Scope::Edges) { activeGraph->edgeFilterReset(); editFilterEdgesRestoreAllAct->setEnabled(false); } else { activeGraph->vertexFilterRestoreAll(); if (activeGraph->visibilityHistoryEmpty()) filterNodesRestoreAllAct->setEnabled(false); } }); connect(m_filterBar, &FilterBarWidget::clearAllRequested, this, [this]() { while (!activeGraph->visibilityHistoryEmpty()) activeGraph->vertexFilterRestoreAll(); filterNodesRestoreAllAct->setEnabled(false); activeGraph->edgeFilterReset(); 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(toolBoxLayoutByIndexApplyButton, SIGNAL (clicked() ), this, SLOT(toolBoxLayoutByIndexApplyBtnPressed() ) ); connect(toolBoxLayoutForceDirectedApplyButton, SIGNAL (clicked() ), 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(){ qDebug()<<"### 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 */ qDebug()<<"### 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->edgeWeightNumbersVisibilitySet( (appSettings["initEdgeWeightNumbersVisibility"] == "true") ? true:false ); activeGraph->setReportsRealNumberPrecision(appSettings["initReportsRealNumberPrecision"].toInt()); activeGraph->setReportsLabelLength(appSettings["initReportsLabelsLength"].toInt()); activeGraph->setReportsChartType(appSettings["initReportsChartType"].toInt()); emit signalSetReportsDataDir(appSettings["dataDir"]); /** Clear graphicsWidget and reset settings and transformations **/ qDebug()<<"### 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 ); 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 **/ qDebug()<<"### Clearing Statistics panel LCDs. Please wait..."; rightPanelClickedNodeInDegreeLCD->setText("-"); rightPanelClickedNodeOutDegreeLCD->setText("-"); rightPanelClickedNodeLCD->setText("-"); rightPanelClickedEdgeNameLCD->setText("-"); rightPanelClickedEdgeWeightLCD->setText("-"); rightPanelClickedEdgeReciprocalWeightLCD->setText(""); /** Clear toolbox and menu checkboxes **/ qDebug()<<"### Resetting toolbox. Please wait..."; toolBoxEditEdgeTransformSelect->setCurrentIndex(0); toolBoxEditEdgeModeSelect->setCurrentIndex(0); initComboBoxes(); toolBoxLayoutByIndexSelect->setCurrentIndex(0); toolBoxLayoutByIndexTypeSelect->setCurrentIndex(0); toolBoxLayoutForceDirectedSelect->setCurrentIndex(0); 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(); qDebug()<<"### Clearing textEditors. Current count: " <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); m_filterBar->clearAllChips(); // Clear the data table if it is visible (graph was reset) if (m_tableWidget && m_tableDock->isVisible()) m_tableWidget->refresh(activeGraph); statusMessage( tr("Ready")); qDebug()<< "#### 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 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 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)); } /** * @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; } /** * @brief Called when user selects something in the Network Auto Create * selectbox of the toolbox * @param selectedIndex */ void MainWindow::toolBoxNetworkAutoCreateSelectChanged(const int &selectedIndex) { qDebug()<< "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; }; qDebug()<< "Calling initComboBoxes() "; initComboBoxes(); } /** * @brief Called when user selects something in the Subgraph from Selected * Nodes selectbox of the toolbox * @param selectedIndex */ void MainWindow::toolBoxEditNodeSubgraphSelectChanged(const int &selectedIndex) { qDebug()<< "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; }; qDebug()<< "Calling initComboBoxes() "; initComboBoxes(); } /** * @brief Called when user selects something in the Edge Transform * selectbox of the toolbox * @param selectedIndex */ void MainWindow::toolBoxEditEdgeTransformSelectChanged(const int &selectedIndex) { qDebug()<< "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 Called when user selects a filter action in the Filter * selectbox of the toolbox Network group * @param selectedIndex */ void MainWindow::toolBoxFilterSelectChanged(const int &selectedIndex) { qDebug()<< "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 Called when user selects something in the Matrices * selectbox of the toolbox * @param selectedIndex */ void MainWindow::toolBoxAnalysisMatricesSelectChanged(const int &selectedIndex) { qDebug()<< "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; }; qDebug()<< "Calling initComboBoxes() "; initComboBoxes(); } /** * @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) { qDebug()<< "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: slotAnalyzeMatrixDistances(); break; case 6: slotAnalyzeMatrixGeodesics(); break; case 7: slotAnalyzeEccentricity(); break; case 8: slotAnalyzeDiameter(); break; case 9: slotAnalyzeConnectedness(); break; case 10: slotAnalyzeWalksLength(); break; case 11: slotAnalyzeWalksTotal(); break; case 12: slotAnalyzeReachabilityMatrix(); break; case 13: slotAnalyzeClusteringCoefficient(); break; }; qDebug()<< "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) { qDebug()<< "selected community analysis, text index: " << selectedIndex; switch(selectedIndex){ case 0: break; case 1: slotAnalyzeCommunitiesCliqueCensus(); break; case 2: slotAnalyzeCommunitiesTriadCensus(); break; }; qDebug()<< "Calling initComboBoxes() "; initComboBoxes(); } /** * @brief Called when the user selects something in the Structural Equivalence * selectbox of the toolbox * @param selectedIndex * */ void MainWindow::toolBoxAnalysisStrEquivalenceSelectChanged(const int &selectedIndex) { qDebug()<< "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; }; qDebug()<< "Calling initComboBoxes() "; initComboBoxes(); } /** * @brief Called when user selects something in the Prominence selectbox * of the toolbox * @param selectedIndex * */ void MainWindow::toolBoxAnalysisProminenceSelectChanged(const int &selectedIndex) { qDebug()<< "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: slotAnalyzePrestigeDegree(); break; case 11: slotAnalyzePrestigePageRank(); break; case 12: slotAnalyzePrestigeProximity(); break; }; qDebug()<< "Calling initComboBoxes() "; initComboBoxes(); } /** * @brief Called when the user selects a Prominence index in the Layout selectbox * of the Control Panel. */ void MainWindow::toolBoxLayoutByIndexApplyBtnPressed(){ qDebug()<<"User request to apply prominence-based layout..."; int selectedIndex = toolBoxLayoutByIndexSelect->currentIndex(); QString selectedIndexText = toolBoxLayoutByIndexSelect->currentText(); int selectedLayoutType = toolBoxLayoutByIndexTypeSelect->currentIndex(); qDebug()<<"elected index is " << selectedIndexText << " : " << selectedIndex << " selected layout type is " << selectedLayoutType; switch(selectedIndex) { case 0: break; case 1: if (selectedLayoutType==0) slotLayoutRadialRandom(); else if (selectedLayoutType==1) slotLayoutRandom(); break; default: if (selectedLayoutType==0) { // radial slotLayoutRadialByProminenceIndex(selectedIndexText); } else if (selectedLayoutType==1) { // on levels slotLayoutLevelByProminenceIndex(selectedIndexText); } else if (selectedLayoutType==2) { // node size slotLayoutNodeSizeByProminenceIndex(selectedIndexText); // re-init other options for node sizes... } else if (selectedLayoutType==3){ // 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(){ qDebug()<<"User selected to apply a FDP layout..."; int selectedModel = toolBoxLayoutForceDirectedSelect->currentIndex(); QString selectedModelText = toolBoxLayoutForceDirectedSelect->currentText(); qDebug() << " 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->setCurrentIndex(0); break; }; } /** * @brief Starts a new network (closing the current one). */ void MainWindow::slotNetworkNew() { slotNetworkClose(); } /** * @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"]; } qDebug()<< "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) { qDebug()<< "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(); } /** * @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) { qDebug() << " 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); qDebug() << "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; } qDebug()<<"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() { qDebug() << "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) { qDebug() << "User selected network file filter" << filter; if (filter.startsWith("GraphML",Qt::CaseInsensitive ) ) { fileType=FileType::GRAPHML; qDebug() << "fileType FileType::GRAPHML"; } else if (filter.contains("PAJEK",Qt::CaseInsensitive ) ) { fileType=FileType::PAJEK; qDebug() << "fileType FileType::PAJEK"; } else if (filter.contains("DL",Qt::CaseInsensitive ) || filter.contains("UCINET",Qt::CaseInsensitive ) ) { fileType=FileType::UCINET; qDebug() << "fileType FileType::UCINET"; } else if (filter.contains("Adjacency",Qt::CaseInsensitive ) ) { fileType=FileType::ADJACENCY; qDebug() << "fileType FileType::ADJACENCY"; } else if (filter.contains("GraphViz",Qt::CaseInsensitive ) ) { fileType=FileType::GRAPHVIZ; qDebug() << "fileType FileType::GRAPHVIZ"; } else if (filter.contains("GML",Qt::CaseInsensitive ) ) { fileType=FileType::GML; qDebug() << "fileType FileType::GML"; } else if (filter.contains("Simple Edge List",Qt::CaseInsensitive ) ) { fileType=FileType::EDGELIST_SIMPLE; qDebug() << "fileType FileType::EDGELIST_SIMPLE"; } else if (filter.contains("Weighted Edge List",Qt::CaseInsensitive ) ) { fileType=FileType::EDGELIST_WEIGHTED; qDebug() << "fileType FileType::EDGELIST_WEIGHTED"; } else if (filter.contains("Two-Mode",Qt::CaseInsensitive ) ) { fileType=FileType::TWOMODE; qDebug() << "fileType FileType::TWOMODE"; } else { fileType=FileType::UNRECOGNIZED; qDebug() << "fileType FileType::UNRECOGNIZED"; } } /** * @brief Called when user selects a file in the fileDialog * Calls slotNetworkFileChoose() again. * @param fileName * */ void MainWindow::slotNetworkFileDialogFileSelected(const QString &fileName) { qDebug() << "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() { qDebug() << "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() { qDebug()<<"Request to close current network file. Check if it is saved..."; statusMessage( tr("Closing network file...")); 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; } } qDebug()<<"Closing network file. Calling initApp ..."; initApp(); qDebug()<<"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 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(){ qDebug() << "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: qDebug() << "Weighted list selected! " ; slotNetworkFileChoose( QString(), FileType::EDGELIST_WEIGHTED, m_checkSelectFileType); break; case 2: qDebug() << "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(){ qDebug() << "Importing a two mode sociomatrix network file..." ; bool m_checkSelectFileType = false; slotNetworkFileChoose( QString(), FileType::TWOMODE, m_checkSelectFileType); } /** * @brief Setup a list of all text codecs supported by OS */ void MainWindow::initNetworkAvailableTextCodecs() { qDebug() << "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(); } /** * @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 ){ qDebug() << "Previewing file: "<< m_fileName; if (m_fileName.isEmpty()) { statusMessage(tr("No file selected.")); return false; } 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) { qDebug() << "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 ) { qDebug() << "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=" "; } qDebug()<<"selected delimiter" << delimiter; } // Change the cursor to wait cursor QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) ); qDebug() << "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() ) { qDebug()<< "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 qDebug()<< "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 Toggles the interactive/selection mouse drag mode * @param checked */ void MainWindow::slotEditDragModeSelection(bool checked){ qDebug() << "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){ qDebug() << "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 Clears the relations combo. */ void MainWindow::slotEditRelationsClear(){ qDebug() << "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(); qDebug() << "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){ qDebug() << "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){ qDebug() << "relIndex==RANDMAX. Changing relation combo to last relation..."; editRelationChangeCombo->setCurrentIndex( ( editRelationChangeCombo->count()-1 ) ); } else { qDebug() << "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; qDebug()<<"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 Opens the Export to Image Dialog */ void MainWindow::slotNetworkExportImageDialog() { qDebug() << "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 ) { qDebug() << "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 // qDebug() << "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); qDebug() << "Creating painter..."; QPainter p; qDebug() << "Begin painter on picture..."; p.begin(&picture); qDebug() << "render scene on painter..."; graphicsWidget->render(&p); // // Add name and optionally log // qDebug() << "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); } qDebug() << "End painter on picture..."; p.end(); QString author = "SocNetV v" + VERSION; qDebug() << "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() { qDebug() << "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) ){ qDebug()<< "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); } qDebug() << "End painter on QPrinter..."; p.end(); delete printerPDF; } qDebug()<< "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() { qDebug() << "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(){ qDebug("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 TODO Exports the network to a DL-formatted file * @return */ bool MainWindow::slotNetworkExportDL(){ 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 UCINET", getLastPath(), 0); if (!fn.isEmpty()) { fileName=fn; setLastPath(fileName); } else { statusMessage( tr("Saving aborted")); return false; } } 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; } /** TODO: Exports the network to a list-formatted file */ bool MainWindow::slotNetworkExportList(){ if (fileName.isEmpty()) { statusMessage( tr("Saving network under new filename...")); QString fn = QFileDialog::getSaveFileName( this, "Export List", getLastPath(), 0); if (!fn.isEmpty()) { fileName=fn; setLastPath(fileName); } else { statusMessage( tr("Saving aborted")); return false; } } 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)); } /** * @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(){ qDebug() << "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) ) { qDebug ("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(){ qDebug() << "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 */ void MainWindow::slotNetworkViewSociomatrix(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-adjacency-"+dateTime+".html"; qDebug() << "MW::slotNetworkViewSociomatrix() - dataDir" << appSettings["dataDir"] << "fn" <writeMatrixAdjacency(fn) ; //AVOID THIS, no preserving of node numbers when nodes are deleted. // activeGraph->writeMatrix(fn,MATRIX_ADJACENCY) ; if ( appSettings["viewReportsInSystemBrowser"] == "true" ) { qDebug() << "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(); statusMessage(tr("Creating plot of adjacency matrix of %1 nodes.").arg(N )); 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; } } activeGraph->writeMatrixAdjacencyPlot(fn, simpler); 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)); } /** * @brief Displays the dataset selection dialog */ void MainWindow::slotNetworkDataSetSelect(){ qDebug()<< "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; qDebug()<< "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(){ qDebug() << "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) { qDebug() << "Request to create an Erdos-Renyi random network..."; statusMessage( tr("Creating new Erdos-Renyi random network. Please wait... ") ); appSettings["randomErdosEdgeProbability"] = QString::number(eprob); if (!activeGraph->randomNetErdosCreate(newNodes, model, edges, eprob, mode, diag)) { 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() { qDebug() << "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) { qDebug() << "Request to create a new scale-free random network..."; if (!activeGraph->randomNetScaleFreeCreate(newNodes, power, initialNodes, edgesPerStep, zeroAppeal, mode)) { 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() { qDebug() << "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); qDebug() << "Request to create a new small-world random network..."; if (!activeGraph->randomNetSmallWorldCreate(newNodes, degree, beta, mode)) { 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() { qDebug() << "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(); if (!activeGraph->randomNetRegularCreate(newNodes, degree, mode, diag)) { 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; } if (! activeGraph->randomNetRingLatticeCreate(newNodes, degree, true ) ) { 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() { qDebug() << "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) { qDebug() << "Request to create a new lattice random network..."; initApp(); if (!activeGraph->randomNetLatticeCreate(newNodes, length, dimension, nei, mode, circular)) { 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.") ); } /** * @brief Shows the web crawler dialog */ void MainWindow::slotNetworkWebCrawlerDialog() { // Close the current network if ( !this->slotNetworkClose() ) { // User cancelled. Do not proceed. return; } qDebug() << "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 qDebug() << "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) { qDebug() << "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 qDebug() << "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. "); } /** * @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){ qDebug()<<"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) { toolBoxEditEdgeModeSelect->setCurrentIndex(1); } 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) { toolBoxEditEdgeModeSelect->setCurrentIndex(0); } rightPanelEdgesLabel->setText(tr("Arcs:")); rightPanelSelectedEdgesLabel->setText( tr("Arcs:") ); editEdgeUndirectedAllAct->setChecked(false); } rightPanelEdgesLCD->setText(QString::number(edges)); rightPanelDensityLCD->setText(QString::number(density, 'f', 3)); qDebug()<<"Finished updating mainwindow."; } /** * @brief Popups a context menu with options when the user right-clicks on the canvas * * @param mPos */ 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(); contextMenu->addAction( "## Selected nodes: " + QString::number( nodesSelected ) + " ## "); contextMenu->addSeparator(); if (nodesSelected > 0) { 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(); } 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 Selects all nodes */ void MainWindow::slotEditNodeSelectAll(){ qDebug() << "Request to select all nodes..."; graphicsWidget->selectAll(); statusMessage( tr("Selected nodes: %1") .arg( activeGraph->getSelectedVerticesCount() ) ); } /** * @brief Selects no nodes. */ void MainWindow::slotEditNodeSelectNone(){ qDebug() << "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){ qDebug("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() { qDebug() << "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(){ qDebug() << "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) { qDebug() << "Request to find nodes:" << nodeList << "search type:"<< searchType << "indexStr"<vertexFindByNumber(nodeList); } else if (searchType == "labels"){ activeGraph->vertexFindByLabel(nodeList); } else if (searchType == "score"){ indexType = activeGraph->getProminenceIndexByName(indexStr); activeGraph->vertexFindByIndexScore(indexType, nodeList, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked() ); } 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() { qDebug() << "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) ); qDebug() << "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 ) { qDebug("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; } } qDebug ("removing vertex with number %i from Graph", nodeNumber); activeGraph->vertexRemove(nodeNumber); qDebug("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() { qDebug() << "Request to open the node properties dialog..."; if (!activeNodes()) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } int min = -1, max = -1, size = appSettings["initNodeSize"].toInt(nullptr, 10); int nodeNumber = 0; int selectedNodesCount = activeGraph->getSelectedVerticesCount(); QColor color = QColor(appSettings["initNodeColor"]); QString shape = appSettings["initNodeShape"]; QString iconPath = QString(); QString label = ""; bool ok = false; QHash customAttributes = QHash(); if (selectedNodesCount == 0) { min = activeGraph->vertexNumberMin(); max = activeGraph->vertexNumberMax(); qDebug() << "no node selected" << "min node number " << min << "max node number " << max << "opening inputdialog"; if (min == -1 || max == -1) { qDebug("ERROR in finding min max nodeNumbers. Abort"); return; } nodeNumber = QInputDialog::getInt( this, "Node Properties", tr("Choose a node between (" + QString::number(min).toLatin1() + "..." + QString::number(max).toLatin1() + "):"), min, 1, max, 1, &ok); if (!ok) { statusMessage("Node properties cancelled."); return; } label = activeGraph->vertexLabel(nodeNumber); color = activeGraph->vertexColor(nodeNumber); shape = activeGraph->vertexShape(nodeNumber); size = activeGraph->vertexSize(nodeNumber); iconPath = activeGraph->vertexShapeIconPath(nodeNumber); customAttributes=activeGraph->vertexCustomAttributes(nodeNumber); } else { qDebug() << "selectedNodesCount" << selectedNodesCount; foreach (const int &nodeNumber, activeGraph->getSelectedVertices()) { qDebug() << "reading properties of selected node"<< nodeNumber; if (selectedNodesCount > 1) { color = activeGraph->vertexColor(nodeNumber); shape = activeGraph->vertexShape(nodeNumber); iconPath = activeGraph->vertexShapeIconPath(nodeNumber); size = activeGraph->vertexSize(nodeNumber); } else { label = activeGraph->vertexLabel(nodeNumber); color = activeGraph->vertexColor(nodeNumber); shape = activeGraph->vertexShape(nodeNumber); iconPath = activeGraph->vertexShapeIconPath(nodeNumber); size = activeGraph->vertexSize(nodeNumber); customAttributes=activeGraph->vertexCustomAttributes(nodeNumber); } } } // @todo Add a function to group multiple nodes' properties changes together // This function should allow setting properties like color, size, and shape for multiple nodes at once. qDebug() << "opening DialogNodeEdit." << "label" << label << "size" << size << "color" << color << "shape" << shape << "iconPath" << iconPath << "customAttributes" << customAttributes; 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(); qDebug()<< "Request to update node properties - new properties: " << " label " << label << " size " << size << " color " << color << " shape " << shape << " vertexClicked " <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() ) { qDebug()<< "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 Creates a complete subgraph (clique) from selected nodes. */ void MainWindow::slotEditNodeSelectedToClique () { qDebug() << "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; } activeGraph->verticesCreateSubgraph(QList (), SUBGRAPH_CLIQUE); 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() { qDebug() << "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; } activeGraph->verticesCreateSubgraph(QList (), SUBGRAPH_STAR,center); 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() { qDebug() << "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; } activeGraph->verticesCreateSubgraph(QList (),SUBGRAPH_CYCLE); 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() { qDebug() << "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; } activeGraph->verticesCreateSubgraph(QList (),SUBGRAPH_LINE); 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) ); qDebug() << "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); qDebug() << "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) { qDebug() << "MW::slotEditNodeShape() - vertex " << vertex << "(0 means all)" <<"new shape" << shape << "nodeIconPath"<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; qDebug() << "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){ qDebug() << "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; qDebug() << "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; qDebug() << "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){ qDebug() << "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; qDebug() << "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 MainWindow::slotEditNodeOpenContextMenu * Called from GW when the user has right-clicked on a node * Opens a node context menu with some options when the user right-clicks on a node */ void MainWindow::slotEditNodeOpenContextMenu() { qDebug("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(); nodeContextMenu->addAction(editNodePropertiesAct ); 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 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) { qDebug()<< "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); // // NOTE: // DO NOT display a message on the status bar on high frequently called functions like this // } /** * @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) { qDebug()<<"Updating node info in status bar..."; rightPanelClickedNodeLCD->setText (QString::number(number)); 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()) ); } } /** * @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; // qDebug()<<"clicked edge" // << v1 // << "->" // << v2 // << "=" << weight // << "type" << type // << "openMenu"<setText("-"); rightPanelClickedEdgeWeightLCD->setText("-"); rightPanelClickedEdgeReciprocalWeightLCD->setText(""); return; } 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->setText(""); rightPanelClickedEdgeReciprocalWeightLCD->setText(""); 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.:"); rightPanelClickedEdgeReciprocalWeightLCD->setText(QString::number(reverseWeight)); 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->setText(""); rightPanelClickedEdgeReciprocalWeightLCD->setText(""); if (openMenu) { edgeName=QString("DIRECTED EDGE: ") + QString::number(v1)+QString(" -->")+QString::number(v2); } } if (openMenu) { slotEditEdgeOpenContextMenu(edgeName); } } /** * @brief Popups a context menu with edge-related options * Called when the user right-clicks on an edge * @param str */ void MainWindow::slotEditEdgeOpenContextMenu(const QString &str) { qDebug()<< "MW: slotEditEdgeOpenContextMenu() for" << str << "at"<< QCursor::pos().x() << "," << QCursor::pos().y(); //make the menu QMenu *edgeContextMenu = new QMenu(str, this); edgeContextMenu->addAction( str ); edgeContextMenu->addSeparator(); 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(){ qDebug()<<"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(); qDebug()<<"sourceNode:" << sourceNode; if ( activeGraph->vertexExists(sourceNode) ==-1 ) { qDebug()<< "Cannot find sourceNode"<vertexExists(targetNode) ==-1 ) { qDebug()<< "Cannot find targetNode"<edgeExists(sourceNode, targetNode)!=0 ) { qDebug("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) { qDebug()<< "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() { qDebug() << "MainWindow::slotEditEdgePropertiesDialog()"; if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } const MyEdge clicked = activeGraph->edgeClicked(); int v1 = clicked.source; int v2 = clicked.target; if (v1 == 0 || v2 == 0) { slotHelpMessageToUser(USER_MSG_CRITICAL, tr("No edge selected"), tr("Please right-click an edge first to open its properties.")); return; } QString label = activeGraph->edgeLabel(v1, v2); double weight = static_cast(activeGraph->edgeWeight(v1, v2)); QColor color = QColor(activeGraph->edgeColor(v1, v2)); QHash attrs = activeGraph->edgeCustomAttributes(v1, v2); DialogEdgeEdit *dialog = new DialogEdgeEdit(this, v1, v2, label, weight, color, attrs); connect(dialog, &DialogEdgeEdit::userChoices, this, &MainWindow::slotEditEdgeProperties); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->exec(); } /** * @brief Applies updated edge properties received from DialogEdgeEdit. */ void MainWindow::slotEditEdgeProperties(const QString &label, const double &weight, const QColor &color, const QHash &customAttributes) { qDebug() << "MainWindow::slotEditEdgeProperties()"; const MyEdge clicked = activeGraph->edgeClicked(); int v1 = clicked.source; int v2 = clicked.target; 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(){ qDebug() << "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(); qDebug() << "Selected edges:" << selectedEdgeCount; if ( ! selectedEdgeCount ) { min=activeGraph->vertexNumberMin(); max=activeGraph->vertexNumberMax(); qDebug() << "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) { qDebug() << "MW::slotEditEdgeRemove() - Multiple edges selected. " "Calling Graph to remove all of them..."; activeGraph->edgeRemoveSelectedAll(); return; } qDebug() << "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); qDebug()<< "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(){ qDebug() << "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()) { qDebug() << "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){ qDebug() << "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()) { qDebug() << "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(){ qDebug() << "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(); qDebug() << "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; } qDebug("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; } qDebug("source %i target %i",sourceNode, targetNode); } else { // An edge is clicked/selected. qDebug() << "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; } qDebug() << "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; } qDebug() << "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; } qDebug() << "Request to add a new symmetric relation using cocited nodes..."; activeGraph->relationAddCocitation(); 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; } qDebug() << "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; } qDebug("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; } qDebug()<< "MW::slotEditEdgeSymmetrizeStrongTies() - calling addRelationSymmetricStrongTies()"; int oldRelationsCounter=activeGraph->relations(); int answer=0; if (oldRelationsCounter>0) { switch ( answer=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: activeGraph->addRelationSymmetricStrongTies(true); break; case 2: activeGraph->addRelationSymmetricStrongTies(false); break; } } else { activeGraph->addRelationSymmetricStrongTies(false); } 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){ qDebug()<<"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) { qDebug()<<"Changing edge mode to undirected. Informing Graph..."; activeGraph->setUndirected(true); qDebug()<<"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 { qDebug()<<"Changing edge mode to directed. Informing Graph..."; activeGraph->setDirected(true); qDebug()<<"Triggering optionsEdgeArrowsAct checkbox"; optionsEdgeArrowsAct->trigger(); qDebug()<<"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.")) ; } } } /** * @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); connect(&dlg, &DialogFilterNodesByCentrality::userChoices, activeGraph, &Graph::vertexFilterByCentrality); if (dlg.exec() == QDialog::Accepted) { filterNodesRestoreAllAct->setEnabled(true); m_filterBar->addChip(tr("Nodes: centrality filter"), FilterCondition::Scope::Nodes); } } /** * @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; } activeGraph->vertexFilterBySelection(selection); filterNodesRestoreAllAct->setEnabled(true); 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; } activeGraph->vertexFilterByEgoNetwork(v1); filterNodesRestoreAllAct->setEnabled(true); 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 Node or Edge Properties dialogs first.")); return; } DialogFilterByAttribute dlg(nodeKeys, edgeKeys, this); connect(&dlg, &DialogFilterByAttribute::userChoices, this, [this](const FilterCondition &cond) { if (cond.scope == FilterCondition::Scope::Edges) { activeGraph->edgeFilterByAttribute(cond); m_filterBar->addChip(cond.label(), FilterCondition::Scope::Edges); } else if (cond.scope == FilterCondition::Scope::Both) { activeGraph->vertexFilterByAttribute(cond); activeGraph->edgeFilterByAttribute(cond); // 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_filterBar->addChip(nc.label(), FilterCondition::Scope::Nodes); m_filterBar->addChip(ec.label(), FilterCondition::Scope::Edges); } else { activeGraph->vertexFilterByAttribute(cond); m_filterBar->addChip(cond.label(), FilterCondition::Scope::Nodes); } filterNodesRestoreAllAct->setEnabled(true); }); dlg.exec(); } /** * @brief Restores all nodes hidden by the last filter. */ void MainWindow::slotFilterNodesRestoreAll() { activeGraph->vertexFilterRestoreAll(); m_filterBar->removeLatestChipForScope(FilterCondition::Scope::Nodes); if (activeGraph->visibilityHistoryEmpty()) filterNodesRestoreAllAct->setEnabled(false); } /** * @brief Toggles the status of all isolated vertices * * @param checked */ void MainWindow::slotEditFilterNodesIsolates(bool checked){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } activeGraph->vertexIsolatedAllToggle( ! editFilterNodesIsolatesAct->isChecked() ); 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); // Connect dialog signal to the graph connect( m_DialogEdgeFilterByWeight, &DialogFilterEdgesByWeight::userChoices, activeGraph, &Graph::edgeFilterByWeight); // Enable restore action and add filter bar chip after filter is applied connect( m_DialogEdgeFilterByWeight, &DialogFilterEdgesByWeight::userChoices, this, [this](){ editFilterEdgesRestoreAllAct->setEnabled(true); 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() { activeGraph->edgeFilterReset(); editFilterEdgesRestoreAllAct->setEnabled(false); m_filterBar->removeAllChipsForScope(FilterCondition::Scope::Edges); } /** * @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; } activeGraph->edgeFilterUnilateral( ! editFilterEdgesUnilateralAct->isChecked() ); if ( checked ){ statusMessage( tr("Unilateral (weak) edges disabled.") ); } else { statusMessage( tr("Unilateral (weak) edges enabled.") ); } } /** * Transforms all nodes to edges TODO slotEditTransformNodes2Edges */ void MainWindow::slotEditTransformNodes2Edges(){ } /** 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(); activeGraph->layoutRandom(); statusMessage( tr("Nodes in random positions.") ); } /** * @brief Calls Graph::layoutRadialRandom * to reposition all nodes on a radial layout randomly */ void MainWindow::slotLayoutRadialRandom(){ qDebug() << "MainWindow::slotLayoutRadialRandom()"; if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } slotLayoutGuides(false); activeGraph->layoutRadialRandom(true); 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() { qDebug() << "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; } activeGraph->layoutEgoRadial(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() { qDebug() << "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()) ); } activeGraph->layoutForceDirectedSpringEmbedder(300); statusMessage(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(){ qDebug("MW: slotLayoutFruchterman ()"); if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } activeGraph->layoutForceDirectedFruchtermanReingold(100); statusMessage( tr("Fruchterman & Reingold model embedded.") ); } /** * @brief Layouts the network according to the Kamada-Kawai FDP model */ void MainWindow::slotLayoutKamadaKawai(){ qDebug()<< "MW::slotLayoutKamadaKawai ()"; if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } activeGraph->layoutForceDirectedKamadaKawai(400); statusMessage( 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(){ qDebug() << "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=""){ qDebug() << "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); qDebug() << "indexType" << indexType; toolBoxLayoutByIndexSelect->setCurrentIndex(indexType+1); toolBoxLayoutByIndexTypeSelect->setCurrentIndex(0); bool dropIsolates=false; if (indexType==IndexType::IC && activeNodes() > 200) { switch( QMessageBox::critical( this, "Slow function warning", tr("Please note that this function is SLOW 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.
" "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.
" "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(); activeGraph->layoutByProminenceIndex( indexType, 0, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked() || dropIsolates); statusMessage( 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(){ qDebug() << "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=""){ qDebug() << "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); qDebug() << "indexType" << indexType ; toolBoxLayoutByIndexSelect->setCurrentIndex(indexType+1); toolBoxLayoutByIndexTypeSelect->setCurrentIndex(1); bool dropIsolates=false; if (indexType ==IndexType::IC && activeNodes() > 200) { switch( QMessageBox::critical( this, "Slow function warning", tr("Please note that this function is SLOW 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.
" "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.
" "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(); activeGraph->layoutByProminenceIndex( indexType , 1, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked() || dropIsolates); statusMessage( 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(){ qDebug() << "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=""){ qDebug() << "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); qDebug() << "indexType" << indexType; toolBoxLayoutByIndexSelect->setCurrentIndex(indexType+1); toolBoxLayoutByIndexTypeSelect->setCurrentIndex(2); bool dropIsolates=false; if (indexType==IndexType::IC && activeNodes() > 200) { switch( QMessageBox::critical( this, "Slow function warning", tr("Please note that this function is SLOW 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.
" "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.
" "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(); activeGraph->layoutByProminenceIndex( indexType, 2, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked() || dropIsolates); statusMessage( 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=""){ qDebug() << "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); qDebug() << "indexType" << indexType; toolBoxLayoutByIndexSelect->setCurrentIndex(indexType+1); toolBoxLayoutByIndexTypeSelect->setCurrentIndex(3); bool dropIsolates=false; if (indexType==8 && activeNodes() > 200) { switch( QMessageBox::critical( this, "Slow function warning", tr("Please note that this function is SLOW 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.
" "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.
" "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(); activeGraph->layoutByProminenceIndex( indexType, 3, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked() || dropIsolates); statusMessage( tr("Nodes with warmer color have greater %1 score.").arg(prominenceIndexName)); } /** * @brief Shows or hides (clears) layout guides * * @param toggle */ void MainWindow::slotLayoutGuides(const bool &toggle){ qDebug()<< "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") ); } } /** * Returns the amount of enabled/active edges on the scene. */ int MainWindow::activeEdges(){ qDebug() << "MW::activeEdges()"; return activeGraph->edgesEnabled(); } /** * Returns the number of active nodes on the scene. */ int MainWindow::activeNodes(){ return activeGraph->vertices(); } /** * Displays the arc and dyad reciprocity of the network */ void MainWindow::slotAnalyzeReciprocity(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-reciprocity-"+dateTime+".html"; askAboutEdgeWeights(); activeGraph->writeReciprocity(fn, optionsEdgeWeightConsiderAct->isChecked()); if ( 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) ); } /** * Displays a box informing the user about the symmetry or not of the adjacency matrix */ 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 */ void MainWindow::slotAnalyzeMatrixAdjacencyInverse(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-adjacency-inverse-"+dateTime+".html"; statusMessage(tr ("Inverting adjacency matrix.") ); if ( !activeGraph->writeMatrix(fn,MATRIX_ADJACENCY_INVERSE) ) { 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("Inverse matrix saved as: ")+QDir::toNativeSeparators(fn)); } /** * @brief Writes the transpose adjacency matrix */ void MainWindow::slotAnalyzeMatrixAdjacencyTranspose(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-adjacency-transpose-"+dateTime+".html"; statusMessage( tr ("Transposing adjacency matrix.") ); if ( !activeGraph->writeMatrix(fn,MATRIX_ADJACENCY_TRANSPOSE) ) { 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("Transpose adjacency matrix saved as: ")+QDir::toNativeSeparators(fn)); } /** * @brief Writes the cocitation matrix */ void MainWindow::slotAnalyzeMatrixAdjacencyCocitation(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-cocitation-"+dateTime+".html"; statusMessage( tr ("Computing Cocitation matrix.") ); if ( !activeGraph->writeMatrix(fn,MATRIX_COCITATION) ) { 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("Cocitation matrix saved as: ")+QDir::toNativeSeparators(fn)); } /** * @brief Writes the degree matrix of the graph */ void MainWindow::slotAnalyzeMatrixDegree(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-degree-"+dateTime+".html"; statusMessage(tr ("Computing Degree matrix.") ); if ( !activeGraph->writeMatrix(fn, MATRIX_DEGREE) ) { 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("Degree matrix saved as: ")+QDir::toNativeSeparators(fn)); } /** * @brief Writes the Laplacian matrix of the graph */ void MainWindow::slotAnalyzeMatrixLaplacian(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } qDebug() << "MW:slotAnalyzeMatrixLaplacian()"; QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-laplacian-"+dateTime+".html"; statusMessage(tr ("Computing Laplacian matrix") ); if ( !activeGraph->writeMatrix(fn, MATRIX_LAPLACIAN) ) { 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("Laplacian matrix saved as: ")+QDir::toNativeSeparators(fn)); } /** * @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){ qDebug() << "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; } } qDebug() << "MW::askAboutEdgeWeights() - graph weighted - checking if we have asked user."; if (askedAboutWeights) { return; } qDebug() << "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), " "press Yes to inverse the weights, since the distance between two " "nodes should be the most valuable one."), 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; } /** * @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; } qDebug() << "Computing geodesic distance:" << sourceNum << "->" << targetNum; if (activeGraph->isSymmetric() && sourceNum>targetNum) { qSwap(sourceNum,targetNum); } askAboutEdgeWeights(); int distanceGeodesic = activeGraph->graphDistanceGeodesic( sourceNum, targetNum, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights); if ( distanceGeodesic > 0 && distanceGeodesic < RAND_MAX) { qDebug() << "geodesic distance" << sourceNum << "->" << targetNum << "=" << distanceGeodesic; slotHelpMessageToUser ( USER_MSG_INFO, tr("Geodesic Distance: %1").arg(distanceGeodesic), tr("Geodesic Distance: %1").arg(distanceGeodesic), tr("Nodes %1 and %2 are connected through at least one path. The length of the shortest path is %3.") .arg(sourceNum).arg(targetNum).arg(distanceGeodesic) ); } else { qDebug() << "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 Invokes calculation of the matrix of geodesic distances for the loaded network, then displays it. */ void MainWindow::slotAnalyzeMatrixDistances(){ qDebug() << "Request to compute the matrix of geodesic distances. Please wait..."; if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-geodesic-distances-"+dateTime+".html"; askAboutEdgeWeights(); statusMessage( tr("Computing geodesic distances. Please wait...") ); if ( !activeGraph->writeMatrix(fn,MATRIX_DISTANCES, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked()) ) { 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("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. */ void MainWindow::slotAnalyzeMatrixGeodesics(){ qDebug() << "Request to compute the matrix of geodesics. Please wait..."; if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-geodesics-"+dateTime+".html"; askAboutEdgeWeights(); statusMessage( tr("Computing geodesics (number of shortest paths) for each pair. Please wait...") ); if ( !activeGraph->writeMatrix(fn,MATRIX_GEODESICS, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked()) ) { 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("Geodesics Matrix saved as: ") + QDir::toNativeSeparators(fn)); } /** * @brief Displays the network diameter (largest geodesic) */ void MainWindow::slotAnalyzeDiameter() { if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } askAboutEdgeWeights(); statusMessage( tr("Computing graph diameter. Please wait...") ); int netDiameter=activeGraph->graphDiameter( optionsEdgeWeightConsiderAct->isChecked(), inverseWeights); if ( activeGraph->isWeighted() ) { if (optionsEdgeWeightConsiderAct->isChecked()) { 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(); statusMessage( tr("Computing Average Graph Distance. Please wait...") ); qreal averGraphDistance=activeGraph->graphDistanceGeodesicAverage( optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked() ); bool isConnected = activeGraph->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.") ); } } /** * Writes Eccentricity indices into a file, then displays it. */ void MainWindow::slotAnalyzeEccentricity(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-eccentricity-"+dateTime+".html"; askAboutEdgeWeights(); if ( ! activeGraph->writeEccentricity( fn, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked()) ) { 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("Eccentricities saved as: ") + QDir::toNativeSeparators(fn) ); } /** * @brief Reports the network connectedness */ void MainWindow::slotAnalyzeConnectedness(){ qDebug() << "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 { bool isConnected=activeGraph->isConnected(); qDebug() << "MW::slotAnalyzeConnectedness result " << isConnected; if(isConnected){ if (activeGraph->isDirected()){ slotHelpMessageToUser ( USER_MSG_INFO, tr("This directed network is strongly connected."), tr("This directed network is strongly connected."), tr("A 1-actor network (singleton graph) is considered connected.") ); } else { slotHelpMessageToUser ( USER_MSG_INFO, tr("This undirected network is connected."), tr("This undirected network is connected."), tr("This network has an undirected graph which is connected.") ); } } else { if (activeGraph->isDirected()){ slotHelpMessageToUser ( USER_MSG_INFO, tr("This directed network is disconnected."), tr("This directed network is disconnected."), tr("There are pairs of nodes that are not connected with any directed path.") ); } else { slotHelpMessageToUser ( USER_MSG_INFO, tr("This undirected network is not connected."), tr("This undirected network is not connected."), tr("There are pairs of nodes that are not connected with any path.") ); } } } } /** * Calculate and print the number of walks of a given length , between each pair of nodes. */ 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; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-walks-length-"+QString::number(length)+"-"+dateTime+".html"; activeGraph->writeMatrixWalks(fn, length); 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("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. */ 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; } } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-walks-total-"+dateTime+".html"; statusMessage( tr("Computing total walks matrix. Please wait...") ); activeGraph->writeMatrixWalks(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("Total walks matrix saved as: " + QDir::toNativeSeparators(fn)); } /** * Calls Graph:: writeReachabilityMatrixPlainText() to calculate and print * the Reachability Matrix of the network. */ void MainWindow::slotAnalyzeReachabilityMatrix(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-matrix-reachability-"+dateTime+".html"; statusMessage( tr("Computing reachability matrix. Please wait...") ); if ( !activeGraph->writeMatrix(fn, MATRIX_REACHABILITY) ) { 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("Reachability matrix saved as: ") + QDir::toNativeSeparators(fn) ); } /** * @brief Calls Graph::writeClusteringCoefficient() to write Clustering Coefficients * into a file, and displays it. */ void MainWindow::slotAnalyzeClusteringCoefficient (){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-clustering-coefficient-"+dateTime+".html"; bool considerWeights=true; if ( ! activeGraph->writeClusteringCoefficient(fn, considerWeights) ) { 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("Clustering Coefficients saved as: ") + QDir::toNativeSeparators(fn)); } /** * @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; if (! activeGraph->writeCliqueCensus(fn, considerWeights) ) { 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)); } /** * Calls Graph to compute and write a triad census into a file, then displays it. */ void MainWindow::slotAnalyzeCommunitiesTriadCensus() { if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-triad-census-"+dateTime+".html"; bool considerWeights=true; if ( ! activeGraph->writeTriadCensus(fn, considerWeights)) { 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("Triad Census saved as: ") + QDir::toNativeSeparators(fn)); } /** * @brief Displays the DialogSimilarityMatches dialog. */ void MainWindow::slotAnalyzeStrEquivalenceSimilarityMeasureDialog() { qDebug()<< "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. * */ void MainWindow::slotAnalyzeStrEquivalenceSimilarityByMeasure(const QString &matrix, const QString &varLocation, const QString &measure, const bool &diagonal) { if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } 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+".html"; bool considerWeights=true; if ( ! activeGraph->writeMatrixSimilarityMatching( fn, measure, matrix, varLocation, diagonal, considerWeights) ) { 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("Similarity matrix saved as: ") + QDir::toNativeSeparators(fn)); } /** * @brief Displays the DialogDissimilarities dialog. */ void MainWindow::slotAnalyzeStrEquivalenceDissimilaritiesDialog() { qDebug()<< "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 */ void MainWindow::slotAnalyzeStrEquivalenceDissimilaritiesTieProfile(const QString &metric, const QString &varLocation, const bool &diagonal){ qDebug() << "MW::slotAnalyzeStrEquivalenceDissimilaritiesTieProfile()"; if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } 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+".html"; askAboutEdgeWeights(); if (!activeGraph->writeMatrixDissimilarities(fn, metric, varLocation, diagonal, optionsEdgeWeightConsiderAct->isChecked())) { 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("Tie profile dissimilarities matrix saved as: ")+QDir::toNativeSeparators(fn)); } /** * @brief Calls the m_dialogSimilarityPearson to display the Pearson statistics dialog */ void MainWindow::slotAnalyzeStrEquivalencePearsonDialog(){ qDebug()<< "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. * */ void MainWindow::slotAnalyzeStrEquivalencePearson(const QString &matrix, const QString &varLocation, const bool &diagonal) { if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-equivalence-pearson-coefficients-"+dateTime+".html"; bool considerWeights=true; if ( ! activeGraph->writeMatrixSimilarityPearson( fn, considerWeights, matrix, varLocation, diagonal) ) { 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("Pearson correlation coefficients matrix saved as: ") + QDir::toNativeSeparators(fn)); } /** * @brief Displays the slotAnalyzeStrEquivalenceClusteringHierarchicalDialog dialog. */ void MainWindow::slotAnalyzeStrEquivalenceClusteringHierarchicalDialog() { qDebug()<< "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){ qDebug()<< "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=true; bool inverseWeights=false; bool dropIsolates=true; if (! activeGraph->writeClusteringHierarchical(fn, varLocation, matrix, metric, method, diagonal, diagram, considerWeights, inverseWeights, dropIsolates) ){ 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)); } /** * Writes Out-Degree Centralities into a file, then displays it. */ void MainWindow::slotAnalyzeCentralityDegree(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } askAboutEdgeWeights(false); QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-out-degree-"+dateTime+".html"; if (!activeGraph->writeCentralityDegree( fn, optionsEdgeWeightConsiderAct->isChecked(), editFilterNodesIsolatesAct->isChecked())) { return; } statusMessage(tr("Opening Out-Degree Centralities report...")); if ( 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. */ void MainWindow::slotAnalyzeCentralityCloseness(){ qDebug() << "MW::slotAnalyzeCentralityCloseness()"; if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } bool dropIsolates=false; askAboutEdgeWeights(); QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-closeness-"+dateTime+".html"; if (!activeGraph->writeCentralityCloseness( fn, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked() || dropIsolates)) { return; } statusMessage(tr("Opening Closeness Centralities report...")); if ( 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. */ void MainWindow::slotAnalyzeCentralityClosenessIR(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-closeness-influence-range-"+dateTime+".html"; askAboutEdgeWeights(); if (! activeGraph->writeCentralityClosenessInfluenceRange( fn, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked())) { return; } statusMessage(tr("Opening Influence Range Closeness Centralities report...")); if ( 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)); } /** * Writes Betweenness Centralities into a file, then displays it. */ void MainWindow::slotAnalyzeCentralityBetweenness(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-betweenness-"+dateTime+".html"; askAboutEdgeWeights(); if (!activeGraph->writeCentralityBetweenness( fn, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked())) { return; } statusMessage(tr("Opening Betweenness Centralities report...")); if ( 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. */ 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); QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-prestige-degree-"+dateTime+".html"; if (!activeGraph->writePrestigeDegree(fn, optionsEdgeWeightConsiderAct->isChecked(), editFilterNodesIsolatesAct->isChecked())) { return; } statusMessage(tr("Opening Degree Prestige (in-degree) report...")); if ( 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. */ void MainWindow::slotAnalyzePrestigePageRank(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-prestige-pagerank-"+dateTime+".html"; askAboutEdgeWeights(); if ( ! activeGraph->writePrestigePageRank(fn, editFilterNodesIsolatesAct->isChecked()) ) { return; } statusMessage(tr("Opening PageRank Prestige report...")); if ( 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. */ void MainWindow::slotAnalyzePrestigeProximity(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-prestige-proximity-"+dateTime+".html"; askAboutEdgeWeights(); if (!activeGraph->writePrestigeProximity(fn, true, false, editFilterNodesIsolatesAct->isChecked())) { return; } statusMessage(tr("Opening Proximity Prestige report...")); if ( 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. */ void MainWindow::slotAnalyzeCentralityInformation(){ qDebug() << "MW::slotAnalyzeCentralityInformation()"; if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } if (activeNodes() > 200) { switch( QMessageBox::critical( this, "Slow function warning", tr("Please note that this function is SLOW 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.
" "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.
" "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; } } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-information-"+dateTime+".html"; askAboutEdgeWeights(); if (!activeGraph->writeCentralityInformation( fn, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights)) { return; } statusMessage(tr("Opening Information Centralities report...")); if ( 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. */ void MainWindow::slotAnalyzeCentralityEigenvector(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-eigenvector-"+dateTime+".html"; askAboutEdgeWeights(); bool dropIsolates = false; if (!activeGraph->writeCentralityEigenvector( fn, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, dropIsolates)) { return; } statusMessage(tr("Opening Eigenvector Centralities report...")); if ( 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. */ void MainWindow::slotAnalyzeCentralityStress(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-stress-"+dateTime+".html"; askAboutEdgeWeights(); if (!activeGraph->writeCentralityStress( fn, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked())) { return; } statusMessage(tr("Opening Stress Centralities report...")); if ( 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. */ void MainWindow::slotAnalyzeCentralityPower(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-power-Gil-Schmidt-"+dateTime+".html"; askAboutEdgeWeights(); if (!activeGraph->writeCentralityPower( fn, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked())) { return; } statusMessage(tr("Opening Gil-Schmidt Power Centralities report...")); if ( 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::slotAnalyzeCentralityEccentricity * Writes Eccentricity Centralities into a file, then displays it. */ void MainWindow::slotAnalyzeCentralityEccentricity(){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } QString dateTime=QDateTime::currentDateTime().toString ( QString ("yy-MM-dd-hhmmss")); QString fn = appSettings["dataDir"] + "socnetv-report-centrality-eccentricity-"+dateTime+".html"; askAboutEdgeWeights(); if (!activeGraph->writeCentralityEccentricity( fn, optionsEdgeWeightConsiderAct->isChecked(), inverseWeights, editFilterNodesIsolatesAct->isChecked())) { return; } statusMessage(tr("Opening Closeness Centralities report...")); if ( 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)); } /** * @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); qDebug() << "Updating the prominence distribution miniChart"; if (series == Q_NULLPTR) { qDebug() << "series is null! Resetting to trivial"; miniChart->resetToTrivial(); return; } // Set the style of the lines and bars switch (series->type()) { case QAbstractSeries::SeriesTypeBar : qDebug() << "this an BarSeries"; break; case QAbstractSeries::SeriesTypeArea : qDebug() << "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 ) { qDebug() << "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 ){ qDebug() << "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 ){ qDebug() << "MW::slotAnalyzeProminenceDistributionChartUpdate() - " "axisX and axisY null. Calling createDefaultAxes()"; miniChart->createDefaultAxes(); qDebug() << "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); } } /** * @brief Creates a Qt Progress Dialog. * If max = 0, then max becomes equal to active vertices. * Connects the dialog's Cancel button to Graph::slotCancelComputation() * so that the user can interrupt long-running computations. * @param max The maximum value for the progress bar (0 = use active vertex count) * @param msg The message to display in the dialog */ void MainWindow::slotProgressBoxCreate(const int &max, const QString &msg) { qDebug() << "MW::slotProgressBoxCreate"; if (appSettings["showProgressBar"] == "true") { int duration = (max == 0) ? activeNodes() : max; if (!progressDialogs.isEmpty()) { // A dialog is already active β€” reuse it for the new sub-step // instead of stacking a new one on top. QProgressDialog *progressBox = progressDialogs.top(); progressBox->setLabelText(msg); progressBox->setMaximum(duration); progressBox->setValue(0); // Push nullptr sentinel so slotProgressBoxDestroy knows // this is a sub-step finish and should not destroy the real dialog. progressDialogs.push(nullptr); qDebug() << "MW::slotProgressBoxCreate - reusing existing dialog for sub-step"; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return; } QProgressDialog *progressBox = new QProgressDialog(msg, "Cancel", 0, duration, this); polishProgressDialog(progressBox); progressBox->setWindowModality(Qt::ApplicationModal); connect(activeGraph, &Graph::signalProgressBoxUpdate, progressBox, &QProgressDialog::setValue); connect(progressBox, &QProgressDialog::canceled, activeGraph, &Graph::slotCancelComputation); progressBox->setMinimumDuration(0); progressBox->setAutoClose(true); progressBox->setAutoReset(true); progressDialogs.push(progressBox); } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } /** * @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 Destroys the first in queue Progress dialog */ void MainWindow::slotProgressBoxDestroy(const int &max){ qDebug() << "MainWindow::slotProgressBoxDestroy"; QApplication::restoreOverrideCursor(); if (appSettings["showProgressBar"] == "true" && max > -1) { if (!progressDialogs.isEmpty()) { QProgressDialog *progressBox = progressDialogs.pop(); if (progressBox == nullptr) { // Sub-step sentinel β€” real dialog stays alive. qDebug() << "MW::slotProgressBoxDestroy - sub-step sentinel, skipping destroy"; return; } progressBox->reset(); progressBox->deleteLater(); } } } /** * @brief MainWindow::slotOptionsNodeNumbersVisibility * Turns on/off displaying the numbers of nodes (outside ones) * @param toggle */ void MainWindow::slotOptionsNodeNumbersVisibility(bool toggle) { qDebug() << "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 MainWindow::slotOptionsNodeNumbersInside * Turns on/off displaying the nodenumbers inside the nodes. * @param toggle */ void MainWindow::slotOptionsNodeNumbersInside(bool toggle){ qDebug() << "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 MainWindow::slotOptionsNodeLabelsVisibility * Turns on/off displaying labels * @param toggle */ void MainWindow::slotOptionsNodeLabelsVisibility(bool toggle){ qDebug() << "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 MainWindow::slotOptionsEdgesVisibility * @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 Turns on/off the arrows of edges * @param toggle */ void MainWindow::slotOptionsEdgeArrowsVisibility(bool toggle){ qDebug()<<"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 Toggles edge weights during computations * @param toggle */ void MainWindow::slotOptionsEdgeWeightsDuringComputation(bool toggle) { askedAboutWeights=false; askAboutEdgeWeights(toggle); activeGraph->setModStatus(activeGraph->ModStatus::EdgeCount); } /** * FIXME edges Bezier */ void MainWindow::slotOptionsEdgesBezier(bool toggle){ if ( !activeNodes() ) { slotHelpMessageToUser(USER_MSG_CRITICAL_NO_NETWORK); return; } statusMessage( tr("Toggle edges bezier. Please wait...") ); // // graphicsWidget->setBezier(toggle); if (!toggle) { // QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) ); // QList list = scene->items(); // for (QList::iterator item=list.begin();item!=list.end(); item++) { // if ( (*item)->type() ==TypeEdge ){ // GraphicsEdge *edge = (GraphicsEdge*) (*item); // // edge->toggleBezier(false); // (*item)->hide();(*item)->show(); // } // // } // QApplication::restoreOverrideCursor(); // return; } else{ // QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) ); // QList list = scene->items(); // for (QList::iterator item=list.begin();item!=list.end(); item++){ // if ( (*item)->type() ==TypeEdge ){ // GraphicsEdge *edge = (GraphicsEdge*) (*item); // // edge->toggleBezier(true); // (*item)->hide();(*item)->show(); // } // } // QApplication::restoreOverrideCursor(); } } /** * @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; qDebug() << "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 * Turns on/off displaying edge weight numbers * @param toggle */ void MainWindow::slotOptionsEdgeWeightNumbersVisibility(bool toggle) { qDebug() << "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 MainWindow::slotOptionsEdgeLabelsVisibility * Turns on/off displaying edge labels * @param toggle */ void MainWindow::slotOptionsEdgeLabelsVisibility(bool toggle) { qDebug() << "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) { qDebug() << "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 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) { qDebug()<< "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) { qDebug()<< "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) { qDebug()<< "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) { qDebug()<< "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) { qDebug()<< "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) { qDebug()<< "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) { qDebug()<< "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) { qDebug()<< "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) { qDebug()<<"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"); qDebug()<<"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 displaying a custom image in the background * If toggle = true, presents a dialog to select an image file * Called from app menu option * @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) { leftPanel->hide(); appSettings["showLeftPanel"] = "false"; statusMessage( tr("Left Panel off.") ); } else { leftPanel->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 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(); } } /** * @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) { qDebug() << "Opening stylesheet file: "<< sheetFileName; QString styleSheet = ""; if ( !sheetFileName.isEmpty() ) { QFile file(sheetFileName); if (!file.open(QFile::ReadOnly)) { qDebug () << "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); } /** * 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"; qDebug() << "Will make a 'check for updates' request to url:" << url; slotNetworkManagerRequest(QUrl(url), NetworkRequestType::CheckUpdate); } /** * @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 Parses the reply from the network request we do in slotHelpCheckUpdateDialog */ void MainWindow::slotHelpCheckUpdateParse() { qDebug() << "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); qDebug() << "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() { qDebug() << "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-app-39db829/src/mainwindow.h000077500000000000000000001002071517721000100174300ustar00rootroot00000000000000/** * @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 // Allows to use QT_CHARTS namespace directives (see below) #include #include "global.h" 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 DialogFilterNodesByCentrality; class DialogFilterEdgesByWeight; 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); ~MainWindow(); void slotOptionsCustomStylesheet(const bool checked); void slotStyleSheetByName(const QString &sheetFileName); void polishProgressDialog(QProgressDialog* dialog); 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 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(); 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 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 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 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 slotFilterNodesRestoreAll(); void slotEditFilterEdgesByWeightDialog(); void slotEditFilterEdgesReset(); void slotEditFilterEdgesUnilateral(bool checked); 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 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 slotAnalyzeDiameter(); void slotAnalyzeEccentricity(); void slotAnalyzeWalksLength(); void slotAnalyzeWalksTotal(); void slotAnalyzeReachabilityMatrix(); void slotAnalyzeConnectedness(); void slotAnalyzeCentralityDegree(); void slotAnalyzeCentralityCloseness(); void slotAnalyzeCentralityClosenessIR(); void slotAnalyzeCentralityBetweenness(); void slotAnalyzeCentralityInformation(); void slotAnalyzeCentralityEigenvector(); void slotAnalyzeCentralityStress(); void slotAnalyzeCentralityPower(); void slotAnalyzeCentralityEccentricity(); 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 slotOptionsSaveZeroWeightEdges(bool toggle); 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(); void slotProgressBoxCreate(const int &max=0, const QString &msg="Please wait..."); void slotProgressBoxDestroy(const int &max=0); 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 settingsFilePath, settingsDir ; QStringList fortuneCookie; QStringList tempFileNameNoPath, tips; QStringList prominenceIndexList; QStringList recentFiles; QStringList iconPathList; QStringList nodeShapeList; QMap appSettings; QList codecs; QList m_textEditors; QStack progressDialogs; 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; 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 ; QComboBox *editRelationChangeCombo; QMenu *importSubMenu, *exportSubMenu, *editMenu, *analysisMenu, *helpMenu; QMenu *optionsMenu, *colorOptionsMenu, *edgeOptionsMenu, *nodeOptionsMenu; QMenu *editNodeMenu, *editEdgeMenu, *centrlMenu, *viewOptionsMenu, *layoutMenu; QMenu *cohesionMenu, *strEquivalenceMenu, *communitiesMenu, *connectivityMenu; QMenu *matrixMenu; QMenu *networkMenu, *randomNetworkMenu, *filterMenu, *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; QPushButton *toolBoxLayoutByIndexApplyButton, *toolBoxLayoutForceDirectedApplyButton; 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; 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 *editEdgeAddAct, *editEdgeRemoveAct, *editEdgePropertiesAct; QAction *editNodeNumbersSizeAct, *editNodeLabelsSizeAct; QAction *editNodeSizeAllAct, *editNodeShapeAll; QAction *editEdgeLabelAct, *editEdgeColorAct, *editEdgeWeightAct; QAction *filterNodesByCentralityAct, *filterNodesByEgoNetworkAct, *filterNodesBySelectionAct, *filterNodesByAttributeAct, *filterNodesRestoreAllAct, *editFilterNodesIsolatesAct, *editFilterEdgesByWeightAct, *editFilterEdgesRestoreAllAct; QAction *editFilterEdgesUnilateralAct; 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; QAction *analyzeMatrixDistancesGeodesicAct, *analyzeMatrixGeodesicsAct; QAction *analyzeGraphDiameterAct, *analyzeGraphEccentricityAct; QAction *analyzeStrEquivalenceTieProfileDissimilaritiesAct; QAction *analyzeGraphWalksAct,*analyzeGraphWalksTotalAct, *analyzeMatrixReachabilityAct, *analyzeGraphConnectednessAct; 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; 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; 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; 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; 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; 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 *rightPanelClickedNodeHeaderLabel; QLabel *rightPanelNodesLCD; QLabel *rightPanelEdgesLCD; QLabel *rightPanelDensityLCD; QLabel *rightPanelClickedNodeLCD; QLabel *rightPanelClickedNodeInDegreeLCD; QLabel *rightPanelClickedNodeOutDegreeLCD; QLabel *rightPanelSelectedNodesLCD; QLabel *rightPanelSelectedEdgesLCD; QLabel *rightPanelSelectedEdgesLabel; QLabel *rightPanelClickedEdgeNameLabel; QLabel *rightPanelClickedEdgeNameLCD; QLabel *rightPanelClickedEdgeWeightLabel; QLabel *rightPanelClickedEdgeWeightLCD; QLabel *rightPanelClickedEdgeReciprocalWeightLabel; QLabel *rightPanelClickedEdgeReciprocalWeightLCD; }; #endif socnetv-app-39db829/src/matrix.cpp000077500000000000000000002413171517721000100171230ustar00rootroot00000000000000/** * @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 #include //allows the use of RAND_MAX macro #include #include //needed for fabs, qFloor etc #include /** * @brief Matrix::Matrix * Default constructor - creates a Matrix of given dimension (0x0) * Use resize(m,n) or zeromatrix(m,n) to resize it * @param Actors */ Matrix::Matrix (int rowDim, int colDim) : m_rows (rowDim), m_cols(colDim) { row = new (nothrow) MatrixRow[ m_rows ]; Q_CHECK_PTR( row ); for (int i=0;i 0){ qDebug() << "Matrix::clear() deleting old rows"; m_rows=0; m_cols=0; delete [] row; } } /** * @brief Resizes this matrix to m x n * Called before every operation on new matrices. * Every MatrixRow object holds max_int=32762 * @param Actors */ void Matrix::resize (const int m, const int n) { qDebug() << "Matrix: resize() "; clear(); m_rows = m; m_cols = n; row = new (nothrow) MatrixRow [ m_rows ]; Q_CHECK_PTR( row ); qDebug() << "Matrix: resize() -- resizing each row"; for (int i=0;i max) { max = item(r,c) ; } if ( item(r,c) < min){ min = item(r,c) ; } } } } /** * @brief Like Matrix::findMinMaxValues only it skips r==c * * @param min value. If (r,c) = minimum, it mean that neighbors r and c are the nearest in the matrix/network * @param max value * Complexity: O(n^2) */ 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) { qDebug() << "Matrix::identityMatrix() -- deleting old rows"; clear(); m_rows=dim; m_cols=dim; row = new (nothrow) MatrixRow [m_rows]; Q_CHECK_PTR( row ); //qDebug() << "Matrix: resize() -- resizing each row"; for (int i=0;i=m_rows, corner case (will be deleted). Setting to RAND_MAX." // << "New item value (" << i+1 << ", " << j+1 << ") ="<< item(i, j) ; } else if (i=erased) { setItem( i, j, item(i,j+1) ) ; // qDebug() << "Matrix:deleteRowColumn() -" // <<"j>=erased, shifting column left" // << "New item value (" << i+1 << ", " << j+1 << ") ="<< item(i, j) ; } else if (i>=erased && j=erased, shifting rows up." // << "New item value (" << i+1 << ", " << j+1 << ") ="<< item(i, j) ; } else if (i>=erased && j>=erased) { setItem( i, j, item(i+1,j+1) ) ; // qDebug() << "Matrix:deleteRowColumn() -" // <<"both i,j>=erased, shifting row up and column left." // << "New item value (" << i+1 << ", " << j+1 << ") ="<< item(i, j) ; } } row[i].setSize(m_cols); } qDebug() << "Matrix:deleteRowColumn() - finished, new matrix:"; } /** * @brief Fills a matrix with a given value * @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 Subtracts this matrix from I and returns * * @return I-this to this matrix */ 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 - * Subtract this matrix - B of the same dim and returns the result S * Allows S = A-B * @param b * @return Matrix S */ Matrix& Matrix::operator -(Matrix & b) { Matrix *S = new Matrix(rows(), cols() ); qDebug()<< "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 * * Multiplies (right) this matrix with given matrix b. * Allows P = A * B where A,B of same dimension * and returns product as a reference to the calling object * @param b * @param symmetry * @return */ Matrix& Matrix::operator *(Matrix & b) { qDebug()<< "Matrix::operator *"; Matrix *P = new Matrix(rows(), cols()); if ( cols() != b.rows() ) { qDebug()<< "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 * and returns the product in the calling matrix which becomes an m x p matrix. * This convenience operator *= allows A *= B * @param b * @param symmetry * @return */ void Matrix::operator *=(Matrix & b) { qDebug()<< "Matrix::operator *"; if ( cols() != b.rows() ) { qDebug()<< "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 to the calling matrix which becomes * an m x p matrix * Allows P.product(A, B) * @param A * @param B * @param symmetry * @return i x k matrix */ void Matrix::product(Matrix &A, Matrix & B, bool symmetry) { qDebug()<< "Matrix::product() - symmetry" << symmetry; if (A.cols() != B.rows() ) { qDebug()<< "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 ); } } } // qDebug() << "Matrix::product() - this"; *this = *P; //this->printMatrixConsole(); } /** * @brief Takes two ( N x N ) matrices (symmetric) and outputs an upper triangular matrix * @param a * @param b * @return */ 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-nth power of this matrix * @param n * @param symmetry * @return Matrix */ Matrix& Matrix::pow (int n, bool symmetry) { if (rows()!= cols()) { qDebug()<< "Matrix::pow() - Error. This works only for square matrix"; return *this; } qDebug()<< "Matrix::pow() "; Matrix X, Y; //auxilliary matrices qDebug()<< "Matrix::pow() - creating X = this"; X=*this; //X = this //X.printMatrixConsole(true); qDebug()<< "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. */ Matrix& Matrix::expBySquaring2 (Matrix &Y, Matrix &X, int n, bool symmetry) { if (n==1) { qDebug() <<"Matrix::expBySquaring2() - n = 1. Computing PM = X*Y where " "X = " ; //X.printMatrixConsole(); //qDebug() <<"Matrix::expBySquaring2() - n = 1. And Y = "; //Y.printMatrixConsole(); Matrix *PM = new Matrix(rows(), cols()); PM->product(X, Y, symmetry); //qDebug()<<"Matrix::expBySquaring2() - n = 1. PM = X*Y =" ; //PM->printMatrixConsole(); return *PM; } else if ( n%2 == 0 ) { //even qDebug()<<"Matrix::expBySquaring2() - even n =" << n << "Computing PM = X * X"; Matrix PM(rows(), cols()); PM.product(X,X,symmetry); //qDebug()<<"Matrix::expBySquaring2() - even n =" << n << ". PM = X * X = " ; //PM.printMatrixConsole(); return expBySquaring2 ( Y, PM, n/2 ); } else { //odd qDebug()<<"Matrix::expBySquaring2() - odd n =" << n << "First compute PM = X * Y"; Matrix PM(rows(), cols()); Matrix PM2(rows(), cols()); PM.product(X,Y,symmetry); //qDebug()<<"Matrix::expBySquaring2() - odd n =" << n << ". PM = X * Y = " ; //PM.printMatrixConsole(); qDebug()<<"Matrix::expBySquaring2() - odd n =" << n << "Now compute PM2 = X * X"; PM2.product(X,X,symmetry); //qDebug()<<"Matrix::expBySquaring2() - odd n =" << n << ". PM2 = X * X = " ; //PM2.printMatrixConsole(); return expBySquaring2 ( PM, PM2, (n-1)/2 ); } } /** * @brief Calculates the matrix-by-vector product Ax of this matrix * Default product: Ax * if leftMultiply=true then it returns the left product xA * @param in input array/vector * @param out output array * @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). * 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 * @param x * @param xsum * @param xmax * @param xmaxi * @param xmin * @param xmini * @param eps * @param maxIter */ void Matrix::powerIteration ( qreal x[], qreal &xsum, qreal &xmax, int &xmaxi, qreal &xmin, int &xmini, const qreal eps, const int &maxIter) { qDebug() << "Matrix::powerIteration() - maxIter" << maxIter <<"initial x" << x; int n = rows(); qreal norm = 0, distance=0; qreal *tmp; tmp=new (nothrow) qreal [n]; Q_CHECK_PTR( tmp ); xsum = 0; int iter = 0; do { qDebug() << "Matrix::powerIteration() - iteration" << iter ; // calculate the matrix-by-vector product Ax and // store the result to vector tmp productByVector(x, tmp, false); qDebug() << "Matrix::powerIteration() - tmp = Ax =" << tmp; // calculate the euclidean length of the resulting vector // which will be the denominator in the vector normalization norm = distanceEuclidean(tmp, n); qDebug() << "Matrix::powerIteration() - norm" << norm; // norm should never be zero, but in case there is // numerical error, we set it to 1 if (!norm) { qDebug() << "### 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; } qDebug() << "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; } } qDebug() << "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); delete [] tmp; } /** * @brief Returns the Transpose of this matrix * Allows T = A.transpose() * @param b * @return Matrix T */ Matrix& Matrix::transpose() { Matrix *T = new Matrix(cols(), rows()); //T->zeroMatrix(cols(), rows()); qDebug()<< "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() * @param b * @return Matrix T */ Matrix& Matrix::cocitationMatrix() { Matrix *T = new Matrix(cols(), rows()); qDebug()<< "Matrix::cocitationMatrix() this transpose"; //this->transpose().printMatrixConsole(); T->product(this->transpose(),*this, true); return *T; } /** * @brief Returns the Degree Matrix of this matrix. * The Degree Matrix is diagonal matrix which contains information about the degree * of each graph vertex (row of the adjacency matrix) * Allows S = A.degreeMatrix() * @param b * @return Matrix S */ Matrix& Matrix::degreeMatrix() { Matrix *S = new Matrix(rows(), cols()); qDebug()<< "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. * The Laplacian is a NxN matrix L = D - A where D is the degree matrix of A * Allows S = A.laplacianMatrix() * @param b * @return Matrix S */ Matrix& Matrix::laplacianMatrix() { Matrix *S = new Matrix(rows(), cols()); //S->zeroMatrix(rows(), cols()); qDebug()<< "Matrix::laplacianMatrix()"; *S = (this->degreeMatrix()) - *this; return *S; } /** * @brief Inverts given matrix A by Gauss Jordan elimination Input: matrix A Output: matrix A becomes unit matrix *this becomes the invert of A and is returned back. * @param A * @return inverse matrix of A */ Matrix& Matrix::inverseByGaussJordanElimination(Matrix &A){ qDebug()<< "Matrix::inverseByGaussJordanElimination()"; int n=A.cols(); qDebug()<<"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); qDebug() << "inverseByGaussJordanElimination() at column " << j+1 << " Initial pivot " << m_pivot ; for ( int i=l; i qFabs ( m_pivot ) ) { qDebug() << " 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); } qDebug()<<" 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) ); qDebug()<<" A.item("<< j+1 << ","<< k+1 << ") = " << A.item(j,k); qDebug()<<" item("<< j+1 << ","<< k+1 << ") = " << item(j,k); } qDebug() << "eliminate variables FromRowsBelow()" << j+1 ; for ( int i=0; i< rows(); i++) { qDebug()<<" 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. qDebug () << "Matrix::ludcmp() - loop over row to get scaling info" ; for (i=0;i big) big=temp; } if (big == 0) // No nonzero largest element. { qDebug() << "Matrix::ludcmp() - Singular matrix in routine ludcmp"; return false; } vv[i]=1.0/big; // Save the scaling. qDebug () << "Matrix::ludcmp() - big element in row i " << i+1 << " is "<< big << " row scaling vv[i] " << vv[i]; } qDebug () << "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; qDebug () << "Matrix::ludcmp() - found new largest pivot at row " << imax+1 << " big " << temp; } } qDebug () << "Matrix::ludcmp() - check for row interchange "; if (j != imax) // Do we need to interchange rows? { qDebug () << "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]; qDebug() << "Matrix::lubksb() backsubstitution: "<< "i " << i << " b[i] " << b[i] << "sum " << sum ; for ( j=i+1;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); qDebug () << "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; qDebug() << "Matrix::solve() - matrix a singular - RETURN"; return false ; } // qDebug () << "Matrix::solve() - call lubksb"; lubksb(*A, n, indx, b); // qDebug () << "Matrix::solve() - finished!"; return true; } /** * @brief Computes the dissimilarities matrix of the variables (rows, columns, both) * of this matrix using the user defined metric * @param metric * @param varLocation * @param diagonal * @param considerWeights * @return */ Matrix& Matrix::distancesMatrix(const int &metric, const QString varLocation, const bool &diagonal, const bool &considerWeights) { Q_UNUSED(considerWeights); Matrix *T = new Matrix(cols(), rows()); qDebug()<< "Matrix::distancesMatrix() -" <<"metric"<< metric << "varLocation"<< varLocation << "diagonal"< mean (N,0); // holds mean values qDebug()<< "Matrix::distancesMatrix() - input matrix:"; //this->printMatrixConsole(); for (int i = 0 ; i < N ; i++ ) { sum = 0 ; for (int k = i ; k < N ; k++ ) { distTemp = 0; ties = 0; max = 0; for (int j = 0 ; j < N ; j++ ) { // qDebug() << "(i,j)" << i<< ","< 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; } // qDebug() << "distTemp("< mean (N,0); // holds mean values qDebug()<< "Matrix::distancesMatrix() -" <<"input matrix"; //printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { 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; } // qDebug() << "distTemp("< 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)); } } qDebug()<< "Matrix::distancesMatrix() -" <<"input matrix"; //CM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { 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; } // qDebug() << "distTemp("<printMatrixConsole(); return *T; } /** * @brief Computes the pair-wise matching score of the rows, columns * or both of the given matrix AM, based on the given matching measure * and returns the similarity matrix. * @param AM Matrix * @return Matrix nxn with matching scores for every pair of rows/columns of AM */ Matrix& Matrix::similarityMatrix(Matrix &AM, const int &measure, const QString varLocation, const bool &diagonal, const bool &considerWeights){ Q_UNUSED(considerWeights); qDebug()<< "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 qDebug()<< "Matrix::similarityMatrix() -" <<"input matrix"; //AM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { 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; } qDebug() << "matches("<zeroMatrix(N,N); QList mean (N,0); // holds mean values qDebug()<< "Matrix::similarityMatrix() -" <<"input matrix"; //AM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { 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; } qDebug() << "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)); } } qDebug()<< "Matrix::similarityMatrix() -" <<"input matrix"; //CM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { 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; } qDebug() << "matches("<zeroMatrix(N,N); QList mean (N,0); // holds mean values QList sigma(N,0); qDebug()<< "Matrix::pearsonCorrelationCoefficients() -" <<"input matrix"; //AM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { for (int k = i ; k < N ; k++ ) { qDebug() << "comparing rows i"<zeroMatrix(N,N); QList mean (N,0); // holds mean values QList sigma(N,0); qDebug()<< "Matrix::pearsonCorrelationCoefficients() -" <<"input matrix"; //AM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { for (int k = i ; k < N ; k++ ) { qDebug() << "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)); } } qDebug()<< "Matrix::pearsonCorrelationCoefficients() -" <<"input matrix"; //CM.printMatrixConsole(true); for (int i = 0 ; i < N ; i++ ) { //a column for (int k = i ; k < N ; k++ ) { // next column qDebug() << "comparing augmented columns i"<=N && ( (i+N)==j || (k+N)==j )) { qDebug() << "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(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 Prints this matrix as HTML table * WARNING: DO NOT USE WHEN THE NETWORK CONTAINS DISABLED/DELETED NODES * It has the problem that the real actorNumber != elementLabel i.e. when we * have deleted a node/vertex * @param os * @param debug * @return */ bool Matrix::printHTMLTable(QTextStream& os, const bool markDiag, const bool &plain, const bool &printInfinity){ qDebug() << "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 to stderr or stdout * @return */ bool Matrix::printMatrixConsole(bool debug){ qDebug() << "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 if matrix is ill-defined (contains at least an inf element) * @return */ bool Matrix::illDefined(){ qDebug() << "Matrix::illDefined() " ; for (int r = 0; r < rows(); ++r) { for (int c = 0; c < cols(); ++c) { if ( item(r,c) < RAND_MAX ) { } else { qDebug() << "Matrix::illDefined() - matrix ill-defined: TRUE" ; return true; } } } return false; } socnetv-app-39db829/src/matrix.h000077500000000000000000000143741517721000100165710ustar00rootroot00000000000000/** * @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::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; class MatrixRow { public: MatrixRow (int cols=0) { cell=new (nothrow) qreal [m_cols=cols]; Q_CHECK_PTR( cell ); for (int i=0;i. * @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; Parser::Parser() { qDebug() << "Parser constructor, on thread:" << this->thread(); } Parser::~Parser() { qDebug() << "**** 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) { qDebug() << "**** 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}; qDebug() << "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; qDebug() << "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 qDebug() << "Opening file..."; QFile file(cfg.fileName); if (!file.open(QIODevice::ReadOnly)) { qint64 elapsedTime = computationTimer.elapsed(); qDebug() << "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 qDebug() << "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) { qDebug() << "[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; } qDebug() << "**** Parser finished. Emitting finished() signal. "; emit finished("Parser::load() - reach end"); } socnetv-app-39db829/src/parser.h000077500000000000000000000162611517721000100165560ustar00rootroot00000000000000/** * @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 "graph/io/graph_parse_sink.h" class QXmlStreamReader; class QXmlStreamAttributes; /** * @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; // qDebug () << t1.value << " > " << t2.value << "?" // << ( t1.value > t2.value ) ; 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 &, QString &label, QString &shape, QString &color, QString &fontName, QString &fontColor); 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-app-39db829/src/parser/000077500000000000000000000000001517721000100163745ustar00rootroot00000000000000socnetv-app-39db829/src/parser/parser_adjacency.cpp000066400000000000000000000566061517721000100224120ustar00rootroot00000000000000/** * @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) { qDebug() << "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"); } } qDebug() << "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; } qDebug() << "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; } qDebug() << "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)) { qDebug() << 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(); qDebug() << "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); } qDebug() << "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) { qDebug() << "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; qDebug() << "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) { qDebug() << "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) { qDebug() << "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); } qDebug() << "parseAsTwoModeSociomatrix done." << "totalNodes" << totalNodes << "totalLinks" << totalLinks; return true; }socnetv-app-39db829/src/parser/parser_common.cpp000066400000000000000000000022641517721000100217500ustar00rootroot00000000000000/** * @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()) { qDebug() << "Parser::isComment() - Comment or an empty line was found. " "Skipping..."; return true; } return false; } socnetv-app-39db829/src/parser/parser_dl.cpp000066400000000000000000001203461517721000100210610ustar00rootroot00000000000000/** * @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) { qDebug() << "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++; qDebug() << "actualLineNumber " << actualLineNumber << "str.simplified: \n" << str; if (actualLineNumber == 1) { if (!str.startsWith("DL", Qt::CaseInsensitive)) { qDebug() << "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(",")) { qDebug() << "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("=")) { qDebug() << "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) { qDebug() << "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") { qDebug() << "N is declared to be : " << value; totalNodes = value.toInt(&intOK, 10); if (!intOK) { qDebug() << "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") { qDebug() << "NM is declared to be : " << value; NM = value.toInt(&intOK, 10); if (!intOK) { qDebug() << "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") { qDebug() << "NR is declared to be : " << value; NR = value.toInt(&intOK, 10); if (!intOK) { qDebug() << "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") { qDebug() << "NC is declared to be : " << value; NC = value.toInt(&intOK, 10); if (!intOK) { qDebug() << "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") { qDebug() << "FORMAT is declared to be : " << value; if (value.contains("FULLMATRIX", Qt::CaseInsensitive)) { fullmatrixFormat = true; edgelist1Format = false; qDebug() << "βœ… FORMAT: FullMatrix detected"; } else if (value.contains("edgelist", Qt::CaseInsensitive)) { edgelist1Format = true; fullmatrixFormat = false; qDebug() << "βœ… FORMAT: EdgeList detected"; } else { qDebug() << "❌ 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; qDebug() << "βœ… 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) { qDebug() << "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) { qDebug() << "βœ… 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; qDebug() << "START LABELS RECOGNITION " "AND NODE CREATION"; continue; } else if (str.startsWith("COLUMN LABELS", Qt::CaseInsensitive)) { colLabels_flag = true; rowLabels_flag = false; data_flag = false; relation_flag = false; qDebug() << "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; qDebug() << "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; qDebug() << "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; qDebug() << "matrix labels not supported"; continue; } else if (str.isEmpty()) { qDebug() << "EMPTY STRING - CONTINUE"; continue; } if (rowLabels_flag) { // try to read row labels label = str.trimmed().simplified(); if (rowLabels.contains(label)) { qDebug() << "⚠ Warning: Duplicate row label '" << label << "' found. Ignoring."; continue; } else { qDebug() << "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)) { qDebug() << "col label exists. CONTINUE"; continue; } else { qDebug() << "Adding col label " << label << " to colLabels"; colLabels << label; } } else if (relation_flag) { relation = str; if (relationsList.contains(relation)) { qDebug() << "relation exists. CONTINUE"; continue; } else { qDebug() << "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) { // check if there were NR and NC declared (then this is two-mode) qDebug() << "check if NR != 0 (two mode net)."; if (NR != 0 && NC != 0) { twoMode_flag = true; qDebug() << "this is a two-mode net."; // TODO: Check two-mode networks... } // check if we have found row labels if (rowLabels.size() == 0) { // no labels found qDebug() << "Nodes have not been created yet." << "No node labels found." << "Calling createRandomNodes(N) for all"; createRandomNodes(1, QString(), totalNodes); nodeSum = totalNodes; } else if (rowLabels.size() == 1) { // only one label line was found // probably contains a comma to separate labels // split it qDebug() << "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 qDebug() << "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) { // check if we have found col labels if (colLabels.size() == 0) { // no col labels found qDebug() << "Nodes have not been created yet." << "No node labels found." << "Calling createRandomNodes(NC) for all columns"; createRandomNodes(totalNodes, QString(), NC); } else if (colLabels.size() == 1) { // only one col label line was found // probably contains a comma to separate labels // split it qDebug() << "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 qDebug() << "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) { qDebug() << "❌ 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) { qDebug() << "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); qDebug() << "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) { qDebug() << "❌ ERROR: Mismatch in matrix row size at line" << fileLineNumber << ". Expected" << totalNodes << "columns but found" << lineElement.size(); qDebug() << "πŸ” 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) { qDebug() << "we are at source 1. " "Checking relationList"; relation = relationsList[relationCounter]; qDebug() << "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]; qDebug() << "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 { qDebug() << "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) qDebug() << "Diagonal element at (" << source << "," << target << ") with value " << edgeWeight; if (diagonalPresent && edgeWeight > 0) { // Create self-loop only if DIAGONAL PRESENT and value is non-zero qDebug() << "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) { qDebug() << "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++; qDebug() << "TotalLinks= " << totalLinks; } } target++; } // end for source++; } else { // two-mode target = NR + 1; qDebug() << "this is a two-mode fullmatrix file. " "Splitting str to elements:"; myRegExp.setPattern("\\s+"); lineElement = str.split(myRegExp, Qt::SkipEmptyParts); qDebug() << "lineElement:" << lineElement; if (lineElement.size() != NC) { qDebug() << "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) { qDebug() << "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++; qDebug() << "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); qDebug() << "edgelist str line:" << str; qDebug() << "edgelist data element:" << lineElement; if (lineElement.size() != 3) { qDebug() << "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); qDebug() << "source node " << source << " target node " << target; edgeWeight = (lineElement[2]).toDouble(&conversionOK); if (conversionOK) { qDebug() << "list file declares edge weight: " << edgeWeight; } else { edgeWeight = 1.0; qDebug() << " list file NOT declaring edge weight. Setting default: " << edgeWeight; } qDebug() << "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(); qDebug() << "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); qDebug() << "element:" << tempStr.toLatin1(); // Check for standalone DIAGONAL token if (tempStr.compare("DIAGONAL", Qt::CaseInsensitive) == 0) { qDebug() << "βœ… Found standalone DIAGONAL token"; diagonalPresent = true; continue; } if (tempStr.startsWith("DL", Qt::CaseInsensitive)) { // remove DL tempStr.remove("DL", Qt::CaseInsensitive); tempStr = tempStr.simplified(); qDebug() << "element contained DL. Removed it:" << tempStr; } // check if this element contains a "=" if (tempStr.size() > 0) { if (tempStr.contains("=", Qt::CaseInsensitive)) { qDebug() << "splitting element at = sign"; tempList = tempStr.split("=", Qt::SkipEmptyParts); label = tempList[0].simplified(); value = tempList[1].simplified(); if (label == "n" || label == "N") { qDebug() << "N is declared to be : " << value; N = value.toInt(&intOK, 10); if (!intOK) { qDebug() << "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") { qDebug() << "NM is declared to be : " << value; NM = value.toInt(&intOK, 10); if (!intOK) { qDebug() << "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") { qDebug() << "NR is declared to be : " << value; NR = value.toInt(&intOK, 10); if (!intOK) { qDebug() << "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") { qDebug() << "NC is declared to be : " << value; NC = value.toInt(&intOK, 10); if (!intOK) { qDebug() << "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") { qDebug() << "FORMAT is declared to be : " << value; // Check if DIAGONAL PRESENT is specified in the format if (value.contains("DIAGONAL", Qt::CaseInsensitive)) { diagonalPresent = true; qDebug() << "βœ… FORMAT: DIAGONAL token found in format value"; } if (value.contains("FULLMATRIX", Qt::CaseInsensitive)) { fullmatrixFormat = true; edgelist1Format = false; qDebug() << "βœ… FORMAT: FullMatrix detected"; } else if (value.contains("edgelist", Qt::CaseInsensitive)) { edgelist1Format = true; fullmatrixFormat = false; qDebug() << "βœ… FORMAT: EdgeList detected"; } else { qDebug() << "❌ 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 qDebug() << "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++) { qDebug() << "Signaling to create multiple nodes. Now signaling for node:" << i + 1; if (m_parseSink) { m_parseSink->createNodeAtPosRandom(false); } } } else { qDebug() << "Signaling to create a single node:" << fixedNum << "with label:" << label; if (m_parseSink) { m_parseSink->createNodeAtPosRandomWithLabel(fixedNum, label, false); } } }socnetv-app-39db829/src/parser/parser_dot.cpp000066400000000000000000000643341517721000100212540ustar00rootroot00000000000000/** * @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) { qDebug() << "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; 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)) { qDebug() << "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++; qDebug() << "πŸ”Ž Reading fileLine " << fileLine; str = ts.readLine().simplified().trimmed(); qDebug() << 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(" 1 && lineElement[1] != "{") networkName = lineElement[1]; qDebug() << "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 = lineElement[1]; qDebug() << "This is a DOT GRAPH named " << networkName; continue; } else { qDebug() << "*** 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 (str.contains("graph [", Qt::CaseInsensitive)) { netProperties = true; qDebug() << "πŸ”΅ Detected global graph settings. Skipping..."; 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)) { qDebug() << "πŸ”΅ 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") qDebug() << "⚠️ 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)) { qDebug() << "πŸ”΅ Detected global node settings..."; readDotProperties( str.mid(str.indexOf('[') + 1, str.indexOf(']') - str.indexOf('[') - 1), nodeValue, nodeLabel, initNodeShape, initNodeColor, fontName, fontColor); qDebug() << "βœ… Default node color set to:" << initNodeColor; continue; } // Global edge defaults if (str.startsWith("edge [", Qt::CaseInsensitive)) { qDebug() << "πŸ”΅ Detected global edge settings..."; readDotProperties( str.mid(str.indexOf('[') + 1, str.indexOf(']') - str.indexOf('[') - 1), edgeWeight, edgeLabel, edgeShape, initEdgeColor, fontName, fontColor); qDebug() << "βœ… 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) { qDebug() << "πŸ”΅ 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('\"'); qDebug() << "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 readDotProperties(temp, nodeValue, nodeLabel, initNodeShape, initNodeColor, fontName, fontColor); if (nodeLabel.isEmpty()) 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; } // 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) { qDebug() << "πŸ”΅ 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; qDebug() << "πŸ”΅ Edge definition found ..."; // Parse optional attribute block [...] end = str.indexOf('['); if (end != -1) { temp = str.right(str.size() - end - 1); temp = temp.remove(']').remove(';'); readDotProperties(temp, edgeWeight, edgeLabel, edgeShape, edgeColor, fontName, fontColor); 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); } } 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(); readDotProperties(temp, nodeValue, label, nodeShape, nodeColor, fontName, fontColor); if (start > 2) { node = str.left(start).remove('\"').simplified(); if (!nodesDiscovered.contains(node)) { totalNodes++; randX = rand() % gwWidth; randY = rand() % gwHeight; if (m_parseSink) { m_parseSink->createNode( totalNodes, initNodeSize, nodeColor, initNodeNumberColor, initNodeNumberSize, label, initNodeLabelColor, initNodeLabelSize, QPointF(randX, randY), nodeShape, QString()); } nodesDiscovered.push_back(node); } } continue; } qDebug() << " 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; qDebug() << "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) { qDebug() << "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 qDebug() << "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(); } qDebug() << "Parsed property:" << prop << "=" << value; // Process the property bool ok = false; if (prop == "label") { label = value; qDebug() << "Set label to:" << label; } else if (prop == "fontname") { fontName = value; qDebug() << "Set fontName to:" << fontName; } else if (prop == "value") { nValue = value.toFloat(&ok); if (ok) { qDebug() << "Set value to:" << nValue; } else { qDebug() << "Error converting value:" << value; } } else if (prop == "color" || prop == "fillcolor") { color = value; qDebug() << "Set color to:" << color; } else if (prop == "fontcolor") { fontColor = value; qDebug() << "Set fontColor to:" << fontColor; } else if (prop == "shape") { shape = value; qDebug() << "Set shape to:" << shape; } else if (prop == "weight") { nValue = value.toFloat(&ok); if (ok) { qDebug() << "Set weight to:" << nValue; } else { qDebug() << "Error converting weight:" << value; } } else if (prop == "style") { qDebug() << "Style property:" << value << "(currently not used)"; } else { qDebug() << "Ignoring unknown property:" << prop << "=" << value; } } } socnetv-app-39db829/src/parser/parser_edgelist.cpp000066400000000000000000000543601517721000100222640ustar00rootroot00000000000000/** * @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) { qDebug() << "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(); qDebug() << "*** Initial file parsing " "to test integrity and edge naming scheme"; while (!ts.atEnd()) { fileLine++; str = ts.readLine().simplified().trimmed(); qDebug() << " 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))) { qDebug() << "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) { qDebug() << "*** 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]; qDebug() << " Dissecting line - " "source:" << edge_source << "target:" << edge_target << "weight:" << edge_weight; if (!edge_source.contains(onlyDigitsExp)) { qDebug() << " node named by non-digit only string. " "nodesWithLabels = true"; nodesWithLabels = true; } if (!edge_target.contains(onlyDigitsExp)) { qDebug() << " node named by non-digit only string. " "nodesWithLabels = true"; nodesWithLabels = true; } } ts.seek(0); fileLine = 0; qDebug() << "*** Initial file parsing finished. " "This is really a weighted edge list. Proceed to main parsing"; while (!ts.atEnd()) { str = ts.readLine(); qDebug() << " str" << str; str = str.simplified(); qDebug() << " simplified str" << str; if (isComment(str)) continue; lineElement = str.split(delimiter); edge_source = lineElement[0]; edge_target = lineElement[1]; edge_weight = lineElement[2]; qDebug() << " 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()); } qDebug() << " source, new node named" << edge_source << "totalNodes" << totalNodes << "nodeMap.count" << nodeMap.size(); } else { qDebug() << " 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()); } qDebug() << " target, new node named" << edge_target << "totalNodes" << totalNodes << "nodeMap.count" << nodeMap.size(); } else { qDebug() << " target already found, continue"; } edgeWeight = edge_weight.toDouble(&conversionOK); if (conversionOK) { qDebug() << " read edge weight" << edgeWeight; } else { edgeWeight = 1.0; qDebug() << " error reading edge weight." "Using default edgeWeight" << edgeWeight; } edgeKey = edge_source + edgeKeyDelimiter + edge_target; if (!edgeList.contains(edgeKey)) { qDebug() << " inserting edgeKey" << edgeKey << "in edgeList with weight" << edgeWeight; edgeList.insert(edgeKey, edgeWeight); totalLinks++; } } // end ts.stream while here qDebug() << " 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) { qDebug() << "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 { qDebug() << "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()) { qDebug() << " 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"); } } qDebug() << " 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) { qDebug() << "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(); qDebug() << "*** Initial file parsing " "to test integrity and edge naming scheme"; while (!ts.atEnd()) { fileLine++; str = ts.readLine().simplified().trimmed(); qDebug() << " 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))) { qDebug() << "*** 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)) { qDebug() << " node named by non-digit only string. " "nodesWithLabels = true"; nodesWithLabels = true; } if (edge_source == "0") { nodesWithLabels = true; } } } ts.seek(0); fileLine = 0; qDebug() << " Reset and read lines. nodesWithLabels" << nodesWithLabels; while (!ts.atEnd()) { fileLine++; str = ts.readLine(); str = str.simplified(); qDebug() << " 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); qDebug() << " 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()); } qDebug() << " source, new node named" << edge_source << "totalNodes" << totalNodes << "nodeMap.count" << nodeMap.size(); } else { qDebug() << " source already found, continue"; } } else { // target nodes edge_target = (*it1); qDebug() << " 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()); } qDebug() << " target, new node named" << edge_target << "totalNodes" << totalNodes << "nodeMap.count" << nodeMap.size(); } else { qDebug() << " target already found, continue"; } } if (columnCount > 1) { edgeKey = edge_source + edgeKeyDelimiter + edge_target; if (!edgeList.contains(edgeKey)) { qDebug() << " 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; qDebug() << " 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) { qDebug() << "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 { qDebug() << "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()) { qDebug() << " 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"); } } qDebug() << " Finished OK. Returning."; return true; } /** * Debugging only - Used while parsing weighted edge lists */ template void print_queue(T &q) { qDebug() << "print_queue() "; while (!q.empty()) { qDebug() << q.top().key << " value: " << q.top().value << " "; q.pop(); } qDebug() << "\n"; } socnetv-app-39db829/src/parser/parser_gml.cpp000066400000000000000000000507571517721000100212510ustar00rootroot00000000000000/** * @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) { qDebug() << "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(); qDebug() << "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))) { qDebug() << "*** 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)) { qDebug() << "graph description list start"; graphKey = true; continue; } if (str.startsWith("directed", Qt::CaseInsensitive)) { if (graphKey) { if (str.contains("1")) { qDebug() << "graph directed 1. A directed graph."; edgeDirType = EdgeType::Directed; } else { qDebug() << "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)) { qDebug() << "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; } qDebug() << "id description " << "This node" << totalNodes << "id" << node_id; } continue; } if (str.startsWith("label ", Qt::CaseInsensitive)) { if (nodeKey) { nodeLabel = str.split(" ", Qt::SkipEmptyParts).last().remove("\""); qDebug() << "node label definition" << "node" << totalNodes << "id" << node_id << "label" << nodeLabel; } else if (edgeKey) { edgeLabel = str.split(" ", Qt::SkipEmptyParts).last(); qDebug() << "edge label definition" << "edge" << totalLinks << "label" << edgeLabel; } continue; } if (str.startsWith("edge ", Qt::CaseInsensitive) || str == "edge") { qDebug() << "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); qDebug() << "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); qDebug() << "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; } qDebug() << "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"); } } qDebug() << "Finished OK. Returning."; return true; } socnetv-app-39db829/src/parser/parser_graphml.cpp000066400000000000000000001064061517721000100221150ustar00rootroot00000000000000/** * @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) { qDebug() << "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); qDebug() << "Testing if XML document encoding is the same as the userSelectedCodec:" << userSelectedCodec; xml.readNext(); if (xml.isStartDocument()) { qDebug() << "XML document version" << xml.documentVersion() << "encoding" << xml.documentEncoding() << "userSelectedCodec" << m_textCodecName; if (xml.documentEncoding().toString() != m_textCodecName) { qDebug() << "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 { qDebug() << "Testing XML: OK"; xml.clear(); xml.addData(rawData); } } while (!xml.atEnd()) { xml.readNext(); qDebug() << "xml.token " << xml.tokenString(); if (xml.isStartDocument()) { qDebug() << "xml startDocument" << " version " << xml.documentVersion() << " encoding " << xml.documentEncoding(); } if (xml.isStartElement()) { qDebug() << "element name " << xml.name().toString(); if (xml.name().toString() == "graphml") { qDebug() << "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.")); qDebug() << "### 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.")); qDebug() << "### 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()) { qDebug() << "### 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(); qDebug() << "signaling to change to the first relation..."; if (m_parseSink) { m_parseSink->setRelation(0); } qDebug() << "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) { qDebug() << "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 qDebug() << "line:" << xml.lineNumber(); if (xml.isStartElement()) { // new token (graph, node, or edge) here qDebug() << "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 qDebug() << " 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()) { qDebug() << "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(); qDebug() << "Parsing graph element - edgedefault " << defaultDirection; if (defaultDirection == "undirected") { qDebug() << "this is an undirected graph "; edgeDirType = EdgeType::Undirected; arrows = false; } else { qDebug() << "this is a directed graph "; edgeDirType = EdgeType::Directed; arrows = true; } // store graph id networkName = xmlStreamAttr.value("id").toString(); // add it as relation relationsList << networkName; qDebug() << "Signaling to add new relation:" << networkName; if (m_parseSink) { m_parseSink->addNewRelation(networkName); } int lastRelationIndex = relationsList.size() - 1; if (lastRelationIndex > 0) { totalNodes = 0; qDebug() << "last relation index:" << lastRelationIndex << "signaling to change to the new relation"; if (m_parseSink) { m_parseSink->setRelation(lastRelationIndex); } } qDebug() << "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(); qDebug() << "Reading key element - key id" << key_id; key_what = xmlStreamAttr.value("for").toString(); keyFor[key_id] = key_what; qDebug() << "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; qDebug() << "key attr.name" << key_name; } if (xmlStreamAttr.hasAttribute("attr.type")) { key_type = xmlStreamAttr.value("attr.type").toString(); keyType[key_id] = key_type; qDebug() << "key attr.type" << key_type; } else if (xmlStreamAttr.hasAttribute("yfiles.type")) { key_type = xmlStreamAttr.value("yfiles.type").toString(); keyType[key_id] = key_type; qDebug() << "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 qDebug() << "Reading default key values - key default value is" << key_value; if (keyFor.value(key_id) == "node") { if (key_name == "size") { qDebug() << "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") { qDebug() << "key default value" << key_value << "is for nodes shape"; initNodeShape = key_value; } if (key_name == "custom-icon") { qDebug() << "key default value" << key_value << "is for node custom-icon path"; initNodeCustomIcon = key_value; initNodeCustomIcon = fileDirPath + "/" + initNodeCustomIcon; qDebug() << "initNodeCustomIcon full path:" << initNodeCustomIcon; if (QFileInfo::exists(initNodeCustomIcon)) { qDebug() << "custom icon file exists!"; } else { qDebug() << "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") { qDebug() << "key default value" << key_value << "is for nodes color"; initNodeColor = key_value; } if (key_name == "label.color") { qDebug() << "key default value" << key_value << "is for node labels color"; initNodeLabelColor = key_value; } if (key_name == "label.size") { qDebug() << "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") { qDebug() << "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") { qDebug() << "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++; // qDebug()<< "reading node id"<< node_id // << "index" << totalNodes // << "added to nodeHash" // << "gwWidth, gwHeight "<< gwWidth<< "," < 1) { qDebug() << "multirelational data" "skipping node creation. Node should have been created in earlier relation"; bool_node = false; return; } qDebug() << "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(); // qDebug()<< "Parsing edge id: " // << xmlStreamAttr.value("id").toString() // << "edge_source " << edge_source // << "edge_target " << edge_target // << "directed " << edge_directed; 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; qDebug() << "Edge is UNDIRECTED"; } else { edgeDirType = EdgeType::Directed; qDebug() << "Edge is DIRECTED"; } if (!nodeHash.contains(edge_source)) { qDebug() << "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)) { qDebug() << "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]; qDebug() << "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) { qDebug() << "missingNode true " << " postponing edge creation signal"; return; } qDebug() << "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(); qDebug() << "parding data for key_id: " << key_id << "key_value " << key_value; if (key_value.trimmed() == "") { qDebug() << "empty key_value: " << key_value << "reading more xml.text()..."; xml.readNext(); key_value = xml.text().toString(); qDebug() << "now key_value: " << key_value; if (key_value.trimmed() != "") { // if there's simple text after the StartElement, qDebug() << "key_id " << key_id << " value is simple text " << key_value; } else { // no text, probably more tags. Return... qDebug() << "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") { qDebug() << "Data found. Node color: " << key_value << " for this node"; nodeColor = key_value; } else if (key_name == "label") { qDebug() << "Data found. Node label: " "" << key_value << " for this node"; nodeLabel = key_value; } else if (key_name == "x_coordinate") { qDebug() << "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; qDebug() << "Using: " << randX; } else if (key_name == "y_coordinate") { qDebug() << "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; qDebug() << "Using: " << randY; } else if (key_name == "size") { qDebug() << "Data found. Node size: " << key_value << " for this node"; conv_OK = false; nodeSize = key_value.toInt(&conv_OK); if (!conv_OK) nodeSize = initNodeSize; qDebug() << "Using: " << nodeSize; } else if (key_name == "label.size") { qDebug() << "Data found. Node label size: " << key_value << " for this node"; conv_OK = false; nodeLabelSize = key_value.toInt(&conv_OK); if (!conv_OK) nodeLabelSize = initNodeLabelSize; qDebug() << "Using: " << nodeSize; } else if (key_name == "label.color") { qDebug() << "Data found. Node label Color: " << key_value << " for this node"; nodeLabelColor = key_value; } else if (key_name == "shape") { qDebug() << "Data found. Node shape: " << key_value << " for this node"; nodeShape = key_value; } else if (key_name == "custom-icon") { qDebug() << "Data found. Node custom-icon path: " << key_value << " for this node"; nodeIconPath = key_value; nodeIconPath = fileDirPath + ("/") + nodeIconPath; qDebug() << "full node custom-icon path: " << nodeIconPath; } else { qDebug() << "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") { qDebug() << "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)); } qDebug() << "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; qDebug() << "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)); } qDebug() << "Data found. Edge label: " << edgeLabel << " for this edge"; } else { qDebug() << "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) { qDebug() << "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; } qDebug() << "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; qDebug() << "Node Size: " << temp << " Using nodesize" << nodeSize; } if (xmlStreamAttr.hasAttribute("shape")) { nodeShape = xmlStreamAttr.value("shape").toString(); qDebug() << "Node Shape: " << nodeShape; } } else if (xml.name().toString() == "Fill") { if (xmlStreamAttr.hasAttribute("color")) { nodeColor = xmlStreamAttr.value("color").toString(); qDebug() << "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()) { qDebug() << "Node Label " << key_value; nodeLabel = key_value; } else { qDebug() << "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(); qDebug() << "Node shape: " << nodeShape; } } } /** * @brief Reads edge graphics data and properties: path, linestyle,width, arrows, etc * @param xml */ void Parser::readGraphMLElementEdgeGraphics(QXmlStreamReader &xml) { qDebug() << "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; } qDebug() << "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(); qDebug() << "Edge color: " << edgeColor; } if (xmlStreamAttr.hasAttribute("type")) { edgeType = xmlStreamAttr.value("type").toString(); qDebug() << "Edge type: " << edgeType; } if (xmlStreamAttr.hasAttribute("width")) { temp = xmlStreamAttr.value("width").toString().toFloat(&conv_OK); if (conv_OK) edgeWeight = temp; else edgeWeight = 1.0; qDebug() << "Edge width: " << edgeWeight; } } else if (xml.name().toString() == "Arrows") { if (xmlStreamAttr.hasAttribute("source")) { tempString = xmlStreamAttr.value("source").toString(); qDebug() << "Edge source arrow type: " << tempString; } if (xmlStreamAttr.hasAttribute("target")) { tempString = xmlStreamAttr.value("target").toString(); qDebug() << "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()) { qDebug() << "Edge Label " << key_value; // probably there's more than simple text after StartElement edgeLabel = key_value; } else { qDebug() << "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()); qDebug() << "unknown element found:" << xml.name().toString(); } /** * @brief Creates any missing node edges */ void Parser::createMissingNodeEdges() { qDebug() << "Creating missing node edges... "; int count = 0; if ((count = edgesMissingNodesHash.size()) > 0) { bool ok; edgeWeight = initEdgeWeight; edgeColor = initEdgeColor; edgeDirType = EdgeType::Directed; qDebug() << "edges to create " << count; QHash::const_iterator it = edgesMissingNodesHash.constBegin(); while (it != edgesMissingNodesHash.constEnd()) { qDebug() << "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; } qDebug() << "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 { qDebug() << "nothing to do"; } } socnetv-app-39db829/src/parser/parser_pajek.cpp000066400000000000000000001017621517721000100215550ustar00rootroot00000000000000/** * @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) { qDebug() << "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++; qDebug() << "*** 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) { qDebug() << "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; }; qDebug() << 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; qDebug() << "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) { qDebug() << "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) { // qDebug("=== Reading nodes ==="); nodes_flag = true; nodeNum = lineElement[0].toInt(&intOk, 10); // qDebug()<<"node number: "<createNode(num, initNodeSize, nodeColor, initNodeNumberColor, initNodeNumberSize, label, lineElement[3], initNodeLabelSize, QPointF(randX, randY), nodeShape, QString()); } listDummiesPajek.push_back(num); miss++; } } else if (j > nodeNum) { qDebug("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; } qDebug() << "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 qDebug() << "*** 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. qDebug() << "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; qDebug() << "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 */ qDebug("==== Reading edges ===="); qDebug() << 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; } // qDebug()<<"weight "<< weight; if (lineElement.contains("c", Qt::CaseSensitive)) { // qDebug("file with link colours"); 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; // qDebug()<< " current color "<< edgeColor; } else { // qDebug("file with no link colours"); edgeColor = initEdgeColor; } if (lineElement.contains("l", Qt::CaseSensitive)) { qDebug("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; qDebug() << " edge label " << edgeLabel; } else { // qDebug("file with no link labels"); edgeLabel = initEdgeLabel; } arrows = false; bezier = false; qDebug() << "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 */ // qDebug("=== Reading 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)) { // qDebug("file with link colours"); myRegExp.setPattern("[c]"); edgeColor = lineElement.at(lineElement.indexOf(myRegExp, 0) + 1); fileContainsLinkColors = true; } else { // qDebug("file with no link colours"); edgeColor = initEdgeColor; } if (lineElement.contains("l", Qt::CaseSensitive)) { qDebug("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; qDebug() << " edge label " << edgeLabel; } else { // qDebug("file with no link labels"); edgeLabel = initEdgeLabel; } arrows = true; bezier = false; has_arcs = true; qDebug() << "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 */ // qDebug("=== Reading arcs list==="); 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); qDebug() << "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 */ // qDebug("=== Reading matrix of edges==="); 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); qDebug() << " 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; } qDebug("Removing all dummy nodes, if any"); if (listDummiesPajek.size() > 0) { qDebug("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); } } qDebug() << "Clearing temporary dummies and relations list"; listDummiesPajek.clear(); relationsList.clear(); qDebug() << "signaling to change to the first relation..."; if (m_parseSink) { m_parseSink->setRelation(0); } if (has_arcs) { edgeDirType = EdgeType::Directed; } else { edgeDirType = EdgeType::Undirected; } qDebug() << "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-app-39db829/src/qss/000077500000000000000000000000001517721000100157065ustar00rootroot00000000000000socnetv-app-39db829/src/qss/default.qss000066400000000000000000000354521517721000100200730ustar00rootroot00000000000000/* * 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: yellow; width: 10px; /* when vertical */ height: 10px; /* when horizontal */ } QMainWindow::separator:hover { background: #babdbe; } 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#toolBoxLayoutByIndexApplyButton, QPushButton#toolBoxLayoutForceDirectedApplyButton { subcontrol-origin: padding; background: #fff; border: 1px solid rgba(0, 0, 0, 0.1); color: #222; padding: 3px 0px; margin:0; min-width: 128px; min-height: 2.5ex; text-align:center; } QPushButton#toolBoxLayoutForceDirectedApplyButton:hover, QPushButton#toolBoxLayoutByIndexApplyButton:hover { background-color: #eaeaea; } QPushButton#toolBoxLayoutForceDirectedApplyButton:pressed, QPushButton#toolBoxLayoutByIndexApplyButton:pressed { background-color:#007ac1; color: #fff; } 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; } 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; } 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; } socnetv-app-39db829/src/texteditor.cpp000077500000000000000000000247021517721000100200070ustar00rootroot00000000000000/** * @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 "texteditor.h" TextEditor::TextEditor(const QString &fileName, QWidget *parent, const bool &format) : QMainWindow(parent), formatHTML(format) { qDebug("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-app-39db829/src/texteditor.h000077500000000000000000000036121517721000100174510ustar00rootroot00000000000000/** * @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-app-39db829/src/tools/000077500000000000000000000000001517721000100162405ustar00rootroot00000000000000socnetv-app-39db829/src/tools/README.md000066400000000000000000000004661517721000100175250ustar00rootroot00000000000000# SocNetV Tools This directory contains headless utilities used for refactoring safety nets. ## socnetv-cli `socnetv-cli` loads a dataset headlessly (no GUI), runs distance metrics, and can dump/compare deterministic JSON golden outputs. See: [SOCNETV_CLI_REGRESSION_TOOL](./SOCNETV_CLI_REGRESSION_TOOL.md)socnetv-app-39db829/src/tools/SOCNETV_CLI_REGRESSION_TOOL.md000066400000000000000000000447121517721000100227770ustar00rootroot00000000000000# SocNetV CLI Regression Tool `socnetv-cli` is a headless regression tool used to verify algorithmic correctness during the ongoing architectural refactor of SocNetV. It provides deterministic execution of compute kernels without loading any UI components. Originally introduced to protect the code refactor we did during WorkStreams WS1 and WS2 [WorkStreams WS1 and WS2](../../docs/ARCHITECTURAL_REFACTORING_ROADMAP.md). It has since evolved into a modular regression harness for multiple algorithm families. --- # 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 kernel only) * 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` 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. --- # 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: * Some file formats do not export yet (e.g. GRAPHVIZ, GML, EDGELIST_*). For those 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 --- # 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. ### 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` as referenced in this doc): * `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. --- ### 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 Examples: ```bash --kernel distance --kernel reachability --kernel walks_matrix --kernel prominence --kernel io_roundtrip --kernel clustering ``` 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`) 3. strict compare against a golden JSON baseline (`--compare-json`) 4. benchmarking (`--bench`, distance kernel only) #### `--dump-json ` 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 ` 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 ``` --- ### Benchmarking (distance kernel 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` * Cannot combine with `--dump-json` * Cannot combine with `--compare-json` Example: ```bash --kernel distance -c 1 -w 1 -x 1 -k 0 --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` only here. ### `--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` (if the implementation supports weighted adjacency behavior; otherwise treat as unweighted by design) * `--dump-json`, `--compare-json` Not supported: * `--bench` ### `--kernel prominence` (schema v4) Allowed: * `-w`, `-x`, `-k` * `--dump-json`, `--compare-json` Notes: * Prominence kernel covers *many* indices; `-w/-x` materially changes several results. ### `--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. ## 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.) 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/ --- # 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 --- # Micro-Benchmarking Mode (Distance Kernel Only) The CLI provides benchmarking for DistanceEngine. ```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` --- # Automated Regression Scripts ## Golden Comparisons ``` scripts/run_golden_compares.sh ``` Validates: * Distance (v1) * Reachability (v2) * Walks (v3) * Prominence (v4) * Clustering (v6) Fails on any mismatch. --- ## Performance Benchmarks ``` scripts/run_benchmarks.sh ``` Validates median compute times for the distance kernel only. Machine-aware baseline sets supported. --- # Baselines Distance baselines: ``` src/tools/baselines/ ``` 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/ ``` See: [BASELINES__README.md](./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-app-39db829/src/tools/baselines/000077500000000000000000000000001517721000100202055ustar00rootroot00000000000000socnetv-app-39db829/src/tools/baselines/BASELINES__README.md000066400000000000000000000141031517721000100232070ustar00rootroot00000000000000# 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 Version | Folder | | ------------ | -------------- | ----------------------------------- | | distance | v1 | `src/tools/baselines/` | | reachability | v2 | `src/tools/baselines/reachability/` | | walks_matrix | v3 | `src/tools/baselines/walks/` | | prominence | v4 | `src/tools/baselines/prominence/` | 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 ``` Where: * `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 DunbarGelada_H22a__WALKS_K6__V3.json ``` --- ## Prominence Kernel (schema v4) ``` __PROM__V4__FT__W<0|1>_IW<0|1>_DI<0|1>.json ``` Where: * `FT` β†’ file type (`-f` argument) * `W` β†’ considerWeights * `IW` β†’ inverseWeights * `DI` β†’ dropIsolates Example: ``` Krackhardt_Kite_N10__PROM__V4__FT2__W0_IW1_DI0.json Krackhardt_Kite_N10__PROM__V4__FT2__W1_IW1_DI0.json ``` Note: * `C` is not encoded for prominence. * Prominence always computes the full set of centrality + prestige indices. --- # 3. Golden Checklist (Pre-Release) Before tagging a release: 1. Build `socnetv-cli` 2. Run all golden comparisons 3. Ensure **no mismatches** Recommended: ``` scripts/run_golden_compares.sh ``` 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 ## Step 1 β€” Generate JSON ### 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/.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 \ -c 0 -w <0|1> -x <0|1> -k <0|1> \ --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 ``` --- ## Step 2 β€” Review * Check graph-level metrics * Confirm deterministic vertex ordering * Confirm numeric plausibility For walks: * Small test networks (e.g., TinyPath_N3_E2) are strongly recommended * Validate known combinatorial results manually For prominence: * Validate well-known datasets (e.g., Krackhardt Kite, Sampson Monks) * Confirm directed vs undirected semantics * Confirm prestige behaves correctly for directed graphs --- ## Step 3 β€” Commit * Add JSON file * Mention dataset + flags in commit message * If replacing an existing baseline, explain why Baselines must correspond to datasets in: ``` src/data/ ``` --- # 5. What Is Verified ## Distance Kernel (v1) ### Graph-Level Metrics * nodes (N) * links_sna * ties_graph * directed / weighted * average geodesic distance * diameter * disconnected_pairs * connected ### Per-Node Metrics (when C=1) For each vertex (ascending deterministic id): * 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 = A^K XM(i,j) = number of walks of exact length K from i to j ``` Note: A long-standing bug in `walksBetween()` was fixed. Older baselines may reflect adjacency values instead of true A^K results. --- ## Prominence Kernel (v4) ### Graph-Level * nodes * links_sna * ties_graph * directed / weighted ### Per-Node (deterministic ascending id) 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 All floating-point values are serialized as strings. --- # 6. Exit Codes & CI Integration `socnetv-cli --compare-json` exits with: * `0` β†’ match * non-zero β†’ mismatch or runtime error This makes it suitable for: * CI pipelines * pre-merge validation * pre-release checklist `scripts/run_golden_compares.sh` aggregates failures and exits non-zero if any mismatch occurs. --- # 7. Existing Baselines ## Distance (v1) Located under: ``` src/tools/baselines/ ``` --- ## Reachability (v2) Located under: ``` src/tools/baselines/reachability/ ``` --- ## Walks (v3) Located under: ``` src/tools/baselines/walks/ ``` --- ## Prominence (v4) Located under: ``` src/tools/baselines/prominence/ ``` Includes: * Tiny validation networks * Krackhardt Kite * Sampson Monks --- # 8. Notes * `LINKS_SNA` reflects loader semantics. * `TIES_GRAPH` reflects canonical Graph model. * Baselines must be generated from identical datasets. * Deterministic ordering is mandatory. * Never update baselines silently. * Schema structures must remain immutable once committed. socnetv-app-39db829/src/tools/baselines/DunbarGelada_H22a__FT2__C1_W0_IW1_DI0.json000066400000000000000000000164161517721000100271530ustar00rootroot00000000000000{ "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, "diameter": 4, "disconnected_pairs": 0 }, "per_node": [ { "BC": "12.472222222222221", "CC": "0.055555555555555552", "EC": "0.33333333333333331", "PC": "7.8333333333333339", "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" }, { "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" }, { "BC": "9.1666666666666661", "CC": "0.058823529411764705", "EC": "0.33333333333333331", "PC": "8.6666666666666661", "SBC": "0.16666666666666666", "SC": "15", "SCC": "0.6470588235294118", "SEC": "0.33333333333333331", "SPC": "0.78787878787878785", "SSC": "0.1875", "distance_sum": "17", "eccentricity": "3", "eccentricity_inf": false, "id": 3, "label": "Adult" }, { "BC": "8.3333333333333339", "CC": "0.0625", "EC": "0.33333333333333331", "PC": "8.8333333333333339", "SBC": "0.15151515151515152", "SC": "14", "SCC": "0.6875", "SEC": "0.33333333333333331", "SPC": "0.80303030303030309", "SSC": "0.17499999999999999", "distance_sum": "16", "eccentricity": "3", "eccentricity_inf": false, "id": 4, "label": "Adult" }, { "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" }, { "BC": "1.2777777777777777", "CC": "0.052631578947368418", "EC": "0.33333333333333331", "PC": "7.6666666666666661", "SBC": "0.023232323232323229", "SC": "4", "SCC": "0.57894736842105265", "SEC": "0.33333333333333331", "SPC": "0.69696969696969691", "SSC": "0.050000000000000003", "distance_sum": "19", "eccentricity": "3", "eccentricity_inf": false, "id": 6, "label": "3" }, { "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" }, { "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" }, { "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" }, { "BC": "3.5", "CC": "0.052631578947368418", "EC": "0.33333333333333331", "PC": "7.3333333333333339", "SBC": "0.06363636363636363", "SC": "5.5", "SCC": "0.57894736842105265", "SEC": "0.33333333333333331", "SPC": "0.66666666666666674", "SSC": "0.068750000000000006", "distance_sum": "19", "eccentricity": "3", "eccentricity_inf": false, "id": 10, "label": "3" }, { "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" }, { "BC": "1.1666666666666665", "CC": "0.047619047619047616", "EC": "0.25", "PC": "7.0833333333333339", "SBC": "0.02121212121212121", "SC": "3", "SCC": "0.52380952380952372", "SEC": "0.25", "SPC": "0.64393939393939403", "SSC": "0.037499999999999999", "distance_sum": "21", "eccentricity": "4", "eccentricity_inf": false, "id": 12, "label": "1" } ], "run": { "computeCentralities": true, "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 1 } socnetv-app-39db829/src/tools/baselines/DunbarGelada_H22a__FT2__C1_W1_IW1_DI0.json000066400000000000000000000163771517721000100271620ustar00rootroot00000000000000{ "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": 1, "load_msg": "", "net_name": "Dunbar&Dunbar_Gelada_baboon_colony_H22a", "ok": true }, "metrics": { "avg_distance": "0.42027859123447353", "connected": true, "diameter": 1, "disconnected_pairs": 0 }, "per_node": [ { "BC": "23", "CC": "0.2512380902620982", "EC": "0.83333333333333337", "PC": "56.855755672539502", "SBC": "0.41818181818181815", "SC": "0", "SCC": "0.092120633096102661", "SEC": "0.83333333333333337", "SPC": "3.3444562160317353", "SSC": "nan", "distance_sum": "3.980288175876411", "eccentricity": "1.2", "eccentricity_inf": false, "id": 1, "label": "Adult" }, { "BC": "0", "CC": "0.20452494736490326", "EC": "0.77464788732394363", "PC": "44.591943775745001", "SBC": "0", "SC": "0", "SCC": "0.07499248070046452", "SEC": "0.77464788732394363", "SPC": "2.623055516220294", "SSC": "nan", "distance_sum": "4.8893790849673202", "eccentricity": "1.290909090909091", "eccentricity_inf": false, "id": 2, "label": "2" }, { "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" }, { "BC": "33", "CC": "0.33415873177954203", "EC": "1", "PC": "88.874529827533252", "SBC": "0.59999999999999998", "SC": "0", "SCC": "0.1225248683191654", "SEC": "1", "SPC": "5.2279135192666617", "SSC": "nan", "distance_sum": "2.9925897631779987", "eccentricity": "1", "eccentricity_inf": false, "id": 4, "label": "Adult" }, { "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" }, { "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" }, { "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" }, { "BC": "12.5", "CC": "0.22053878593220508", "EC": "0.69230769230769229", "PC": "56.571176817359806", "SBC": "0.22727272727272727", "SC": "0", "SCC": "0.080864221508475193", "SEC": "0.69230769230769229", "SPC": "3.142843156519989", "SSC": "nan", "distance_sum": "4.5343498005262708", "eccentricity": "1.4444444444444444", "eccentricity_inf": false, "id": 8, "label": "Adult" }, { "BC": "0", "CC": "0.10618322848479345", "EC": "0.51428571428571435", "PC": "19.218835564759711", "SBC": "0", "SC": "0", "SCC": "0.03893385044442426", "SEC": "0.51428571428571435", "SPC": "1.1305197391035124", "SSC": "nan", "distance_sum": "9.4176831338596045", "eccentricity": "1.9444444444444444", "eccentricity_inf": false, "id": 9, "label": "1" }, { "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" }, { "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" }, { "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" } ], "run": { "computeCentralities": true, "considerWeights": true, "dropIsolates": false, "inverseWeights": true }, "schema_version": 1 } socnetv-app-39db829/src/tools/baselines/ErdosRenyi_N10_E10__C1_W0_IW1_DI0.json000066400000000000000000000120301517721000100263460ustar00rootroot00000000000000{ "counts": { "links_sna": 10, "nodes": 10, "ties_graph": 10 }, "dataset": { "filetype": 1, "name": "ErdosRenyi_N10_E10.graphml", "path": "src/data/ErdosRenyi_N10_E10.graphml" }, "graph": { "directed": true, "weighted": false }, "load_report": { "fileType_signal": 1, "load_ms": 1, "load_msg": "", "net_name": "erdos-renyi", "ok": true }, "metrics": { "avg_distance": "8024807315.6315794", "connected": false, "diameter": 3, "disconnected_pairs": 71 }, "per_node": [ { "BC": "0", "CC": "0", "EC": "0", "PC": "4", "SBC": "0", "SC": "0", "SCC": "0", "SEC": "0", "SPC": "0.80000000000000004", "SSC": "0", "distance_sum": "7", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 1, "label": "1" }, { "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": 2, "label": "2" }, { "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": 3, "label": "3" }, { "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": 4, "label": "4" }, { "BC": "3", "CC": "0", "EC": "0", "PC": "2", "SBC": "0.041666666666666664", "SC": "1", "SCC": "0", "SEC": "0", "SPC": "0.66666666666666663", "SSC": "0.1111111111111111", "distance_sum": "5", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 5, "label": "5" }, { "BC": "0", "CC": "0", "EC": "0", "PC": "2", "SBC": "0", "SC": "0", "SCC": "0", "SEC": "0", "SPC": "0.66666666666666663", "SSC": "0", "distance_sum": "5", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 6, "label": "6" }, { "BC": "0", "CC": "0", "EC": "0", "PC": "2.1666666666666665", "SBC": "0", "SC": "0", "SCC": "0", "SEC": "0", "SPC": "0.54166666666666663", "SSC": "0", "distance_sum": "9", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 7, "label": "7" }, { "BC": "0", "CC": "0", "EC": "0", "PC": "2", "SBC": "0", "SC": "0", "SCC": "0", "SEC": "0", "SPC": "1", "SSC": "0", "distance_sum": "2", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 8, "label": "8" }, { "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": 9, "label": "9" }, { "BC": "8", "CC": "0", "EC": "0", "PC": "2", "SBC": "0.1111111111111111", "SC": "8", "SCC": "0", "SEC": "0", "SPC": "1", "SSC": "0.88888888888888884", "distance_sum": "2", "eccentricity": "2147483647", "eccentricity_inf": true, "id": 10, "label": "10" } ], "run": { "computeCentralities": true, "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 1 } socnetv-app-39db829/src/tools/baselines/SmallWorld_N10_E12__C1_W0_IW1_DI0.json000066400000000000000000000137171517721000100263620ustar00rootroot00000000000000{ "counts": { "links_sna": 12, "nodes": 10, "ties_graph": 12 }, "dataset": { "filetype": 1, "name": "SmallWorld_N10_E12.graphml", "path": "src/data/SmallWorld_N10_E12.graphml" }, "graph": { "directed": false, "weighted": false }, "load_report": { "fileType_signal": 1, "load_ms": 3, "load_msg": "", "net_name": "small-world", "ok": true }, "metrics": { "avg_distance": "4.7999999999999998", "connected": true, "diameter": 6, "disconnected_pairs": 0 }, "per_node": [ { "BC": "1.833333333333333", "CC": "0.052631578947368418", "EC": "0.25", "PC": "5.416666666666667", "SBC": "0.050925925925925916", "SC": "2.5", "SCC": "0.47368421052631576", "SEC": "0.25", "SPC": "0.60185185185185186", "SSC": "0.0625", "distance_sum": "19", "eccentricity": "4", "eccentricity_inf": false, "id": 1, "label": "1" }, { "BC": "7.3333333333333321", "CC": "0.055555555555555552", "EC": "0.25", "PC": "5.583333333333333", "SBC": "0.20370370370370366", "SC": "5", "SCC": "0.5", "SEC": "0.25", "SPC": "0.62037037037037035", "SSC": "0.125", "distance_sum": "18", "eccentricity": "4", "eccentricity_inf": false, "id": 2, "label": "2" }, { "BC": "9.3333333333333321", "CC": "0.047619047619047616", "EC": "0.20000000000000001", "PC": "5.2833333333333332", "SBC": "0.25925925925925924", "SC": "6.5", "SCC": "0.42857142857142855", "SEC": "0.20000000000000001", "SPC": "0.58703703703703702", "SSC": "0.16250000000000001", "distance_sum": "21", "eccentricity": "5", "eccentricity_inf": false, "id": 3, "label": "3" }, { "BC": "5", "CC": "0.050000000000000003", "EC": "0.25", "PC": "4.916666666666667", "SBC": "0.1388888888888889", "SC": "3", "SCC": "0.45000000000000001", "SEC": "0.25", "SPC": "0.54629629629629628", "SSC": "0.074999999999999997", "distance_sum": "20", "eccentricity": "4", "eccentricity_inf": false, "id": 4, "label": "4" }, { "BC": "9.1666666666666679", "CC": "0.058823529411764705", "EC": "0.33333333333333331", "PC": "5.666666666666667", "SBC": "0.25462962962962965", "SC": "6.5", "SCC": "0.52941176470588236", "SEC": "0.33333333333333331", "SPC": "0.62962962962962965", "SSC": "0.16250000000000001", "distance_sum": "17", "eccentricity": "3", "eccentricity_inf": false, "id": 5, "label": "5" }, { "BC": "0", "CC": "0.034482758620689655", "EC": "0.16666666666666666", "PC": "3.6166666666666667", "SBC": "0", "SC": "0", "SCC": "0.31034482758620691", "SEC": "0.16666666666666666", "SPC": "0.40185185185185185", "SSC": "0", "distance_sum": "29", "eccentricity": "6", "eccentricity_inf": false, "id": 6, "label": "6" }, { "BC": "0", "CC": "0.03125", "EC": "0.16666666666666666", "PC": "3.2833333333333332", "SBC": "0", "SC": "0", "SCC": "0.28125", "SEC": "0.16666666666666666", "SPC": "0.36481481481481476", "SSC": "0", "distance_sum": "32", "eccentricity": "6", "eccentricity_inf": false, "id": 7, "label": "7" }, { "BC": "8", "CC": "0.041666666666666664", "EC": "0.20000000000000001", "PC": "4.4500000000000002", "SBC": "0.22222222222222221", "SC": "4.5", "SCC": "0.375", "SEC": "0.20000000000000001", "SPC": "0.49444444444444446", "SSC": "0.1125", "distance_sum": "24", "eccentricity": "5", "eccentricity_inf": false, "id": 8, "label": "8" }, { "BC": "14.833333333333334", "CC": "0.055555555555555552", "EC": "0.25", "PC": "5.583333333333333", "SBC": "0.41203703703703703", "SC": "7", "SCC": "0.5", "SEC": "0.25", "SPC": "0.62037037037037035", "SSC": "0.17499999999999999", "distance_sum": "18", "eccentricity": "4", "eccentricity_inf": false, "id": 9, "label": "9" }, { "BC": "7.5", "CC": "0.055555555555555552", "EC": "0.33333333333333331", "PC": "5.5", "SBC": "0.20833333333333334", "SC": "5", "SCC": "0.5", "SEC": "0.33333333333333331", "SPC": "0.61111111111111105", "SSC": "0.125", "distance_sum": "18", "eccentricity": "3", "eccentricity_inf": false, "id": 10, "label": "10" } ], "run": { "computeCentralities": true, "considerWeights": false, "dropIsolates": false, "inverseWeights": true }, "schema_version": 1 } socnetv-app-39db829/src/tools/baselines/StokmanZiegler_Netherlands__FT5__C1_W0_IW1_DI0.json000066400000000000000000000207631517721000100313560ustar00rootroot00000000000000{ "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-app-39db829/src/tools/baselines/StokmanZiegler_Netherlands__FT5__C1_W1_IW1_DI0.json000066400000000000000000000215011517721000100313460ustar00rootroot00000000000000{ "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-app-39db829/src/tools/baselines/clustering/000077500000000000000000000000001517721000100223645ustar00rootroot00000000000000DunbarGelada_H22a__CLUST__V6__FT2__W0_IW1_DI0.json000066400000000000000000000060011517721000100325400ustar00rootroot00000000000000socnetv-app-39db829/src/tools/baselines/clustering{ "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" }, { "CLC": "0", "hasCLC": true, "id": 2, "label": "2" }, { "CLC": "0.38095238095238093", "hasCLC": true, "id": 3, "label": "Adult" }, { "CLC": "0.47619047619047616", "hasCLC": true, "id": 4, "label": "Adult" }, { "CLC": "0.40000000000000002", "hasCLC": true, "id": 5, "label": "Adult" }, { "CLC": "0.69999999999999996", "hasCLC": true, "id": 6, "label": "3" }, { "CLC": "0.33333333333333331", "hasCLC": true, "id": 7, "label": "3" }, { "CLC": "0", "hasCLC": true, "id": 8, "label": "Adult" }, { "CLC": "0", "hasCLC": true, "id": 9, "label": "1" }, { "CLC": "0.5", "hasCLC": true, "id": 10, "label": "3" }, { "CLC": "0", "hasCLC": true, "id": 11, "label": "2" }, { "CLC": "0.66666666666666663", "hasCLC": true, "id": 12, "label": "1" } ], "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 } } DunbarGelada_H22a__CLUST__V6__FT2__W1_IW1_DI0.json000066400000000000000000000060001517721000100325400ustar00rootroot00000000000000socnetv-app-39db829/src/tools/baselines/clustering{ "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" }, { "CLC": "0", "hasCLC": true, "id": 2, "label": "2" }, { "CLC": "0.38095238095238093", "hasCLC": true, "id": 3, "label": "Adult" }, { "CLC": "0.47619047619047616", "hasCLC": true, "id": 4, "label": "Adult" }, { "CLC": "0.40000000000000002", "hasCLC": true, "id": 5, "label": "Adult" }, { "CLC": "0.69999999999999996", "hasCLC": true, "id": 6, "label": "3" }, { "CLC": "0.33333333333333331", "hasCLC": true, "id": 7, "label": "3" }, { "CLC": "0", "hasCLC": true, "id": 8, "label": "Adult" }, { "CLC": "0", "hasCLC": true, "id": 9, "label": "1" }, { "CLC": "0.5", "hasCLC": true, "id": 10, "label": "3" }, { "CLC": "0", "hasCLC": true, "id": 11, "label": "2" }, { "CLC": "0.66666666666666663", "hasCLC": true, "id": 12, "label": "1" } ], "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 } } Krackhardt_Kite_N10__CLUST__V6__FT2__W0_IW1_DI0.json000066400000000000000000000052231517721000100331300ustar00rootroot00000000000000socnetv-app-39db829/src/tools/baselines/clustering{ "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 } } Sampson_Monks_N18__CLUST__V6__FT2__W0_IW1_DI0.json000066400000000000000000000075131517721000100327010ustar00rootroot00000000000000socnetv-app-39db829/src/tools/baselines/clustering{ "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.json000066400000000000000000000072241517721000100347530ustar00rootroot00000000000000socnetv-app-39db829/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.json000066400000000000000000000072231517721000100347530ustar00rootroot00000000000000socnetv-app-39db829/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-app-39db829/src/tools/baselines/clustering/TinyDirChain_N3__CLUST__V6__FT2__W0_IW1_DI0.json000066400000000000000000000032121517721000100324200ustar00rootroot00000000000000{ "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-app-39db829/src/tools/baselines/clustering/TinyPath_N3_E2__CLUST__V6__FT2__W0_IW1_DI0.json000066400000000000000000000032101517721000100321570ustar00rootroot00000000000000{ "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-app-39db829/src/tools/baselines/io_roundtrip/000077500000000000000000000000001517721000100227225ustar00rootroot00000000000000socnetv-app-39db829/src/tools/baselines/io_roundtrip/Benchmark_BA_Directed_N500_m3__FT2_big.json000066400000000000000000013060311517721000100324140ustar00rootroot00000000000000{ "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": 14, "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": 14, "reload_ms": 13, "save_ms": 50, "total_ms": 78 } } socnetv-app-39db829/src/tools/baselines/io_roundtrip/Bernard_Killworth_Fraternity__FT5__IO__V5.json000066400000000000000000035371101517721000100335260ustar00rootroot00000000000000{ "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": 28, "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": false }, "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": 28, "reload_ms": 0, "save_ms": 0, "total_ms": 30 } } Krackhardt_High-tech_managers__FT2_multirel.json000066400000000000000000002151441517721000100341070ustar00rootroot00000000000000socnetv-app-39db829/src/tools/baselines/io_roundtrip{ "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": 1, "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": 1, "reload_ms": 2, "save_ms": 1, "total_ms": 11 } } socnetv-app-39db829/src/tools/baselines/io_roundtrip/Padgett_Florentine_Families__FT2_multirel.json000066400000000000000000000460441517721000100337420ustar00rootroot00000000000000{ "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": 2, "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": 2, "reload_ms": 0, "save_ms": 0, "total_ms": 3 } } Stephenson_Zelen_5actors_6edges__FT2_multirel.json000066400000000000000000000170311517721000100344470ustar00rootroot00000000000000socnetv-app-39db829/src/tools/baselines/io_roundtrip{ "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": 3, "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": 3, "reload_ms": 0, "save_ms": 0, "total_ms": 2 } } socnetv-app-39db829/src/tools/baselines/io_roundtrip/StokmanZiegler_Netherlands__FT5__IO__V5.json000066400000000000000000001005421517721000100331600ustar00rootroot00000000000000{ "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": 1, "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": false }, "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": 1, "reload_ms": 0, "save_ms": 0, "total_ms": 1 } } socnetv-app-39db829/src/tools/baselines/io_roundtrip/TinyAdj_Undir_N3__FT3.json000066400000000000000000000056341517721000100275030ustar00rootroot00000000000000{ "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": 1, "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": 1, "reload_ms": 0, "save_ms": 0, "total_ms": 2 } } socnetv-app-39db829/src/tools/baselines/io_roundtrip/TinyAdj_Weighted_Dir_N3__FT3.json000066400000000000000000000052641517721000100307570ustar00rootroot00000000000000{ "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": 2, "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": 2, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } socnetv-app-39db829/src/tools/baselines/io_roundtrip/TinyEdgeList_Simple_Dir_N3__FT8.json000066400000000000000000000047231517721000100314560ustar00rootroot00000000000000{ "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": 1, "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": false }, "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": 1, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } socnetv-app-39db829/src/tools/baselines/io_roundtrip/TinyEdgeList_Symmetric_Undir_N3__FT8.json000066400000000000000000000057121517721000100325430ustar00rootroot00000000000000{ "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": false }, "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-app-39db829/src/tools/baselines/io_roundtrip/TinyEdgeList_Weighted_Dir_N3__FT7.json000066400000000000000000000053071517721000100317630ustar00rootroot00000000000000{ "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": 2, "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": false }, "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": 2, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } socnetv-app-39db829/src/tools/baselines/io_roundtrip/TinyGML_Dir_N3__FT6.json000066400000000000000000000051031517721000100270530ustar00rootroot00000000000000{ "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": 4, "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": 4, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } socnetv-app-39db829/src/tools/baselines/io_roundtrip/TinyGML_Weighted_Dir_N3__FT6_weighted.json000066400000000000000000000056231517721000100325620ustar00rootroot00000000000000{ "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": 3, "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": 3, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } socnetv-app-39db829/src/tools/baselines/io_roundtrip/TinyGraphML_Weighted_Dir_N3__FT1.json000066400000000000000000000053301517721000100315430ustar00rootroot00000000000000{ "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": 1, "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": 1, "reload_ms": 0, "save_ms": 0, "total_ms": 0 } } socnetv-app-39db829/src/tools/baselines/io_roundtrip/TinyGraphviz_Dir_N3__FT4.json000066400000000000000000000047061517721000100302340ustar00rootroot00000000000000{ "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": false }, "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-app-39db829/src/tools/baselines/io_roundtrip/TinyGraphviz_Undir_N3__FT4.json000066400000000000000000000047171517721000100306010ustar00rootroot00000000000000{ "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": false }, "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-app-39db829/src/tools/baselines/io_roundtrip/TinyGraphviz_Weighted_Dir_N3__FT4_weighted.json000066400000000000000000000053251517721000100337320ustar00rootroot00000000000000{ "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": false }, "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-app-39db829/src/tools/baselines/prominence/000077500000000000000000000000001517721000100223445ustar00rootroot00000000000000Krackhardt_Kite_N10__PROM__V4__FT2__W0_IW1_DI0.json000066400000000000000000000242001517721000100327650ustar00rootroot00000000000000socnetv-app-39db829/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 }, "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__W1_IW1_DI0.json000066400000000000000000000240631517721000100327750ustar00rootroot00000000000000socnetv-app-39db829/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 }, "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-app-39db829/src/tools/baselines/prominence/Sampson_Monks_N18__PROM__V4__FT2__W0_IW1_DI0.json000066400000000000000000000433421517721000100326210ustar00rootroot00000000000000{ "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-app-39db829/src/tools/baselines/prominence/TinyDirChain_N3__PROM__V4__FT2__W0_IW1_DI0.json000066400000000000000000000056001517721000100322640ustar00rootroot00000000000000{ "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 } socnetv-app-39db829/src/tools/baselines/prominence/TinyPath_N3_E2__PROM__V4__FT2__W0_IW1_DI0.json000066400000000000000000000060151517721000100320260ustar00rootroot00000000000000{ "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 } socnetv-app-39db829/src/tools/baselines/reachability/000077500000000000000000000000001517721000100226455ustar00rootroot00000000000000socnetv-app-39db829/src/tools/baselines/reachability/DunbarGelada_H22a__REACH__V2.json000066400000000000000000000101501517721000100303710ustar00rootroot00000000000000{ "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-app-39db829/src/tools/baselines/reachability/StokmanZiegler_Netherlands__REACH__V2.json000066400000000000000000000145451517721000100326050ustar00rootroot00000000000000{ "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-app-39db829/src/tools/baselines/walks/000077500000000000000000000000001517721000100213265ustar00rootroot00000000000000socnetv-app-39db829/src/tools/baselines/walks/DunbarGelada_H22a__WALKS_K6__V3.json000066400000000000000000000113341517721000100274570ustar00rootroot00000000000000{ "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-app-39db829/src/tools/baselines/walks/StokmanZiegler_Netherlands__WALKS_K6__V3.json000066400000000000000000000173251517721000100316650ustar00rootroot00000000000000{ "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-app-39db829/src/tools/baselines/walks/TinyPath_N3_E2__WALKS_K2__V3.json000066400000000000000000000021621517721000100270130ustar00rootroot00000000000000{ "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-app-39db829/src/tools/cli/000077500000000000000000000000001517721000100170075ustar00rootroot00000000000000socnetv-app-39db829/src/tools/cli/cli_common.cpp000066400000000000000000000127751517721000100216460ustar00rootroot00000000000000// 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-app-39db829/src/tools/cli/cli_common.h000066400000000000000000000041371517721000100213040ustar00rootroot00000000000000#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) }; // ---------------- 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-app-39db829/src/tools/cli/kernels/000077500000000000000000000000001517721000100204525ustar00rootroot00000000000000socnetv-app-39db829/src/tools/cli/kernels/kernel_clustering_v6.cpp000066400000000000000000000367261517721000100253260ustar00rootroot00000000000000// 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(false); 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-app-39db829/src/tools/cli/kernels/kernel_clustering_v6.h000066400000000000000000000004151517721000100247550ustar00rootroot00000000000000#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-app-39db829/src/tools/cli/kernels/kernel_distance_v1.cpp000066400000000000000000000300451517721000100247200ustar00rootroot00000000000000// 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-app-39db829/src/tools/cli/kernels/kernel_distance_v1.h000066400000000000000000000010661517721000100243660ustar00rootroot00000000000000#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-app-39db829/src/tools/cli/kernels/kernel_io_roundtrip_v5.cpp000066400000000000000000000572751517721000100256650ustar00rootroot00000000000000// 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(); graph["weighted"] = g.isWeighted(); 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-app-39db829/src/tools/cli/kernels/kernel_io_roundtrip_v5.h000066400000000000000000000011051517721000100253070ustar00rootroot00000000000000#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-app-39db829/src/tools/cli/kernels/kernel_prominence_v4.cpp000066400000000000000000000305401517721000100252700ustar00rootroot00000000000000// 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 namespace cli { // ------------------------------ // Per-node builder // ------------------------------ static QJsonArray buildPerNodeArrayV4(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(); // ---- 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; QJsonObject run; run["considerWeights"] = cfg.considerWeights; run["inverseWeights"] = cfg.inverseWeights; run["dropIsolates"] = cfg.dropIsolates; 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); 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, 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"}; for (const QString &f : numFields) 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); 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, err); if (!ok) return 1; err << "OK: baseline match\n"; return 0; } // ------------------------------ // Runner // ------------------------------ int runKernelProminenceV4(const CliConfig &cfg, const HeadlessLoadResult &load, Graph &g) { // 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.dropIsolates); g.centralityEigenvector(cfg.considerWeights, cfg.dropIsolates); g.centralityClosenessIR(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); const qint64 computeMs = t.elapsed(); 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-app-39db829/src/tools/cli/kernels/kernel_prominence_v4.h000066400000000000000000000010221517721000100247260ustar00rootroot00000000000000#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-app-39db829/src/tools/cli/kernels/kernel_reachability_v2.cpp000066400000000000000000000202661517721000100255730ustar00rootroot00000000000000// 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 = (gv->distance(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-app-39db829/src/tools/cli/kernels/kernel_reachability_v2.h000066400000000000000000000010531517721000100252310ustar00rootroot00000000000000#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-app-39db829/src/tools/cli/kernels/kernel_walks_v3.cpp000066400000000000000000000176451517721000100242640ustar00rootroot00000000000000// 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-app-39db829/src/tools/cli/kernels/kernel_walks_v3.h000066400000000000000000000011531517721000100237140ustar00rootroot00000000000000#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-app-39db829/src/tools/headless_graph_loader.cpp000066400000000000000000000126731517721000100232540ustar00rootroot00000000000000#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; qDebug() << "[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); qDebug() << "[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; qDebug() << "[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); qDebug() << "[CLI] entering loop.exec()"; loop.exec(); qDebug() << "[CLI] left loop.exec()"; parserThread.quit(); parserThread.wait(); delete parser; parser = nullptr; qDebug() << "[CLI] loadGraphHeadless() end:" << out.ok; return out; } socnetv-app-39db829/src/tools/headless_graph_loader.h000066400000000000000000000025561517721000100227200ustar00rootroot00000000000000#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-app-39db829/src/tools/socnetv_cli.cpp000066400000000000000000000154141517721000100212610ustar00rootroot00000000000000// 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 "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" 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", "name", "distance"); QCommandLineOption walksLenOpt(QStringList() << "walks-length", "Walks length K for walks_matrix (K>=1).", "K", "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.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"; } }); } 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(); if (cfg.inputPath.isEmpty()) { QTextStream(stderr) << "ERROR: --input is required\n"; return 2; } if (cfg.benchRuns > 0 && cfg.kernel != "distance") { QTextStream(stderr) << "ERROR: --bench is only supported with --kernel distance\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); QTextStream(stderr) << "ERROR: unsupported --kernel: " << cfg.kernel << "\n"; return 2; } socnetv-app-39db829/src/webcrawler.cpp000077500000000000000000000530531517721000100177520ustar00rootroot00000000000000/** * @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 /** * @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) { qDebug () << "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. qDebug() << "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(); qDebug() << "Reply HTML belongs to url:" << currentUrlStr << " sourceNode:" << sourceNode << "host:" << host << "path:" << path; // Check for redirects if ( locationHeader != "" && locationHeader != currentUrlStr ) { qDebug () << "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()); qDebug () << "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. qDebug() << "Empty or not useful html from:" << currentUrl << "page size:" << page.size() << "\npage contents: " << page << "\nRETURN ##"; return; } // qDebug() << " \npage contents: " << page << "\n\n"; // We only search inside ... tags qDebug() << "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 ) { qDebug() << "ERROR IN opening tag"; } else if ( end == -1 ) { qDebug() << "ERROR IN locating closing tag"; } // Main Loop: While there are more links in the page, parse them qDebug() << "Searching for href links inside the page code..."; while (page.contains(" href")) { if ( QThread::currentThread()->isInterruptionRequested() ) { qDebug () <<"STOP because currentThread()->isInterruptionRequested() == true."; emit finished("message from parse() - interruptionRequested"); return; } if (m_maxUrls>0) { if (m_discoveredNodes >= m_maxUrls ) { qDebug () <<"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(); qDebug() << "Found new URL:"<< newUrlStr << "Examining it..."; newUrl = QUrl(newUrlStr); if (newUrl.isRelative()) { qDebug() << "newUrl is RELATIVE. Merging baseUrl with it."; newUrl=baseUrl.resolved(newUrl); } if (!newUrl.isValid()) { invalidUrlsInPage ++; qDebug() << "newUrl is INVALID: " << newUrl.toString() << "in page " << currentUrlStr << "SKIPPING IT. Will continue in this page, only if invalidUrlsInPage < 200"; if (invalidUrlsInPage > 200) { qDebug() << "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(); qDebug() << "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) ) { qDebug()<< "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 ) ) { qDebug() << "newUrl in allowed url patterns:" << urlPattern << "OK."; break; } else { qDebug() << "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 ) ) { qDebug() << "newUrl in excluded url patterns:" << urlPattern << "I WILL SKIP IT."; m_urlPatternNotAllowed = true; break; } else { qDebug() << "newUrl not in excluded url patterns. OK."; } } if (m_urlPatternAllowed && !m_urlPatternNotAllowed) { if ( newUrl.isRelative() ) { newUrl = currentUrl.resolved(newUrl); newUrlStr = newUrl.toString(); qDebug() << "newUrl is RELATIVE." << "host: " << host << "resolved url:" << newUrl.toString(); if (!m_intLinks ){ qDebug()<< "SKIPPING node/edge creation because internal URLs are forbidden."; continue; } if (currentUrl.path() == newUrl.path()) { if (m_selfLinks) { qDebug()<< "Self-link found. Creating it!"; newLink(sourceNode, newUrl, false); } else { qDebug()<< "Self-link found but self-links are not allowed. I will not create it."; } } else { qDebug()<< "Internal URLs allowed. Calling newLink() "; this->newLink(sourceNode, newUrl, true); } } else { qDebug() << "newUrl is ABSOLUTE."; if ( newUrl.scheme() != "http" && newUrl.scheme() != "https" && newUrl.scheme() != "ftp" && newUrl.scheme() != "ftps") { qDebug() << "INVALID newUrl SCHEME" << newUrl.toString() << "CONTINUE."; continue; } if ( newUrl.host() != host ) { qDebug()<< "newUrl ABSOLUTE & EXTERNAL."; if ( !m_extLinksIncluded ) { qDebug()<< "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) { qDebug() << "newUrl in excluded social links:" << urlPattern << "CONTINUE "; continue; } } if ( m_extLinksCrawl ) { qDebug()<< "External URLs are included and to be crawled. Calling newLink..."; newLink(sourceNode, newUrl, true); } else { qDebug()<< "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 { qDebug()<< "newUrl ABSOLUTE & INTERNAL."; if (!m_intLinks){ qDebug()<< "Internal URLs forbidden." << "SKIPPING node creation"; continue; } if ( newUrl.path () == path && !m_selfLinks) { qDebug()<< "Self links forbidden. CONTINUE."; continue; } if ( newUrl.isParentOf(currentUrl) && !m_parentLinks ) { qDebug()<< "Parent URLs forbidden. CONTINUE"; continue; } if ( currentUrl.isParentOf(newUrl) && !m_childLinks ) { qDebug()<< "Child URLs forbidden. CONTINUE"; continue; } qDebug()<< "Internal, absolute newURL allowed. Calling newLink..."; newLink(sourceNode, newUrl, true); } } } // end if in allowed patterns validUrlsInPage ++; qDebug() << "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 ) { qDebug () <<"STOP because I reached m_maxLinksPerPage " <0 && m_discoveredNodes >= m_maxUrls ) { qDebug () <<"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() ) { qDebug()<< "Target already discovered (in knownUrls) as node:" << index.value(); if (s !=index.value()) { if ( QThread::currentThread()->isInterruptionRequested() ) { qDebug () <<"STOP because currentThread()->isInterruptionRequested() == true."; emit finished("message from parse() - interruptionRequested"); return; } qDebug()<< "Emitting signalCreateEdge" << s << "->" << index.value() << "and RETURN."; emit signalCreateEdge (s, index.value() ); } else { qDebug()<< "Self links not allowed. RETURN."; } return; } m_discoveredNodes++; knownUrls[target]=m_discoveredNodes; if ( QThread::currentThread()->isInterruptionRequested() ) { qDebug () <<"STOP because currentThread()->isInterruptionRequested() == true."; emit finished("message from parse() - interruptionRequested"); return; } qDebug()<< "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); qDebug()<< "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; qDebug() << "delay requested between signalStartSpider() calls. Setting a deadline for:" << m_wait_msecs << "msecs"; QDeadlineTimer deadline(m_wait_msecs); do { // qDebug() << "deadline.remainingTime():" << deadline.remainingTime() << "msecs"; } while (deadline.remainingTime() > 0 && this->thread()->isRunning()); if ( QThread::currentThread()->isInterruptionRequested() ) { qDebug () <<"STOP because currentThread()->isInterruptionRequested() == true."; emit finished("message from parse() - interruptionRequested"); return; } emit signalStartSpider(); } else { if ( QThread::currentThread()->isInterruptionRequested() ) { qDebug () <<"STOP because currentThread()->isInterruptionRequested() == true."; emit finished("message from parse() - interruptionRequested"); return; } emit signalStartSpider(); } } else { qDebug()<< "NOT adding new node to queue"; } if ( QThread::currentThread()->isInterruptionRequested() ) { qDebug () <<"STOP because currentThread()->isInterruptionRequested() == true."; emit finished("message from parse() - interruptionRequested"); return; } qDebug()<< "Emitting signalCreateEdge" << s << "->" << m_discoveredNodes; emit signalCreateEdge (s, m_discoveredNodes); } WebCrawler::~WebCrawler() { qDebug() << "Destructor running. Clearing vars..."; knownUrls.clear(); m_urlPatternsIncluded.clear(); urlPattern=""; m_urlPatternsExcluded.clear(); m_linkClasses.clear(); m_discoveredNodes = 0; } socnetv-app-39db829/src/webcrawler.h000077500000000000000000000055371517721000100174230ustar00rootroot00000000000000/** * @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-app-39db829/src/widgets/000077500000000000000000000000001517721000100165465ustar00rootroot00000000000000socnetv-app-39db829/src/widgets/edgetablemodel.cpp000066400000000000000000000165531517721000100222210ustar00rootroot00000000000000/** * @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-app-39db829/src/widgets/edgetablemodel.h000066400000000000000000000044731517721000100216640ustar00rootroot00000000000000/** * @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); 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-app-39db829/src/widgets/filterbarwidget.cpp000066400000000000000000000101151517721000100224260ustar00rootroot00000000000000/** * @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]() { removeChip(chip); emit chipCloseRequested(scope); }); 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() { const int last = m_chips.size() - 1; for (int i = 0; i <= last; ++i) { const bool isLast = (i == last); m_chips[i].closeBtn->setEnabled(isLast); m_chips[i].closeBtn->setToolTip( isLast ? tr("Remove this filter") : tr("Remove the most recently applied filter first")); } } socnetv-app-39db829/src/widgets/filterbarwidget.h000066400000000000000000000037561517721000100221100ustar00rootroot00000000000000/** * @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 Γ— removes the chip and emits chipCloseRequested() so MainWindow can * pop the corresponding snapshot. "Clear all" emits clearAllRequested() and lets * MainWindow drain the stack before calling 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); public slots: void clearAllChips(); signals: /** Emitted after the chip has already been removed from the bar. */ void chipCloseRequested(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-app-39db829/src/widgets/graphtablewidget.cpp000066400000000000000000000303021517721000100225650ustar00rootroot00000000000000/** * @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 "../graph.h" #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::SingleSelection); 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); 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")); QHBoxLayout *nodeTopBar = new QHBoxLayout; nodeTopBar->addWidget(m_nodeSearch, 1); nodeTopBar->addWidget(nodeRefreshBtn); nodeTopBar->addWidget(nodeExportCSVBtn); nodeTopBar->addWidget(nodeExportJSONBtn); nodeTopBar->addWidget(nodeImportCSVBtn); nodeTopBar->addWidget(nodeImportJSONBtn); 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); 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")); QHBoxLayout *edgeTopBar = new QHBoxLayout; edgeTopBar->addWidget(m_edgeSearch, 1); edgeTopBar->addWidget(edgeRefreshBtn); edgeTopBar->addWidget(edgeExportCSVBtn); edgeTopBar->addWidget(edgeExportJSONBtn); edgeTopBar->addWidget(edgeImportCSVBtn); edgeTopBar->addWidget(edgeImportJSONBtn); 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); }); // Node row click β†’ nodeSelected signal connect(m_nodeView, &QTableView::clicked, this, &GraphTableWidget::onNodeRowClicked); // 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); } /** * @brief Repopulates both models from @p graph and resizes columns. */ void GraphTableWidget::refresh(Graph *graph) { m_graph = graph; m_nodeModel->populate(graph); m_edgeModel->populate(graph); m_nodeView->resizeColumnsToContents(); m_edgeView->resizeColumnsToContents(); } /** * @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()); } 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); } /** * @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-app-39db829/src/widgets/graphtablewidget.h000066400000000000000000000063561517721000100222460ustar00rootroot00000000000000/** * @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 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; 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); /** 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 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); private: void doExport(QAbstractItemModel *proxyModel, const QString &defaultName, bool csv); void doImport(bool forNodes, bool csv); 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; }; socnetv-app-39db829/src/widgets/nodetablemodel.cpp000066400000000000000000000146401517721000100222350ustar00rootroot00000000000000/** * @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-app-39db829/src/widgets/nodetablemodel.h000066400000000000000000000044371517721000100217050ustar00rootroot00000000000000/** * @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); 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-app-39db829/translations/000077500000000000000000000000001517721000100170325ustar00rootroot00000000000000socnetv-app-39db829/translations/socnetv_de.qm000066400000000000000000000074041517721000100215270ustar00rootroot00000000000000<Έ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-app-39db829/translations/socnetv_es.qm000066400000000000000000000001171517721000100215400ustar00rootroot00000000000000<ΈdΚοœ•Ν!Ώ`‘½έ§esBi MainWindowˆsocnetv-app-39db829/translations/socnetv_es.ts000066400000000000000000026711751517721000100215750ustar00rootroot00000000000000 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