diff --git a/website/.editorconfig b/.editorconfig similarity index 100% rename from website/.editorconfig rename to .editorconfig diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +node_modules diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..58990881 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,36 @@ +{ + "root": true, + "ignorePatterns": ["**/*"], + "plugins": ["@nx"], + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "extends": ["plugin:@nx/typescript"], + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrors": "all" + } + ] + } + }, + { + "files": ["*.js", "*.jsx"], + "extends": ["plugin:@nx/javascript"], + "rules": {} + }, + { + "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], + "env": { + "jest": true + }, + "rules": {} + } + ], + "extends": ["plugin:storybook/recommended"] +} diff --git a/.github/actions/deploy-comment/action.yml b/.github/actions/deploy-comment/action.yml new file mode 100644 index 00000000..90f04d38 --- /dev/null +++ b/.github/actions/deploy-comment/action.yml @@ -0,0 +1,37 @@ +name: Create Deploy Comment +description: Creates and Updates the deploy Comment +inputs: + deploy-url: + description: Url for deployed preview + required: true +runs: + using: composite + steps: + - name: Find Comment + uses: peter-evans/find-comment@v2 + id: 'fc' + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: '# :rocket: Preview Deploy Report' + + - name: Create Comment + if: steps.fc.outputs.comment-id == '' + uses: peter-evans/create-or-update-comment@v2 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + # :rocket: Preview Deploy Report + + ✅ Successfully deployed preview [here](${{ inputs.deploy-url }}) + + - name: Update Comment + if: steps.fc.outputs.comment-id != '' + uses: peter-evans/create-or-update-comment@v2 + with: + comment-id: ${{steps.fc.outputs.comment-id}} + edit-mode: replace + body: | + # :rocket: Preview Deploy Report Updated + + ✅ Successfully deployed preview [here](${{ inputs.deploy-url }}) diff --git a/.github/actions/prepare-deploy/action.yml b/.github/actions/prepare-deploy/action.yml new file mode 100644 index 00000000..12424296 --- /dev/null +++ b/.github/actions/prepare-deploy/action.yml @@ -0,0 +1,75 @@ +name: Prepare a deploy build +description: Build apps, storybook, and compodoc and generates deploy files + +inputs: + main-branch-name: + description: Main branch name against which nx builds + required: true + app-configuration: + description: App build target configuration + required: true + nx-command: + description: Command used to build artifacts ('affected' or 'run-many') + required: false + default: affected + deploy-directory: + description: Deployment directory + required: false + default: deploy + skip-node-js: + description: Skip Node.js setup + required: false + default: 'false' + build-compodoc: + description: Whether to build code documentation + required: false + default: 'true' + build-storybook: + description: Whether to build storybook + required: false + default: 'true' + +runs: + using: composite + steps: + - name: Node.js setup + if: ${{ inputs.skip-node-js == 'false' }} + uses: ./.github/actions/setup-node-js + - name: Configure nx + uses: nrwl/nx-set-shas@v3 + with: + main-branch-name: ${{ inputs.main-branch-name }} + + - name: Build apps + shell: bash + run: npx nx ${{ inputs.nx-command }} --targets=build --configuration=${{ inputs.app-configuration }} + - name: Build compodoc + if: ${{ inputs.build-compodoc == 'true' }} + shell: bash + run: npx nx ${{ inputs.nx-command }} --targets=compodoc --configuration=ci + + - name: Build storybook + if: ${{ inputs.build-storybook == 'true' }} + shell: bash + run: | + # Run one at a time due to https://github.com/nrwl/nx/issues/6842 + # Will hopefully be fixed by backport of https://github.com/storybookjs/storybook/pull/19307 + npx nx ${{ inputs.nx-command }} --targets=build-storybook --configuration=ci --parallel=false + env: + STORYBOOK_DISABLE_TELEMETRY: '1' + + - name: Collect artifacts + id: collect + shell: bash + run: ${{ github.action_path }}/collect_artifacts.sh ${{ inputs.deploy-directory }} dist/{apps,compodoc,storybook} + + - name: Generate index file + uses: cuchi/jinja2-action@v1.2.0 + with: + template: ${{ github.action_path }}/index.html.j2 + output_file: ${{ inputs.deploy-directory }}/index.html + strict: true + variables: | + apps=${{ steps.collect.outputs.apps }} + storybook=${{ inputs.build-storybook == 'true' && steps.collect.outputs.storybook || '' }} + compodoc=${{ inputs.build-compodoc == 'true' && steps.collect.outputs.compodoc || '' }} diff --git a/.github/actions/prepare-deploy/collect_artifacts.sh b/.github/actions/prepare-deploy/collect_artifacts.sh new file mode 100644 index 00000000..45497f4d --- /dev/null +++ b/.github/actions/prepare-deploy/collect_artifacts.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# +# Collects built artifacts and moves them to the specified directory +# Arguments: +# Output directory +# One or more input directories to search +# Outputs: +# Writes an output for each input directory to github outputs +# Each entry contains the name of the directory as the key and +# a comma separated list of copied directories as the value + +shopt -s extglob + +####################################### +# Main logic +####################################### +out_dir="$1" +mkdir -p "${out_dir}" +shift + +while (( "$#" )); do + if [[ -d $1 ]]; then + # Extract directory name (https://stackoverflow.com/a/1371283) + dir_name="${1%%+(/)}" + dir_name="${dir_name##*/}" + dir_name="${dir_name:-/}" + + # Copy directory + cp -r "$1" "${out_dir}" + + # List subdirectories as a comma separated list + sub_dirs=$(echo "${out_dir}/${dir_name}"/*) + sub_dirs="${sub_dirs[*]//$out_dir\/}" + sub_dirs="${sub_dirs[*]//$' '/,}" + + # Write outputs + echo "${dir_name}=${sub_dirs}" >> "${GITHUB_OUTPUT}" + fi + shift +done diff --git a/.github/actions/prepare-deploy/index.html.j2 b/.github/actions/prepare-deploy/index.html.j2 new file mode 100644 index 00000000..4129b94f --- /dev/null +++ b/.github/actions/prepare-deploy/index.html.j2 @@ -0,0 +1,34 @@ + + +
++ No app, storybook, or compodoc were affected by the current changes! +
+ {% endif %} + + {{ createLinks('Apps', apps) }} + {{ createLinks('Storybook', storybook) }} + {{ createLinks('Compodoc', compodoc) }} + + diff --git a/.github/actions/setup-node-js/action.yml b/.github/actions/setup-node-js/action.yml new file mode 100644 index 00000000..00be685a --- /dev/null +++ b/.github/actions/setup-node-js/action.yml @@ -0,0 +1,33 @@ +name: Setup node environment +description: Install Node.js and all dependencies using the cache when available + +inputs: + node-version: + description: Node.js version + required: false + default: '16' + +runs: + using: composite + steps: + - name: Use Node.js ${{ inputs.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ inputs.node-version }} + - name: Cache .npm + uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + - name: Cache node_modules + id: modules_cache + uses: actions/cache@v3 + with: + path: ./node_modules + key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }} + - name: Install dependencies + shell: bash + if: ${{ steps.modules_cache.outputs.cache-hit != 'true' }} + run: npm ci --ignore-scripts diff --git a/.github/workflows/build-previews.yml b/.github/workflows/build-previews.yml index de82f0f2..b73a81c9 100644 --- a/.github/workflows/build-previews.yml +++ b/.github/workflows/build-previews.yml @@ -1,39 +1,39 @@ -name: 'Netlify Preview Deploy' +name: Netlify Preview Deploy on: pull_request: types: ['opened', 'edited', 'synchronize'] +concurrency: + group: preview-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: deploy: name: 'Deploy' runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14.x] steps: - - uses: actions/checkout@v1 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + - uses: actions/checkout@v3 with: - node-version: ${{ matrix.node-version }} - - uses: actions/cache@v2 + fetch-depth: 0 + + - name: Prepare Deploy + uses: ./.github/actions/prepare-deploy with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - name: npm ci for website - run: npm ci - working-directory: website - - run: npm run build -- --output-hashing=none --base-href=/ - working-directory: website + main-branch-name: develop + app-configuration: preview - - uses: jsmrcaga/action-netlify-deploy@master + - name: Publish + id: deploy + uses: netlify/actions/cli@master with: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + args: deploy --dir=deploy --filter=ftu-ui + env: NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} - install_command: false - build_command: false - build_directory: website/dist/a2agc + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + + - name: Create Deploy Comment + uses: ./.github/actions/deploy-comment + with: + deploy-url: ${{steps.deploy.outputs.NETLIFY_URL}} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..a7d2f41a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + branches: + - main + - develop + pull_request: + +concurrency: + group: ci-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + main: + name: Nx Cloud - Main Job + uses: nrwl/ci/.github/workflows/nx-cloud-main.yml@v0.11.2 + with: + number-of-agents: 3 + init-commands: | + npx nx-cloud start-ci-run --stop-agents-after="build" --agent-count=3 + parallel-commands: | + npx nx-cloud record -- npx nx format:check + parallel-commands-on-agents: | + npx nx affected --target=lint --parallel=3 + npx nx affected --target=test --parallel=3 --ci --code-coverage + npx nx affected --target=test-doc-coverage --parallel=3 + npx nx affected --target=build --parallel=3 + main-branch-name: develop + + agents: + name: Nx Cloud - Agents + uses: nrwl/ci/.github/workflows/nx-cloud-agents.yml@v0.11.2 + with: + number-of-agents: 3 + + slack-notification: + name: Slack Notification + if: ${{ always() }} + runs-on: ubuntu-latest + needs: main + steps: + - uses: kpritam/slack-job-status-action@v1 + with: + job-status: ${{ needs.main.result }} + slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }} + channel: github diff --git a/.github/workflows/production-build.yml b/.github/workflows/production-build.yml index 6d5b383b..b8c5c57b 100644 --- a/.github/workflows/production-build.yml +++ b/.github/workflows/production-build.yml @@ -1,41 +1,33 @@ -name: 'Production Build' +name: Staging Build on: workflow_run: - workflows: ['Tests'] + workflows: ['CI'] branches: ['main'] types: - completed jobs: deploy: - name: 'Deploy' if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14.x] steps: - - uses: actions/checkout@v1 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + - uses: actions/checkout@v3 with: - node-version: ${{ matrix.node-version }} - - uses: actions/cache@v2 + ref: main + fetch-depth: 0 + + - name: Prepare Deploy + uses: ./.github/actions/prepare-deploy with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - name: npm ci for website - run: npm ci - working-directory: website - - run: npm run build -- --output-hashing=none --base-href=/ - working-directory: website + main-branch-name: main + app-configuration: production + nx-command: run-many - - uses: peaceiris/actions-gh-pages@v3 + - name: Publish + uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: website/dist/a2agc - commit_message: "Production deploy -- ${{ github.event.head_commit.message }}" + publish_dir: deploy + commit_message: 'Staging deploy -- ${{ github.event.head_commit.message }}' diff --git a/.github/workflows/staging-build.yml b/.github/workflows/staging-build.yml index f793c230..c491ad03 100644 --- a/.github/workflows/staging-build.yml +++ b/.github/workflows/staging-build.yml @@ -1,42 +1,34 @@ -name: 'Staging Build' +name: Staging Build on: workflow_run: - workflows: ['Tests'] + workflows: ['CI'] branches: ['develop'] types: - completed jobs: deploy: - name: 'Deploy' if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14.x] steps: - - uses: actions/checkout@v1 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + - uses: actions/checkout@v3 with: - node-version: ${{ matrix.node-version }} - - uses: actions/cache@v2 + ref: develop + fetch-depth: 0 + + - name: Prepare Deploy + uses: ./.github/actions/prepare-deploy with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - name: npm ci for website - run: npm ci - working-directory: website - - run: npm run build -- --output-hashing=none --base-href=/ - working-directory: website + main-branch-name: develop + app-configuration: staging + nx-command: run-many - - uses: peaceiris/actions-gh-pages@v3 + - name: Publish + uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: website/dist/a2agc + publish_dir: deploy publish_branch: staging - commit_message: "Staging deploy -- ${{ github.event.head_commit.message }}" + commit_message: 'Staging deploy -- ${{ github.event.head_commit.message }}' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 76673f07..00000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: 'Tests' - -on: - push: - branches: - - main - - develop - pull_request: - types: ['opened', 'edited', 'synchronize'] - -jobs: - tests: - name: 'Tests' - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14.x] - - steps: - - uses: actions/checkout@v1 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - name: npm ci for website - run: npm ci - working-directory: website - - run: npm run lint - working-directory: website - - run: npm run test - working-directory: website - - name: SonarCloud Scan - uses: sonarsource/sonarcloud-github-action@master - with: - projectBaseDir: website - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - name: Slack Notification - if: always() - uses: kpritam/slack-job-status-action@v1 - with: - job-status: ${{ job.status }} - slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }} - channel: github diff --git a/.gitignore b/.gitignore index 46149641..ac94d039 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,8 @@ /raw-data container.sif website/debug.log + +.nx +node_modules +.angular +dist \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..103bd516 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +# Add files here to ignore them from prettier formatting +/dist +/coverage +/.nx/cache +.angular diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..544138be --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/data-processor/.gitignore b/apps/data-processor/.gitignore similarity index 100% rename from data-processor/.gitignore rename to apps/data-processor/.gitignore diff --git a/data-processor/README.md b/apps/data-processor/README.md similarity index 91% rename from data-processor/README.md rename to apps/data-processor/README.md index 3d29a0f1..e5cc4be5 100644 --- a/data-processor/README.md +++ b/apps/data-processor/README.md @@ -6,13 +6,13 @@ See [CHANGELOG](../CHANGELOG.md) ## Base Requirements -* bash -* Python 3+ -* Java 1.8+ -* Node JS 10+ -* sqlite3 -* sqlcipher -* GraphViz (dot) +- bash +- Python 3+ +- Java 1.8+ +- Node JS 10+ +- sqlite3 +- sqlcipher +- GraphViz (dot) ## Singularity diff --git a/data-processor/column-distribution-overrides.yml b/apps/data-processor/column-distribution-overrides.yml similarity index 100% rename from data-processor/column-distribution-overrides.yml rename to apps/data-processor/column-distribution-overrides.yml diff --git a/data-processor/constants.sh b/apps/data-processor/constants.sh similarity index 100% rename from data-processor/constants.sh rename to apps/data-processor/constants.sh diff --git a/data-processor/docs/analytics.md b/apps/data-processor/docs/analytics.md similarity index 100% rename from data-processor/docs/analytics.md rename to apps/data-processor/docs/analytics.md diff --git a/data-processor/docs/css/extra.css b/apps/data-processor/docs/css/extra.css similarity index 57% rename from data-processor/docs/css/extra.css rename to apps/data-processor/docs/css/extra.css index e5d5d217..8d8f8449 100644 --- a/data-processor/docs/css/extra.css +++ b/apps/data-processor/docs/css/extra.css @@ -1,20 +1,20 @@ -.toctree-l2>a.nav-item { +.toctree-l2 > a.nav-item { padding-left: 35px; } -.toctree-l3>a.nav-item { +.toctree-l3 > a.nav-item { padding-left: 45px; } -.toctree-l4>a.nav-item { +.toctree-l4 > a.nav-item { padding-left: 55px; } -.toctree-l5>a.nav-item { +.toctree-l5 > a.nav-item { padding-left: 65px; } -.toctree-l6>a.nav-item { +.toctree-l6 > a.nav-item { padding-left: 75px; } diff --git a/data-processor/docs/dataset/access.md b/apps/data-processor/docs/dataset/access.md similarity index 96% rename from data-processor/docs/dataset/access.md rename to apps/data-processor/docs/dataset/access.md index 2585fb79..8813493f 100644 --- a/data-processor/docs/dataset/access.md +++ b/apps/data-processor/docs/dataset/access.md @@ -1,2 +1 @@ # Data Access Requirements - diff --git a/apps/data-processor/docs/dataset/index.md b/apps/data-processor/docs/dataset/index.md new file mode 100644 index 00000000..07dd0c5c --- /dev/null +++ b/apps/data-processor/docs/dataset/index.md @@ -0,0 +1 @@ +# Overview diff --git a/data-processor/docs/dataset/tables.md b/apps/data-processor/docs/dataset/tables.md similarity index 99% rename from data-processor/docs/dataset/tables.md rename to apps/data-processor/docs/dataset/tables.md index 79eda664..317eff36 100644 --- a/data-processor/docs/dataset/tables.md +++ b/apps/data-processor/docs/dataset/tables.md @@ -1,4 +1,3 @@ - # deaths [View in the Schema Browser](../schema/tables/deaths.html) diff --git a/data-processor/docs/dataset/tables/table.template b/apps/data-processor/docs/dataset/tables/table.template similarity index 100% rename from data-processor/docs/dataset/tables/table.template rename to apps/data-processor/docs/dataset/tables/table.template diff --git a/data-processor/docs/dataset/validation.md b/apps/data-processor/docs/dataset/validation.md similarity index 100% rename from data-processor/docs/dataset/validation.md rename to apps/data-processor/docs/dataset/validation.md diff --git a/data-processor/docs/dataset/workflow.md b/apps/data-processor/docs/dataset/workflow.md similarity index 100% rename from data-processor/docs/dataset/workflow.md rename to apps/data-processor/docs/dataset/workflow.md diff --git a/data-processor/docs/examples/bar-visualization/combined-spec.vl.json b/apps/data-processor/docs/examples/bar-visualization/combined-spec.vl.json similarity index 91% rename from data-processor/docs/examples/bar-visualization/combined-spec.vl.json rename to apps/data-processor/docs/examples/bar-visualization/combined-spec.vl.json index d8212f20..f8ff6db1 100644 --- a/data-processor/docs/examples/bar-visualization/combined-spec.vl.json +++ b/apps/data-processor/docs/examples/bar-visualization/combined-spec.vl.json @@ -63,19 +63,19 @@ "as": "census_count" }, { - "aggregate": [ - { - "op": "sum", - "field": "count", - "as": "count" - }, - { - "op": "sum", - "field": "census_count", - "as": "census_count" - } - ], - "groupby": ["gender", "age_group"] + "aggregate": [ + { + "op": "sum", + "field": "count", + "as": "count" + }, + { + "op": "sum", + "field": "census_count", + "as": "census_count" + } + ], + "groupby": ["gender", "age_group"] }, { "joinaggregate": [ @@ -175,21 +175,13 @@ "selection": { "year": { "type": "multi", - "fields": [ - "year" - ], + "fields": ["year"], "bind": "legend", - "init": [ - {"year": 2018}, - {"year": 2017}, - {"year": 2016} - ] + "init": [{ "year": 2018 }, { "year": 2017 }, { "year": 2016 }] }, "gender": { "type": "multi", - "fields": [ - "gender" - ], + "fields": ["gender"], "bind": "legend" } }, @@ -209,7 +201,7 @@ "labels": true, "titleFontSize": 14 }, - "scale": {"domain": [0,0.3]} + "scale": { "domain": [0, 0.3] } }, "y": { "title": null, @@ -233,10 +225,10 @@ "symbolOffset": 5, "labelColor": "#212121" }, - "scale": {"range": ["#DA5E8C", "#586CBB"]} + "scale": { "range": ["#DA5E8C", "#586CBB"] } }, "opacity": { - "condition": {"selection": "gender", "value": 1}, + "condition": { "selection": "gender", "value": 1 }, "value": 0 }, "strokeWidth": { @@ -251,7 +243,9 @@ "rowPadding": 10, "symbolSize": 0, "title": "", - "values": [2018, 2017, 2016, 2015, 2014, 2013, 2012, 2011, 2010] + "values": [ + 2018, 2017, 2016, 2015, 2014, 2013, 2012, 2011, 2010 + ] } } } @@ -328,7 +322,7 @@ "grid": false, "labels": true }, - "scale": {"domain": [0,0.3]} + "scale": { "domain": [0, 0.3] } }, "y": { "title": null, @@ -340,7 +334,7 @@ "field": "gender" }, "opacity": { - "condition": {"selection": "gender", "value": 1}, + "condition": { "selection": "gender", "value": 1 }, "value": 0 } } @@ -360,7 +354,7 @@ "labelFlush": false, "labels": true }, - "scale": {"domain": [0,0.13]} + "scale": { "domain": [0, 0.13] } }, "y": { "title": null, @@ -466,11 +460,11 @@ "as": "census_percentage" } ], - + "facet": { "field": "year", "title": null, - "header": { + "header": { "labelFontSize": 0 }, "sort": "descending" @@ -531,8 +525,8 @@ "width": 1, "height": 1, "mark": { - "type": "text", - "style": "label", + "type": "text", + "style": "label", "fontSize": 14 }, "encoding": { @@ -558,21 +552,17 @@ "selection": { "year": { "type": "multi", - "fields": [ - "year" - ], + "fields": ["year"], "bind": "legend", "init": [ - {"year": 2018}, - {"year": 2017}, - {"year": 2016} + { "year": 2018 }, + { "year": 2017 }, + { "year": 2016 } ] }, "gender": { "type": "multi", - "fields": [ - "gender" - ], + "fields": ["gender"], "bind": "legend" } }, @@ -611,10 +601,10 @@ "orient": "left", "values": ["M", "F", "Total Population"] }, - "scale": {"range": ["#DA5E8C", "#586CBB"]} + "scale": { "range": ["#DA5E8C", "#586CBB"] } }, "opacity": { - "condition": {"selection": "gender", "value": 1}, + "condition": { "selection": "gender", "value": 1 }, "value": 0 }, "strokeWidth": { @@ -627,7 +617,9 @@ "rowPadding": 10, "symbolSize": 0, "title": "", - "values": [2018, 2017, 2016, 2015, 2014, 2013, 2012, 2011, 2010] + "values": [ + 2018, 2017, 2016, 2015, 2014, 2013, 2012, 2011, 2010 + ] } } } @@ -645,7 +637,7 @@ "grid": false, "labels": true }, - "scale": {"domain": [0,0.3]} + "scale": { "domain": [0, 0.3] } }, "y": { "type": "ordinal", @@ -715,7 +707,7 @@ "field": "gender" }, "opacity": { - "condition": {"selection": "gender", "value": 1}, + "condition": { "selection": "gender", "value": 1 }, "value": 0 } } @@ -735,7 +727,7 @@ "labelFlush": false, "labels": true }, - "scale": {"domain": [0,0.3]} + "scale": { "domain": [0, 0.3] } }, "y": { "title": null, diff --git a/data-processor/docs/examples/bar-visualization/multi-spec.vl.json b/apps/data-processor/docs/examples/bar-visualization/multi-spec.vl.json similarity index 91% rename from data-processor/docs/examples/bar-visualization/multi-spec.vl.json rename to apps/data-processor/docs/examples/bar-visualization/multi-spec.vl.json index cdbf2868..0f7e0d2d 100644 --- a/data-processor/docs/examples/bar-visualization/multi-spec.vl.json +++ b/apps/data-processor/docs/examples/bar-visualization/multi-spec.vl.json @@ -89,11 +89,11 @@ "as": "census_percentage" } ], - + "facet": { "field": "year", "title": null, - "header": { + "header": { "labelFontSize": 0 }, "sort": "descending" @@ -148,7 +148,7 @@ { "width": 1, "height": 1, - "mark": {"type": "text", "style": "label"}, + "mark": { "type": "text", "style": "label" }, "encoding": { "text": { "field": "year" @@ -170,21 +170,13 @@ "selection": { "year": { "type": "multi", - "fields": [ - "year" - ], + "fields": ["year"], "bind": "legend", - "init": [ - {"year": 2018}, - {"year": 2017}, - {"year": 2016} - ] + "init": [{ "year": 2018 }, { "year": 2017 }, { "year": 2016 }] }, "gender": { "type": "multi", - "fields": [ - "gender" - ], + "fields": ["gender"], "bind": "legend" } }, @@ -202,7 +194,7 @@ "labelFlush": false, "labels": true }, - "scale": {"domain": [0,0.3]} + "scale": { "domain": [0, 0.3] } }, "y": { "title": null, @@ -222,10 +214,10 @@ "orient": "left", "values": ["M", "F", "Total Population"] }, - "scale": {"range": ["#DA5E8C", "#586CBB"]} + "scale": { "range": ["#DA5E8C", "#586CBB"] } }, "opacity": { - "condition": {"selection": "gender", "value": 1}, + "condition": { "selection": "gender", "value": 1 }, "value": 0 }, "strokeWidth": { @@ -238,7 +230,9 @@ "rowPadding": 10, "symbolSize": 0, "title": "", - "values": [2018, 2017, 2016, 2015, 2014, 2013, 2012, 2011, 2010] + "values": [ + 2018, 2017, 2016, 2015, 2014, 2013, 2012, 2011, 2010 + ] } } } @@ -319,7 +313,7 @@ "grid": false, "labels": true }, - "scale": {"domain": [0,0.3]} + "scale": { "domain": [0, 0.3] } }, "y": { "title": null, @@ -331,7 +325,7 @@ "field": "gender" }, "opacity": { - "condition": {"selection": "gender", "value": 1}, + "condition": { "selection": "gender", "value": 1 }, "value": 0 } } @@ -351,7 +345,7 @@ "labelFlush": false, "labels": true }, - "scale": {"domain": [0,0.13]} + "scale": { "domain": [0, 0.13] } }, "y": { "title": null, @@ -395,5 +389,4 @@ } ] } - } diff --git a/data-processor/docs/examples/bar-visualization/page.md b/apps/data-processor/docs/examples/bar-visualization/page.md similarity index 90% rename from data-processor/docs/examples/bar-visualization/page.md rename to apps/data-processor/docs/examples/bar-visualization/page.md index 119cfe98..efd7a62a 100644 --- a/data-processor/docs/examples/bar-visualization/page.md +++ b/apps/data-processor/docs/examples/bar-visualization/page.md @@ -1,5 +1,6 @@ # Age group and gender of accidental drug overdose deaths and population in Marion County 2010-2018 ---------------------------------------------------------------------------------------------------- + +--- This example visualization shows a bar graph of opioid deaths broken down by gender and age group compared to the census data of Marion County, Indiana. @@ -10,7 +11,7 @@ This example visualization shows a bar graph of opioid deaths broken down by gen #### Legend -- Gray bars represent the percentage of opioid deaths in the corresponding age group. +- Gray bars represent the percentage of opioid deaths in the corresponding age group. - Outlined bars represent the percentage of the population in the corresponding age group according to the census data. ## Data and Graphic Variable Extraction diff --git a/data-processor/docs/examples/bar-visualization/single-spec.vl.json b/apps/data-processor/docs/examples/bar-visualization/single-spec.vl.json similarity index 95% rename from data-processor/docs/examples/bar-visualization/single-spec.vl.json rename to apps/data-processor/docs/examples/bar-visualization/single-spec.vl.json index 89d2d3b9..b5213fe8 100644 --- a/data-processor/docs/examples/bar-visualization/single-spec.vl.json +++ b/apps/data-processor/docs/examples/bar-visualization/single-spec.vl.json @@ -57,19 +57,19 @@ "as": "census_count" }, { - "aggregate": [ - { - "op": "sum", - "field": "count", - "as": "count" - }, - { - "op": "sum", - "field": "census_count", - "as": "census_count" - } - ], - "groupby": ["gender", "age_group"] + "aggregate": [ + { + "op": "sum", + "field": "count", + "as": "count" + }, + { + "op": "sum", + "field": "census_count", + "as": "census_count" + } + ], + "groupby": ["gender", "age_group"] }, { "joinaggregate": [ diff --git a/data-processor/docs/examples/geo-zoom.js b/apps/data-processor/docs/examples/geo-zoom.js similarity index 56% rename from data-processor/docs/examples/geo-zoom.js rename to apps/data-processor/docs/examples/geo-zoom.js index c4362479..d94a4969 100644 --- a/data-processor/docs/examples/geo-zoom.js +++ b/apps/data-processor/docs/examples/geo-zoom.js @@ -1,119 +1,135 @@ -/** - * Geographical zoom configuration. - * @typedef {Object} GeoZoomOptions - * @type {[number, number]} center - Center longitude/latitude pair. - * @type {[number, number]} zoomLevels - Min/max pair of zoom levels. - * @type {number} [initialZoom] - Initial zoom level. - */ - -/** - * @type {GeoZoomOptions} - */ -var INDIANA_ZOOM_CONFIG = { - center: [86.44305475, 39.76622477], - zoomLevels: [6400, 250000] -} - -/** - * Adds zooming functionality to a vega spec with a geographic projection. - * - * @param {any} spec - The vega specification object. - * @param {GeoZoomOptions} opts - The zoom configuration. - */ -function addGeoZoom(spec, opts) { - var signals = spec.signals || (spec.signals = []); - var projection = spec.projections[0]; // Assumes single geo projection - - signals.push( - { name: 'tx', update: 'width / 2' }, - { name: 'ty', update: 'height / 2' }, - { - name: 'scale', - value: opts.initialZoom != null ? opts.initialZoom : opts.zoomLevels[0], - on: [{ - events: { type: 'wheel', consume: true }, - update: [ - 'clamp(scale * pow(1.0015, -event.deltaY * pow(16, event.deltaMode)), ', - opts.zoomLevels[0], - ', ', - opts.zoomLevels[1], - ')' - ].join('') - }] - }, - { - name: 'angles', - value: [0, 0], - on: [{ - events: 'mousedown', - update: '[rotateX, centerY]' - }] - }, - { - name: 'cloned', - value: null, - on: [{ - events: 'mousedown', - update: 'copy("' + projection.name + '")' - }] - }, - { - name: 'start', - value: null, - on: [{ - events: 'mousedown', - update: 'invert(cloned, xy())' - }] - }, - { - name: 'drag', - value: null, - on: [{ - events: '[mousedown, window:mouseup] > window:mousemove', - update: 'invert(cloned, xy())' - }] - }, - { - name: 'delta', - value: null, - on: [{ - events: { signal: 'drag' }, - update: '[drag[0] - start[0], start[1] - drag[1]]' - }] - }, - { - name: 'rotateX', - value: opts.center[0], - on: [{ - events: { signal: 'delta' }, - update: 'angles[0] + delta[0]' - }] - }, - { - name: 'centerY', - value: opts.center[1], - on: [{ - events: { signal: 'delta' }, - update: 'clamp(angles[1] + delta[1], -60, 60)' - }] - } - ); - - Object.assign(projection, { - scale: { signal: 'scale' }, - rotate: [{ signal: 'rotateX' }, 0, 0], - center: [0, { signal: 'centerY' }], - translate: [{ signal: 'tx' }, { signal: 'ty' }] - }); - delete projection.size; -} - -/** - * Patches a vega specification to add geographical zooming focused on Indiana. - * - * @param {any} spec - The vega specification object. - */ -function patchIndianaGeoZoom(spec) { - addGeoZoom(spec, INDIANA_ZOOM_CONFIG) - return spec; -} +/** + * Geographical zoom configuration. + * @typedef {Object} GeoZoomOptions + * @type {[number, number]} center - Center longitude/latitude pair. + * @type {[number, number]} zoomLevels - Min/max pair of zoom levels. + * @type {number} [initialZoom] - Initial zoom level. + */ + +/** + * @type {GeoZoomOptions} + */ +var INDIANA_ZOOM_CONFIG = { + center: [86.44305475, 39.76622477], + zoomLevels: [6400, 250000], +}; + +/** + * Adds zooming functionality to a vega spec with a geographic projection. + * + * @param {any} spec - The vega specification object. + * @param {GeoZoomOptions} opts - The zoom configuration. + */ +function addGeoZoom(spec, opts) { + var signals = spec.signals || (spec.signals = []); + var projection = spec.projections[0]; // Assumes single geo projection + + signals.push( + { name: 'tx', update: 'width / 2' }, + { name: 'ty', update: 'height / 2' }, + { + name: 'scale', + value: opts.initialZoom != null ? opts.initialZoom : opts.zoomLevels[0], + on: [ + { + events: { type: 'wheel', consume: true }, + update: [ + 'clamp(scale * pow(1.0015, -event.deltaY * pow(16, event.deltaMode)), ', + opts.zoomLevels[0], + ', ', + opts.zoomLevels[1], + ')', + ].join(''), + }, + ], + }, + { + name: 'angles', + value: [0, 0], + on: [ + { + events: 'mousedown', + update: '[rotateX, centerY]', + }, + ], + }, + { + name: 'cloned', + value: null, + on: [ + { + events: 'mousedown', + update: 'copy("' + projection.name + '")', + }, + ], + }, + { + name: 'start', + value: null, + on: [ + { + events: 'mousedown', + update: 'invert(cloned, xy())', + }, + ], + }, + { + name: 'drag', + value: null, + on: [ + { + events: '[mousedown, window:mouseup] > window:mousemove', + update: 'invert(cloned, xy())', + }, + ], + }, + { + name: 'delta', + value: null, + on: [ + { + events: { signal: 'drag' }, + update: '[drag[0] - start[0], start[1] - drag[1]]', + }, + ], + }, + { + name: 'rotateX', + value: opts.center[0], + on: [ + { + events: { signal: 'delta' }, + update: 'angles[0] + delta[0]', + }, + ], + }, + { + name: 'centerY', + value: opts.center[1], + on: [ + { + events: { signal: 'delta' }, + update: 'clamp(angles[1] + delta[1], -60, 60)', + }, + ], + } + ); + + Object.assign(projection, { + scale: { signal: 'scale' }, + rotate: [{ signal: 'rotateX' }, 0, 0], + center: [0, { signal: 'centerY' }], + translate: [{ signal: 'tx' }, { signal: 'ty' }], + }); + delete projection.size; +} + +/** + * Patches a vega specification to add geographical zooming focused on Indiana. + * + * @param {any} spec - The vega specification object. + */ +function patchIndianaGeoZoom(spec) { + addGeoZoom(spec, INDIANA_ZOOM_CONFIG); + return spec; +} diff --git a/data-processor/docs/examples/getting_started.md b/apps/data-processor/docs/examples/getting_started.md similarity index 100% rename from data-processor/docs/examples/getting_started.md rename to apps/data-processor/docs/examples/getting_started.md diff --git a/data-processor/docs/examples/vis-geomap-opioid-deaths.md b/apps/data-processor/docs/examples/vis-geomap-opioid-deaths.md similarity index 87% rename from data-processor/docs/examples/vis-geomap-opioid-deaths.md rename to apps/data-processor/docs/examples/vis-geomap-opioid-deaths.md index 4804a674..a09643bc 100644 --- a/data-processor/docs/examples/vis-geomap-opioid-deaths.md +++ b/apps/data-processor/docs/examples/vis-geomap-opioid-deaths.md @@ -2,17 +2,16 @@ This example visualization shows a geographic map of opioid deaths that occurred # Data and Graphic Variable Extraction - Opioid death data was fetched from the database using the following query: {{ include_sql('src/create-vis-geomap-opioid-deaths-data.sql') }} In addition to extracting raw data, the query also creates columns for data-variable to graphic-variable mappings as follows: -* Color mapping was created from the `SEX` column, if `SEX` was `male`, a value of `blue` was assigned and for `female`, a value of `pink` was assigned. -* Stroke Color mapping was created from the `HOME_STATE` column, if `HOME_STATE` was `IN`, a value of `#bebebe` was assigned, else `black` was assigned. -* Shape mapping was created from `HOME_STATE`, if `HOME_STATE` was `IN`, a value of `circle` as assigned, else `triangle` was assigned. -* Area Size mapping was created from `N_OPIOID_PRESCRIPTIONS` and results interpolated in the range of `25 - 525`. +- Color mapping was created from the `SEX` column, if `SEX` was `male`, a value of `blue` was assigned and for `female`, a value of `pink` was assigned. +- Stroke Color mapping was created from the `HOME_STATE` column, if `HOME_STATE` was `IN`, a value of `#bebebe` was assigned, else `black` was assigned. +- Shape mapping was created from `HOME_STATE`, if `HOME_STATE` was `IN`, a value of `circle` as assigned, else `triangle` was assigned. +- Area Size mapping was created from `N_OPIOID_PRESCRIPTIONS` and results interpolated in the range of `25 - 525`. The results were exported to a [csv file](../data/vis-geomap-opioid-deaths.csv) for loading into Make-a-Vis. diff --git a/data-processor/docs/examples/visualization2.md b/apps/data-processor/docs/examples/visualization2.md similarity index 94% rename from data-processor/docs/examples/visualization2.md rename to apps/data-processor/docs/examples/visualization2.md index 80a583f5..7381664a 100644 --- a/data-processor/docs/examples/visualization2.md +++ b/apps/data-processor/docs/examples/visualization2.md @@ -1,48 +1,49 @@ -# Opioid Death Datasets ---------------------------- - -Marion County by History of Opioid Prescription, Previous Overdose, Incarceration, Health Data (2010-2018) - -{{ vega.header() }} -{{ vega.include('./visualization2b.vl.json') }} - -[(Source Vega-Lite Visualization Spec)](./visualization2b.vl.json) - -## Usage - -Click and drag a region in the Date of Death vs # Deaths graph in order to filter the data by time interval. Use the mouse wheel in order to increase or decrease the length of the interval. You may also slide the selected interval left or right along the date axis. - -## Purpose and Details - -The purpose of this visualization is to provide insight on the frequency of common events that commonly occur before an overdose death. - -Four touchpoints are analyzed: - -1. Opioid Prescription (Rx): Whether or not an individual had received an opioid prescription. This includes any opioid or benzo medications that were provided to the individual. -2. Overdose (OD): Whether or not an individual had a previous overdose requiring EMS intervention. -3. Incarceration (Jail): Whether or not an individual had been in jail. -4. Health Data (Health): Whether or not an individual had a recent encounter with healthcare services (excluding opioid prescriptions). - -The visualization provides a comparison of the number of cases across each possible combination of the above touchpoints, including the case where no touchpoints apply (∅). - -2331 total overdose deaths from 2010-2018 were referenced across four separate datasets corresponding to each of the four touchpoints. The timeframes for each dataset are as follows: - -1. Medications: 2010-2018 (based on date of death) -2. EMS Incidents: 2011-2018 (based on date of overdose incident) -3. Incarcerations: 2014-2018 (based on date of incarceration) -4. Health Encounters: 2010-2018 (based on date of death) - -## Data and Graphic Variable Extraction - -The table was created using the following SQL script: - -{{ include_sql('src/visualization2/data.sql') }} - -The resulting aggregated data can be found [here](../data/visualization2/data.csv). - -Following rules were used to determined each of the following attributes: - -1. Prescription VS No Prescription: True if any one of the flags OPIOD_FLAG or BENZO_FLAG was set in `medications` table for the particular CASE_NUMBER, False otherwise. -2. Overdose VS No Overdose: True if OVERDOSE_CC_MOI or NALOXONE_DUMMY was set in `ems_incidents` table for the particular CASE_NUMBER, False otherwise. -3. Justice Involvement VS No Involvement: If the particular CASE_NUMBER is present in `incarcerations` table then True, False otherwise. -4. Health Data VS No Health Data: True if CASE_NUMBER is present in `encounters` table, False otherwise. \ No newline at end of file +# Opioid Death Datasets + +--- + +Marion County by History of Opioid Prescription, Previous Overdose, Incarceration, Health Data (2010-2018) + +{{ vega.header() }} +{{ vega.include('./visualization2b.vl.json') }} + +[(Source Vega-Lite Visualization Spec)](./visualization2b.vl.json) + +## Usage + +Click and drag a region in the Date of Death vs # Deaths graph in order to filter the data by time interval. Use the mouse wheel in order to increase or decrease the length of the interval. You may also slide the selected interval left or right along the date axis. + +## Purpose and Details + +The purpose of this visualization is to provide insight on the frequency of common events that commonly occur before an overdose death. + +Four touchpoints are analyzed: + +1. Opioid Prescription (Rx): Whether or not an individual had received an opioid prescription. This includes any opioid or benzo medications that were provided to the individual. +2. Overdose (OD): Whether or not an individual had a previous overdose requiring EMS intervention. +3. Incarceration (Jail): Whether or not an individual had been in jail. +4. Health Data (Health): Whether or not an individual had a recent encounter with healthcare services (excluding opioid prescriptions). + +The visualization provides a comparison of the number of cases across each possible combination of the above touchpoints, including the case where no touchpoints apply (∅). + +2331 total overdose deaths from 2010-2018 were referenced across four separate datasets corresponding to each of the four touchpoints. The timeframes for each dataset are as follows: + +1. Medications: 2010-2018 (based on date of death) +2. EMS Incidents: 2011-2018 (based on date of overdose incident) +3. Incarcerations: 2014-2018 (based on date of incarceration) +4. Health Encounters: 2010-2018 (based on date of death) + +## Data and Graphic Variable Extraction + +The table was created using the following SQL script: + +{{ include_sql('src/visualization2/data.sql') }} + +The resulting aggregated data can be found [here](../data/visualization2/data.csv). + +Following rules were used to determined each of the following attributes: + +1. Prescription VS No Prescription: True if any one of the flags OPIOD_FLAG or BENZO_FLAG was set in `medications` table for the particular CASE_NUMBER, False otherwise. +2. Overdose VS No Overdose: True if OVERDOSE_CC_MOI or NALOXONE_DUMMY was set in `ems_incidents` table for the particular CASE_NUMBER, False otherwise. +3. Justice Involvement VS No Involvement: If the particular CASE_NUMBER is present in `incarcerations` table then True, False otherwise. +4. Health Data VS No Health Data: True if CASE_NUMBER is present in `encounters` table, False otherwise. diff --git a/data-processor/docs/examples/visualization2.vl.json b/apps/data-processor/docs/examples/visualization2.vl.json similarity index 56% rename from data-processor/docs/examples/visualization2.vl.json rename to apps/data-processor/docs/examples/visualization2.vl.json index 7c1036fb..19696ea4 100644 --- a/data-processor/docs/examples/visualization2.vl.json +++ b/apps/data-processor/docs/examples/visualization2.vl.json @@ -9,14 +9,26 @@ }, "encoding": { - "y": {"field": "Touchpoint A", "type": "ordinal", "sort": ["Rx", "Rx + OD", "OD", "∅"], "title": null, "axis": {"labelFontSize": 15}}, - "x": {"field": "Touchpoint B", "type": "ordinal", "sort": ["∅", "Jail", "Jail + Health", "Health"], "title": null, "axis": {"labelAngle": 0, "labelFontSize": 15}} + "y": { + "field": "Touchpoint A", + "type": "ordinal", + "sort": ["Rx", "Rx + OD", "OD", "∅"], + "title": null, + "axis": { "labelFontSize": 15 } + }, + "x": { + "field": "Touchpoint B", + "type": "ordinal", + "sort": ["∅", "Jail", "Jail + Health", "Health"], + "title": null, + "axis": { "labelAngle": 0, "labelFontSize": 15 } + } }, "layer": [ { - "selection": {"highlight": {"type": "single"}}, - "mark": {"type": "rect", "strokeWidth": 2}, + "selection": { "highlight": { "type": "single" } }, + "mark": { "type": "rect", "strokeWidth": 2 }, "encoding": { "tooltip": [ { @@ -38,14 +50,14 @@ "fill": { "field": "Deaths", "type": "quantitative", - "scale": {"type": "sqrt", "scheme": "lightmulti"} + "scale": { "type": "sqrt", "scheme": "lightmulti" } } } }, { "mark": "text", "encoding": { - "text": {"field": "Set", "type": "ordinal"} + "text": { "field": "Set", "type": "ordinal" } } }, { @@ -54,27 +66,33 @@ "fontSize": 0 }, "encoding": { - "text": {"field": " ", "type": "ordinal"}, + "text": { "field": " ", "type": "ordinal" }, "fill": { "field": " ", "type": "ordinal", "legend": { - "values": ["Rx: Opioid Prescription", "OD: Overdose", "Jail: Incarceration", "Health: Health Data", "∅: No Touchpoints"], + "values": [ + "Rx: Opioid Prescription", + "OD: Overdose", + "Jail: Incarceration", + "Health: Health Data", + "∅: No Touchpoints" + ], "symbolOffset": -15 } } } } ], - + "config": { "scale": { "bandPaddingInner": 0, "bandPaddingOuter": 0 }, - "view": {"step": 40}, + "view": { "step": 40 }, "axis": { "domain": false } } -} \ No newline at end of file +} diff --git a/data-processor/docs/examples/visualization2b.vl.json b/apps/data-processor/docs/examples/visualization2b.vl.json similarity index 73% rename from data-processor/docs/examples/visualization2b.vl.json rename to apps/data-processor/docs/examples/visualization2b.vl.json index b5ade26b..609b50f7 100644 --- a/data-processor/docs/examples/visualization2b.vl.json +++ b/apps/data-processor/docs/examples/visualization2b.vl.json @@ -2,7 +2,7 @@ "$schema": "https://vega.github.io/schema/vega-lite/v4.json", "padding": 0, "config": { - "view": {"strokeOpacity": 0} + "view": { "strokeOpacity": 0 } }, "data": { @@ -20,7 +20,7 @@ "width": 800, "height": 600, "transform": [ - {"filter": { "selection": "period" }}, + { "filter": { "selection": "period" } }, { "calculate": "if(datum['Touchpoint A'] == 1 || datum['Touchpoint A'] == 2, 'OD = Overdose', 0)", "as": "OD" @@ -41,29 +41,29 @@ "calculate": "if(datum['Touchpoint A'] == 0 && datum['Touchpoint B'] == 0, '∅ = No Touchpoints', 0)", "as": "∅" }, - { - "filter": { "selection": "Rx" } - }, - { - "filter": { "selection": "OD" } - }, - { - "filter": { "selection": "Jail" } - }, - { - "filter": { "selection": "Health" } - }, - { - "filter": { "selection": "Empty" } - } + { + "filter": { "selection": "Rx" } + }, + { + "filter": { "selection": "OD" } + }, + { + "filter": { "selection": "Jail" } + }, + { + "filter": { "selection": "Health" } + }, + { + "filter": { "selection": "Empty" } + } ], "encoding": { "y": { - "field": "Touchpoint A", + "field": "Touchpoint A", "type": "quantitative", - "title": null, + "title": null, "scale": { - "domain": [-0.5,3.5] + "domain": [-0.5, 3.5] }, "axis": { "grid": false, @@ -77,7 +77,7 @@ "type": "quantitative", "title": null, "scale": { - "domain": [-0.5,3.5] + "domain": [-0.5, 3.5] }, "axis": { "grid": false, @@ -91,10 +91,11 @@ { "transform": [ { - "aggregate": [{"op": "sum", "field": "True", "as": "sum"}], "groupby": ["Touchpoint A", "Touchpoint B"] + "aggregate": [{ "op": "sum", "field": "True", "as": "sum" }], + "groupby": ["Touchpoint A", "Touchpoint B"] } ], - "mark": {"type": "circle", "yOffset": 10}, + "mark": { "type": "circle", "yOffset": 10 }, "encoding": { "tooltip": [ { @@ -111,14 +112,14 @@ "size": { "field": "sum", "type": "quantitative", - "scale": {"range": [0, 6000]}, + "scale": { "range": [0, 6000] }, "legend": null }, "color": { "field": "sum", "type": "quantitative", "scale": { - "type": "sqrt", + "type": "sqrt", "scheme": "blues" } } @@ -127,17 +128,19 @@ { "transform": [ { - "aggregate": [{ - "op": "max", - "field": "Set", - "as": "label" - }], + "aggregate": [ + { + "op": "max", + "field": "Set", + "as": "label" + } + ], "groupby": ["Touchpoint A", "Touchpoint B"] } ], - "mark": {"type": "text", "dy": -65, "fontSize": 16}, + "mark": { "type": "text", "dy": -65, "fontSize": 16 }, "encoding": { - "text": {"field": "label", "type": "nominal"} + "text": { "field": "label", "type": "nominal" } } }, @@ -147,7 +150,7 @@ "fontSize": 0 }, "selection": { - "OD": {"type": "multi", "bind": "legend", "fields": ["OD"]} + "OD": { "type": "multi", "bind": "legend", "fields": ["OD"] } }, "encoding": { @@ -173,7 +176,7 @@ "fontSize": 0 }, "selection": { - "Rx": {"type": "multi", "bind": "legend", "fields": ["Rx"]} + "Rx": { "type": "multi", "bind": "legend", "fields": ["Rx"] } }, "encoding": { @@ -198,7 +201,7 @@ "fontSize": 0 }, "selection": { - "Jail": {"type": "multi", "bind": "legend", "fields": ["Jail"]} + "Jail": { "type": "multi", "bind": "legend", "fields": ["Jail"] } }, "encoding": { @@ -223,7 +226,7 @@ "fontSize": 0 }, "selection": { - "Empty": {"type": "multi", "bind": "legend", "fields": ["∅"]} + "Empty": { "type": "multi", "bind": "legend", "fields": ["∅"] } }, "encoding": { @@ -250,7 +253,11 @@ "fontSize": 0 }, "selection": { - "Health": {"type": "multi", "bind": "legend", "fields": ["Health"]} + "Health": { + "type": "multi", + "bind": "legend", + "fields": ["Health"] + } }, "encoding": { @@ -271,7 +278,7 @@ }, { - "mark": {"type": "text", "fontSize": 0}, + "mark": { "type": "text", "fontSize": 0 }, "encoding": { "color": { "title": "# Deaths", @@ -295,17 +302,24 @@ { "transform": [ { - "aggregate": [{ - "op": "sum", - "field": "True", - "as": "sum" - }], + "aggregate": [ + { + "op": "sum", + "field": "True", + "as": "sum" + } + ], "groupby": ["Touchpoint A", "Touchpoint B"] } ], - "mark": {"type": "text", "dy": -45, "fontStyle": "bold", "fontSize": 16}, + "mark": { + "type": "text", + "dy": -45, + "fontStyle": "bold", + "fontSize": 16 + }, "encoding": { - "text": {"field": "sum", "type": "quantitative"} + "text": { "field": "sum", "type": "quantitative" } } } ] @@ -331,10 +345,10 @@ }, "selection": { "period": { - "type": "interval", + "type": "interval", "encodings": ["x"], "on": "[mousedown, window:mouseup] > window:mousemove{150, 300}", - "mark": {"fill": "#6ea7ef", "fillOpacity": 0.15} + "mark": { "fill": "#6ea7ef", "fillOpacity": 0.15 } } }, "encoding": { @@ -350,11 +364,25 @@ "labelFlush": false, "labelExpr": "[timeFormat(datum.value, '%m') == '01' ? timeFormat(datum.value, '%Y') : '']", "gridDash": { - "condition": {"test": {"field": "value", "timeUnit": "month", "equal": 7}, "value": [2,2]}, + "condition": { + "test": { + "field": "value", + "timeUnit": "month", + "equal": 7 + }, + "value": [2, 2] + }, "value": [] }, "gridColor": { - "condition": {"test": {"field": "value", "timeUnit": "month", "equal": 1}, "value": "#BDBDBD"}, + "condition": { + "test": { + "field": "value", + "timeUnit": "month", + "equal": 1 + }, + "value": "#BDBDBD" + }, "value": "#e0e0e0" } } @@ -370,15 +398,24 @@ "tickColor": "#757575", "domainColor": "#757575", "gridOpacity": { - "condition": {"test": {"field": "index", "equal": 1}, "value": 0}, + "condition": { + "test": { "field": "index", "equal": 1 }, + "value": 0 + }, "value": 1 }, "tickOpacity": { - "condition": {"test": {"field": "index", "equal": 1}, "value": 0}, + "condition": { + "test": { "field": "index", "equal": 1 }, + "value": 0 + }, "value": 1 }, "labelFontSize": { - "condition": {"test": {"field": "index", "equal": 1}, "value": 0}, + "condition": { + "test": { "field": "index", "equal": 1 }, + "value": 0 + }, "value": 10 } } diff --git a/data-processor/docs/examples/visualization4.md b/apps/data-processor/docs/examples/visualization4.md similarity index 90% rename from data-processor/docs/examples/visualization4.md rename to apps/data-processor/docs/examples/visualization4.md index 281e416f..b439bd7a 100644 --- a/data-processor/docs/examples/visualization4.md +++ b/apps/data-processor/docs/examples/visualization4.md @@ -1,27 +1,28 @@ -# Accidental drug overdose death in Marion County by substance, sex and age group 2010-2018 --------------------------------------------- - -This visualization shows heatmaps of opioid deaths categorized by gender, age group, substance and year. - -{{vega.header()}} -{{include_vega_ext('visualization4.vl.json')}} - -([Source Vega-Lite Visualization Spec](./visualization4.vl.json)) -## Data and Graphic Variable Extraction - -Opiod death data was fetched from the database using the following query - -The given query gets data for the following substances: - - 1. Heroin - 2. Cocaine - 3. Fentanyl - 4. Prescription Opioid - 5. Any Opioid - 6. Benzodiazepine - 7. Methamphetamine - - -{{ include_sql('src/visualization4/data.sql') }} - -The extracted data can be found [here](../data/visualization4/data.csv) \ No newline at end of file +# Accidental drug overdose death in Marion County by substance, sex and age group 2010-2018 + +--- + +This visualization shows heatmaps of opioid deaths categorized by gender, age group, substance and year. + +{{vega.header()}} +{{include_vega_ext('visualization4.vl.json')}} + +([Source Vega-Lite Visualization Spec](./visualization4.vl.json)) + +## Data and Graphic Variable Extraction + +Opiod death data was fetched from the database using the following query + +The given query gets data for the following substances: + + 1. Heroin + 2. Cocaine + 3. Fentanyl + 4. Prescription Opioid + 5. Any Opioid + 6. Benzodiazepine + 7. Methamphetamine + +{{ include_sql('src/visualization4/data.sql') }} + +The extracted data can be found [here](../data/visualization4/data.csv) diff --git a/data-processor/docs/examples/visualization4.vl.json b/apps/data-processor/docs/examples/visualization4.vl.json similarity index 88% rename from data-processor/docs/examples/visualization4.vl.json rename to apps/data-processor/docs/examples/visualization4.vl.json index e152dee4..464fb0f0 100644 --- a/data-processor/docs/examples/visualization4.vl.json +++ b/apps/data-processor/docs/examples/visualization4.vl.json @@ -1,723 +1,667 @@ -{ - "$schema": "https://vega.github.io/schema/vega-lite/v4.json", - "data": { - "url": "../data/visualization4/data.csv" - }, - "autosize": { - "type": "pad", - "resize": true - }, - "config": { - "mark": { - "tooltip": null - }, - "view": { - "stroke": "transparent" - }, - "style": { - "guide-label": { - "fontSize": 11 - } - } - }, - "transform": [ - { - "filter": { - "selection": "filter_selection" - } - }, - { - "filter": { - "selection": "substance_selection" - } - }, - { - "joinaggregate": [ - { - "as": "TOTAL_DEATHS", - "field": "CASE_NUMBER", - "op": "distinct" - } - ], - "groupby": [ - "AGE_GROUP", - "YEAR" - ] - }, - { - "calculate": "datum.OVERDOSE_DUMMY == 1 || datum.OVERDOSE_CC_MOI == 1", - "as": "HAS_OVERDOSE_DATA" - }, - { - "calculate": "datum.HAS_OVERDOSE_DATA == true && datum.MAX_INCIDENT_DATE < datum.DATE_OF_DEATH ? 'Had Previous Overdose' : 'No Previous Overdose'", - "as": "HAD_PREVIOUS_OVERDOSE" - } - ], - "spacing": 150, - "hconcat": [ - { - "spacing": 40, - "vconcat": [ - { - "mark": { - "type": "text", - "fontSize": 0 - }, - "selection": { - "substance_selection": { - "type": "multi", - "fields": [ - "SUBSTANCE_NAME" - ], - "bind": { - "legend": "dblclick" - }, - "init": [ - { - "SUBSTANCE_NAME": "All Substances" - }, - { - "SUBSTANCE_NAME": "Heroin" - }, - { - "SUBSTANCE_NAME": "Cocaine" - } - ] - } - }, - "encoding": { - "fill": { - "field": "SUBSTANCE_NAME", - "title": "Data Variable", - "type": "ordinal", - "legend": { - "orient": "left", - "titlePadding": 10, - "titleColor": "black", - "values": [ - "All Substances", - "Heroin", - "Cocaine", - "Fentanyl", - "Prescription Opioid", - "Any Opioid", - "Methamphetamine", - "Benzodiazepine" - ], - "symbolSize": 0, - "offset": -100 - } - }, - "opacity": { - "condition": { - "selection": "substance_selection", - "value": 1 - }, - "value": 0.75 - } - } - }, - { - "mark": { - "type": "text", - "fontSize": 0 - }, - "selection": { - "filter_selection": { - "type": "multi", - "fields": [ - "HAD_PREVIOUS_OVERDOSE" - ], - "bind": { - "legend": "click" - } - } - }, - "encoding": { - "fill": { - "field": "HAD_PREVIOUS_OVERDOSE", - "title": "Filter", - "type": "ordinal", - "legend": { - "orient": "top-left", - "titlePadding": 10, - "titleColor": "black", - "values": [ - "Had Previous Overdose", - "No Previous Overdose" - ], - "symbolSize": 0, - "offset": -10 - } - }, - "opacity": { - "condition": { - "selection": "filter_selection", - "value": 1 - }, - "value": 0.75 - } - } - }, - { - "spacing": 0, - "vconcat": [ - { - "spacing": 0, - "hconcat": [ - { - "height": 175, - "title": { - "orient": "top", - "text": "# Deaths" - }, - "data": { - "sequence": { - "start": 0, - "stop": 65, - "step": 10, - "as": "deaths_binned" - } - }, - "transform": [ - { - "calculate": "if(datum.deaths_binned < 60, datum.deaths_binned + '-' + (datum.deaths_binned + 9), '60>')", - "as": "label" - } - ], - "mark": "rect", - "width": 50, - "selection": { - "deaths_binned": { - "type": "single", - "fields": [ - "deaths_binned" - ] - }, - "label": { - "type": "single", - "empty": "none", - "fields": [ - "label" - ] - } - }, - "encoding": { - "x": { - "type": "nominal", - "field": "YEAR", - "title": null, - "axis": { - "labels": false, - "ticks": false, - "domain": false, - "titlePadding": 20 - } - }, - "y": { - "field": "deaths_binned", - "type": "ordinal", - "scale": { - "reverse": true - }, - "axis": { - "orient": "right", - "ticks": false, - "domain": false, - "labels": false, - "title": null, - "labelFontSize": 12 - } - }, - "stroke": { - "value": "white" - }, - "strokeWidth": { - "value": 2 - }, - "color": { - "legend": null, - "field": "deaths_binned", - "scale": { - "scheme": "yellowgreenblue" - } - } - } - }, - { - "layer": [ - { - "data": { - "sequence": { - "start": 0, - "stop": 65, - "step": 10, - "as": "deaths_binned" - } - }, - "transform": [ - { - "calculate": "if(datum.deaths_binned < 60, datum.deaths_binned + '-' + (datum.deaths_binned + 9), '60>')", - "as": "label" - } - ], - "height": 175, - "mark": { - "type": "text", - "fontStyle": "bold" - }, - "encoding": { - "color": { - "value": "white", - "condition": { - "selection": "label", - "value": "black" - } - }, - "text": { - "type": "ordinal", - "field": "label" - }, - "y": { - "type": "ordinal", - "field": "deaths_binned", - "axis": null, - "scale": { - "reverse": true - } - } - } - }, - { - "data": { - "sequence": { - "start": 0, - "stop": 65, - "step": 10, - "as": "deaths_binned" - } - }, - "transform": [ - { - "calculate": "if(datum.deaths_binned < 60, datum.deaths_binned + '-' + (datum.deaths_binned + 9), '60>')", - "as": "label" - } - ], - "height": 175, - "mark": { - "type": "text" - }, - "encoding": { - "text": { - "type": "ordinal", - "field": "label" - }, - "y": { - "type": "ordinal", - "field": "deaths_binned", - "axis": null, - "scale": { - "reverse": true - } - } - } - } - ] - } - ] - } - ] - } - ] - }, - { - "facet": { - "field": "SUBSTANCE_NAME", - "title": null, - "header": { - "labelFontSize": 18, - "labelAlign": "left" - } - }, - "columns": 1, - "spec": { - "hconcat": [ - { - "transform": [ - { - "joinaggregate": [ - { - "as": "TOTAL_DEATHS", - "field": "CASE_NUMBER", - "op": "distinct" - } - ], - "groupby": [ - "AGE_GROUP", - "YEAR" - ] - }, - { - "as": "deaths_max69", - "calculate": "min(69, datum.TOTAL_DEATHS)" - }, - { - "calculate": "['#d4eeb4', '#a9ddb7', '#74c9bd', '#45b4c2', '#2997be', '#2173b1', '#234ea0'][datum.deaths_binned/10]", - "as": "color" - }, - { - "bin": { - "anchor": 0, - "maxbins": 7, - "step": 10, - "extent": [ - 0, - 69 - ] - }, - "field": "deaths_max69", - "as": "deaths_binned" - }, - { - "as": "group", - "calculate": "if(toString(10 * datum.AGE_GROUP) < 70, toString(10 * datum.AGE_GROUP) + \"-\" + toString(10 * datum.AGE_GROUP + 9), '70+')" - } - ], - "title": "All", - "width": 300, - "height": 200, - "mark": "rect", - "encoding": { - "color": { - "condition": { - "selection": "deaths_binned", - "field": "deaths_binned", - "type": "nominal", - "scale": { - "range": { - "field": "color" - } - }, - "legend": null - }, - "value": "white" - }, - "tooltip": [ - { - "field": "TOTAL_DEATHS", - "title": "Deaths", - "type": "ordinal" - }, - { - "field": "group", - "title": "Age Group", - "type": "ordinal" - }, - { - "field": "YEAR", - "title": "YEAR", - "type": "ordinal" - } - ], - "x": { - "field": "YEAR", - "title": "Year", - "type": "ordinal", - "axis": { - "labelAngle": 0 - }, - "scale": { - "domain": [ - 2010, - 2011, - 2012, - 2013, - 2014, - 2015, - 2016, - 2017, - 2018 - ] - } - }, - "y": { - "axis": { - "labels": true - }, - "field": "group", - "title": "Age Group", - "type": "ordinal", - "scale": { - "reverse": true, - "domain": [ - "0-9", - "10-19", - "20-29", - "30-39", - "40-49", - "50-59", - "60-69", - "70+" - ] - } - } - } - }, - { - "transform": [ - { - "filter": "datum.SEX == 'M'" - }, - { - "joinaggregate": [ - { - "as": "TOTAL_DEATHS", - "field": "CASE_NUMBER", - "op": "distinct" - } - ], - "groupby": [ - "AGE_GROUP", - "YEAR" - ] - }, - { - "as": "deaths_max69", - "calculate": "min(69, datum.TOTAL_DEATHS)" - }, - { - "calculate": "['#d4eeb4', '#a9ddb7', '#74c9bd', '#45b4c2', '#2997be', '#2173b1', '#234ea0'][datum.deaths_binned/10]", - "as": "color" - }, - { - "bin": { - "anchor": 0, - "maxbins": 7, - "step": 10, - "extent": [ - 0, - 69 - ] - }, - "field": "deaths_max69", - "as": "deaths_binned" - }, - { - "as": "group", - "calculate": "if(toString(10 * datum.AGE_GROUP) < 70, toString(10 * datum.AGE_GROUP) + \"-\" + toString(10 * datum.AGE_GROUP + 9), '70+')" - } - ], - "title": "Male", - "width": 300, - "height": 200, - "mark": "rect", - "encoding": { - "color": { - "condition": { - "selection": "deaths_binned", - "field": "deaths_binned", - "type": "nominal", - "scale": { - "range": { - "field": "color" - } - }, - "legend": null - }, - "value": "white" - }, - "tooltip": [ - { - "field": "TOTAL_DEATHS", - "title": "Deaths", - "type": "ordinal" - }, - { - "field": "group", - "title": "Age Group", - "type": "ordinal" - }, - { - "field": "YEAR", - "title": "YEAR", - "type": "ordinal" - } - ], - "x": { - "field": "YEAR", - "title": "Year", - "type": "ordinal", - "axis": { - "labelAngle": 0 - }, - "scale": { - "domain": [ - 2010, - 2011, - 2012, - 2013, - 2014, - 2015, - 2016, - 2017, - 2018 - ] - } - }, - "y": { - "axis": { - "labels": false - }, - "field": "group", - "title": "", - "type": "ordinal", - "scale": { - "reverse": true, - "domain": [ - "0-9", - "10-19", - "20-29", - "30-39", - "40-49", - "50-59", - "60-69", - "70+" - ] - } - } - } - }, - { - "transform": [ - { - "filter": "datum.SEX == 'F'" - }, - { - "joinaggregate": [ - { - "as": "TOTAL_DEATHS", - "field": "CASE_NUMBER", - "op": "distinct" - } - ], - "groupby": [ - "AGE_GROUP", - "YEAR" - ] - }, - { - "as": "deaths_max69", - "calculate": "min(69, datum.TOTAL_DEATHS)" - }, - { - "calculate": "['#d4eeb4', '#a9ddb7', '#74c9bd', '#45b4c2', '#2997be', '#2173b1', '#234ea0'][datum.deaths_binned/10]", - "as": "color" - }, - { - "bin": { - "anchor": 0, - "maxbins": 7, - "step": 10, - "extent": [ - 0, - 69 - ] - }, - "field": "deaths_max69", - "as": "deaths_binned" - }, - { - "as": "group", - "calculate": "if(toString(10 * datum.AGE_GROUP) < 70, toString(10 * datum.AGE_GROUP) + \"-\" + toString(10 * datum.AGE_GROUP + 9), '70+')" - } - ], - "title": "Female", - "width": 300, - "height": 200, - "mark": "rect", - "encoding": { - "color": { - "condition": { - "selection": "deaths_binned", - "field": "deaths_binned", - "type": "nominal", - "scale": { - "range": { - "field": "color" - } - }, - "legend": null - }, - "value": "white" - }, - "tooltip": [ - { - "field": "TOTAL_DEATHS", - "title": "Deaths", - "type": "ordinal" - }, - { - "field": "group", - "title": "Age Group", - "type": "ordinal" - }, - { - "field": "YEAR", - "title": "YEAR", - "type": "ordinal" - } - ], - "x": { - "field": "YEAR", - "title": "Year", - "type": "ordinal", - "axis": { - "labelAngle": 0 - }, - "scale": { - "domain": [ - 2010, - 2011, - 2012, - 2013, - 2014, - 2015, - 2016, - 2017, - 2018 - ] - } - }, - "y": { - "axis": { - "labels": false - }, - "field": "group", - "title": "", - "type": "ordinal", - "scale": { - "reverse": true, - "domain": [ - "0-9", - "10-19", - "20-29", - "30-39", - "40-49", - "50-59", - "60-69", - "70+" - ] - } - } - } - } - ], - "resolve": { - "scale": { - "y": "shared" - } - } - } - } - ] -} \ No newline at end of file +{ + "$schema": "https://vega.github.io/schema/vega-lite/v4.json", + "data": { + "url": "../data/visualization4/data.csv" + }, + "autosize": { + "type": "pad", + "resize": true + }, + "config": { + "mark": { + "tooltip": null + }, + "view": { + "stroke": "transparent" + }, + "style": { + "guide-label": { + "fontSize": 11 + } + } + }, + "transform": [ + { + "filter": { + "selection": "filter_selection" + } + }, + { + "filter": { + "selection": "substance_selection" + } + }, + { + "joinaggregate": [ + { + "as": "TOTAL_DEATHS", + "field": "CASE_NUMBER", + "op": "distinct" + } + ], + "groupby": ["AGE_GROUP", "YEAR"] + }, + { + "calculate": "datum.OVERDOSE_DUMMY == 1 || datum.OVERDOSE_CC_MOI == 1", + "as": "HAS_OVERDOSE_DATA" + }, + { + "calculate": "datum.HAS_OVERDOSE_DATA == true && datum.MAX_INCIDENT_DATE < datum.DATE_OF_DEATH ? 'Had Previous Overdose' : 'No Previous Overdose'", + "as": "HAD_PREVIOUS_OVERDOSE" + } + ], + "spacing": 150, + "hconcat": [ + { + "spacing": 40, + "vconcat": [ + { + "mark": { + "type": "text", + "fontSize": 0 + }, + "selection": { + "substance_selection": { + "type": "multi", + "fields": ["SUBSTANCE_NAME"], + "bind": { + "legend": "dblclick" + }, + "init": [ + { + "SUBSTANCE_NAME": "All Substances" + }, + { + "SUBSTANCE_NAME": "Heroin" + }, + { + "SUBSTANCE_NAME": "Cocaine" + } + ] + } + }, + "encoding": { + "fill": { + "field": "SUBSTANCE_NAME", + "title": "Data Variable", + "type": "ordinal", + "legend": { + "orient": "left", + "titlePadding": 10, + "titleColor": "black", + "values": [ + "All Substances", + "Heroin", + "Cocaine", + "Fentanyl", + "Prescription Opioid", + "Any Opioid", + "Methamphetamine", + "Benzodiazepine" + ], + "symbolSize": 0, + "offset": -100 + } + }, + "opacity": { + "condition": { + "selection": "substance_selection", + "value": 1 + }, + "value": 0.75 + } + } + }, + { + "mark": { + "type": "text", + "fontSize": 0 + }, + "selection": { + "filter_selection": { + "type": "multi", + "fields": ["HAD_PREVIOUS_OVERDOSE"], + "bind": { + "legend": "click" + } + } + }, + "encoding": { + "fill": { + "field": "HAD_PREVIOUS_OVERDOSE", + "title": "Filter", + "type": "ordinal", + "legend": { + "orient": "top-left", + "titlePadding": 10, + "titleColor": "black", + "values": ["Had Previous Overdose", "No Previous Overdose"], + "symbolSize": 0, + "offset": -10 + } + }, + "opacity": { + "condition": { + "selection": "filter_selection", + "value": 1 + }, + "value": 0.75 + } + } + }, + { + "spacing": 0, + "vconcat": [ + { + "spacing": 0, + "hconcat": [ + { + "height": 175, + "title": { + "orient": "top", + "text": "# Deaths" + }, + "data": { + "sequence": { + "start": 0, + "stop": 65, + "step": 10, + "as": "deaths_binned" + } + }, + "transform": [ + { + "calculate": "if(datum.deaths_binned < 60, datum.deaths_binned + '-' + (datum.deaths_binned + 9), '60>')", + "as": "label" + } + ], + "mark": "rect", + "width": 50, + "selection": { + "deaths_binned": { + "type": "single", + "fields": ["deaths_binned"] + }, + "label": { + "type": "single", + "empty": "none", + "fields": ["label"] + } + }, + "encoding": { + "x": { + "type": "nominal", + "field": "YEAR", + "title": null, + "axis": { + "labels": false, + "ticks": false, + "domain": false, + "titlePadding": 20 + } + }, + "y": { + "field": "deaths_binned", + "type": "ordinal", + "scale": { + "reverse": true + }, + "axis": { + "orient": "right", + "ticks": false, + "domain": false, + "labels": false, + "title": null, + "labelFontSize": 12 + } + }, + "stroke": { + "value": "white" + }, + "strokeWidth": { + "value": 2 + }, + "color": { + "legend": null, + "field": "deaths_binned", + "scale": { + "scheme": "yellowgreenblue" + } + } + } + }, + { + "layer": [ + { + "data": { + "sequence": { + "start": 0, + "stop": 65, + "step": 10, + "as": "deaths_binned" + } + }, + "transform": [ + { + "calculate": "if(datum.deaths_binned < 60, datum.deaths_binned + '-' + (datum.deaths_binned + 9), '60>')", + "as": "label" + } + ], + "height": 175, + "mark": { + "type": "text", + "fontStyle": "bold" + }, + "encoding": { + "color": { + "value": "white", + "condition": { + "selection": "label", + "value": "black" + } + }, + "text": { + "type": "ordinal", + "field": "label" + }, + "y": { + "type": "ordinal", + "field": "deaths_binned", + "axis": null, + "scale": { + "reverse": true + } + } + } + }, + { + "data": { + "sequence": { + "start": 0, + "stop": 65, + "step": 10, + "as": "deaths_binned" + } + }, + "transform": [ + { + "calculate": "if(datum.deaths_binned < 60, datum.deaths_binned + '-' + (datum.deaths_binned + 9), '60>')", + "as": "label" + } + ], + "height": 175, + "mark": { + "type": "text" + }, + "encoding": { + "text": { + "type": "ordinal", + "field": "label" + }, + "y": { + "type": "ordinal", + "field": "deaths_binned", + "axis": null, + "scale": { + "reverse": true + } + } + } + } + ] + } + ] + } + ] + } + ] + }, + { + "facet": { + "field": "SUBSTANCE_NAME", + "title": null, + "header": { + "labelFontSize": 18, + "labelAlign": "left" + } + }, + "columns": 1, + "spec": { + "hconcat": [ + { + "transform": [ + { + "joinaggregate": [ + { + "as": "TOTAL_DEATHS", + "field": "CASE_NUMBER", + "op": "distinct" + } + ], + "groupby": ["AGE_GROUP", "YEAR"] + }, + { + "as": "deaths_max69", + "calculate": "min(69, datum.TOTAL_DEATHS)" + }, + { + "calculate": "['#d4eeb4', '#a9ddb7', '#74c9bd', '#45b4c2', '#2997be', '#2173b1', '#234ea0'][datum.deaths_binned/10]", + "as": "color" + }, + { + "bin": { + "anchor": 0, + "maxbins": 7, + "step": 10, + "extent": [0, 69] + }, + "field": "deaths_max69", + "as": "deaths_binned" + }, + { + "as": "group", + "calculate": "if(toString(10 * datum.AGE_GROUP) < 70, toString(10 * datum.AGE_GROUP) + \"-\" + toString(10 * datum.AGE_GROUP + 9), '70+')" + } + ], + "title": "All", + "width": 300, + "height": 200, + "mark": "rect", + "encoding": { + "color": { + "condition": { + "selection": "deaths_binned", + "field": "deaths_binned", + "type": "nominal", + "scale": { + "range": { + "field": "color" + } + }, + "legend": null + }, + "value": "white" + }, + "tooltip": [ + { + "field": "TOTAL_DEATHS", + "title": "Deaths", + "type": "ordinal" + }, + { + "field": "group", + "title": "Age Group", + "type": "ordinal" + }, + { + "field": "YEAR", + "title": "YEAR", + "type": "ordinal" + } + ], + "x": { + "field": "YEAR", + "title": "Year", + "type": "ordinal", + "axis": { + "labelAngle": 0 + }, + "scale": { + "domain": [ + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + ] + } + }, + "y": { + "axis": { + "labels": true + }, + "field": "group", + "title": "Age Group", + "type": "ordinal", + "scale": { + "reverse": true, + "domain": [ + "0-9", + "10-19", + "20-29", + "30-39", + "40-49", + "50-59", + "60-69", + "70+" + ] + } + } + } + }, + { + "transform": [ + { + "filter": "datum.SEX == 'M'" + }, + { + "joinaggregate": [ + { + "as": "TOTAL_DEATHS", + "field": "CASE_NUMBER", + "op": "distinct" + } + ], + "groupby": ["AGE_GROUP", "YEAR"] + }, + { + "as": "deaths_max69", + "calculate": "min(69, datum.TOTAL_DEATHS)" + }, + { + "calculate": "['#d4eeb4', '#a9ddb7', '#74c9bd', '#45b4c2', '#2997be', '#2173b1', '#234ea0'][datum.deaths_binned/10]", + "as": "color" + }, + { + "bin": { + "anchor": 0, + "maxbins": 7, + "step": 10, + "extent": [0, 69] + }, + "field": "deaths_max69", + "as": "deaths_binned" + }, + { + "as": "group", + "calculate": "if(toString(10 * datum.AGE_GROUP) < 70, toString(10 * datum.AGE_GROUP) + \"-\" + toString(10 * datum.AGE_GROUP + 9), '70+')" + } + ], + "title": "Male", + "width": 300, + "height": 200, + "mark": "rect", + "encoding": { + "color": { + "condition": { + "selection": "deaths_binned", + "field": "deaths_binned", + "type": "nominal", + "scale": { + "range": { + "field": "color" + } + }, + "legend": null + }, + "value": "white" + }, + "tooltip": [ + { + "field": "TOTAL_DEATHS", + "title": "Deaths", + "type": "ordinal" + }, + { + "field": "group", + "title": "Age Group", + "type": "ordinal" + }, + { + "field": "YEAR", + "title": "YEAR", + "type": "ordinal" + } + ], + "x": { + "field": "YEAR", + "title": "Year", + "type": "ordinal", + "axis": { + "labelAngle": 0 + }, + "scale": { + "domain": [ + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + ] + } + }, + "y": { + "axis": { + "labels": false + }, + "field": "group", + "title": "", + "type": "ordinal", + "scale": { + "reverse": true, + "domain": [ + "0-9", + "10-19", + "20-29", + "30-39", + "40-49", + "50-59", + "60-69", + "70+" + ] + } + } + } + }, + { + "transform": [ + { + "filter": "datum.SEX == 'F'" + }, + { + "joinaggregate": [ + { + "as": "TOTAL_DEATHS", + "field": "CASE_NUMBER", + "op": "distinct" + } + ], + "groupby": ["AGE_GROUP", "YEAR"] + }, + { + "as": "deaths_max69", + "calculate": "min(69, datum.TOTAL_DEATHS)" + }, + { + "calculate": "['#d4eeb4', '#a9ddb7', '#74c9bd', '#45b4c2', '#2997be', '#2173b1', '#234ea0'][datum.deaths_binned/10]", + "as": "color" + }, + { + "bin": { + "anchor": 0, + "maxbins": 7, + "step": 10, + "extent": [0, 69] + }, + "field": "deaths_max69", + "as": "deaths_binned" + }, + { + "as": "group", + "calculate": "if(toString(10 * datum.AGE_GROUP) < 70, toString(10 * datum.AGE_GROUP) + \"-\" + toString(10 * datum.AGE_GROUP + 9), '70+')" + } + ], + "title": "Female", + "width": 300, + "height": 200, + "mark": "rect", + "encoding": { + "color": { + "condition": { + "selection": "deaths_binned", + "field": "deaths_binned", + "type": "nominal", + "scale": { + "range": { + "field": "color" + } + }, + "legend": null + }, + "value": "white" + }, + "tooltip": [ + { + "field": "TOTAL_DEATHS", + "title": "Deaths", + "type": "ordinal" + }, + { + "field": "group", + "title": "Age Group", + "type": "ordinal" + }, + { + "field": "YEAR", + "title": "YEAR", + "type": "ordinal" + } + ], + "x": { + "field": "YEAR", + "title": "Year", + "type": "ordinal", + "axis": { + "labelAngle": 0 + }, + "scale": { + "domain": [ + 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + ] + } + }, + "y": { + "axis": { + "labels": false + }, + "field": "group", + "title": "", + "type": "ordinal", + "scale": { + "reverse": true, + "domain": [ + "0-9", + "10-19", + "20-29", + "30-39", + "40-49", + "50-59", + "60-69", + "70+" + ] + } + } + } + } + ], + "resolve": { + "scale": { + "y": "shared" + } + } + } + } + ] +} diff --git a/data-processor/docs/examples/visualization4a.gif b/apps/data-processor/docs/examples/visualization4a.gif similarity index 100% rename from data-processor/docs/examples/visualization4a.gif rename to apps/data-processor/docs/examples/visualization4a.gif diff --git a/data-processor/docs/examples/visualization4a.md b/apps/data-processor/docs/examples/visualization4a.md similarity index 83% rename from data-processor/docs/examples/visualization4a.md rename to apps/data-processor/docs/examples/visualization4a.md index 047a3166..189ad99d 100644 --- a/data-processor/docs/examples/visualization4a.md +++ b/apps/data-processor/docs/examples/visualization4a.md @@ -1,37 +1,37 @@ -# Accidental Drug Overdose Deaths - -#### Marion County by Substance, Sex, & Age (2010-2018) -