Skip to content

Commit

Permalink
Create import graph of types and namespaces (#1261)
Browse files Browse the repository at this point in the history
  • Loading branch information
delvedor authored Jan 17, 2022
1 parent 1c7ea18 commit 2f327d9
Show file tree
Hide file tree
Showing 7 changed files with 1,045 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
output/dangling-types/dangling.csv linguist-generated=true
output/schema/schema.json linguist-generated=true
output/schema/import-type-graph.json linguist-generated=true
output/schema/import-namespace-graph-compact.json linguist-generated=true
output/schema/import-namespace-graph-expanded.json linguist-generated=true
output/schema/validation-errors.json linguist-generated=true
output/typescript/types.ts linguist-generated=true
40 changes: 40 additions & 0 deletions .github/workflows/gh-pages-report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Deploy GH pages report

on:
workflow_dispatch:
push:
branches:
- "main"
paths:
- 'specification/**'

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Use Node.js 16.x
uses: actions/setup-node@v1
with:
node-version: 16.x

- name: Install
run: |
npm install --prefix compiler
- name: Generate output
run: |
npm run generate-schema --prefix compiler
- name: Generate graph
run: |
npm run generate-import-graph --prefix compiler
- name: Deploy 🚀
uses: JamesIves/[email protected]
with:
branch: gh-pages
folder: report
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ artifacts
.validation.json
compiler/lib
typescript-generator/lib

report
output/schema/import-*
149 changes: 149 additions & 0 deletions compiler/generate-import-graph.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the 'License'); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

// code adapted from https://observablehq.com/@d3/hierarchical-edge-bundling

import d3 from 'd3'
import { join } from 'desm'
import { readFileSync, writeFileSync, mkdirSync } from 'fs'

const rawData = JSON.parse(readFileSync(join(import.meta.url, '..', 'output', 'schema', 'import-namespace-graph-compact.json'), 'utf8'))
const data = {
name: 'root',
children: rawData.map(d => {
return {
name: d.namespace,
imports: d.imports,
imported_by: d.imported_by
}
})
}

const html = `
<!DOCTYPE html>
<head>
<script src='https://d3js.org/d3.v7.min.js'></script>
</head>
<body>
<script>
const data = ${JSON.stringify(data)}
;(${generate.toString()})(window)
</script>
</body>
`

function generate (window) {
const { d3, document } = window
const colorin = '#00f'
const colorout = '#f00'
const colornone = '#ccc'
const width = 500
const radius = width / 2

const tree = d3.cluster().size([2 * Math.PI, radius - 100])
const line = d3.lineRadial()
.curve(d3.curveBundle.beta(0.85))
.radius(d => d.y)
.angle(d => d.x)

const root = tree(bilink(d3.hierarchy(data).sort((a, b) => d3.ascending(a.height, b.height) || d3.ascending(a.data.name, b.data.name))))
const body = d3.select(document).select('body')

const svg = body
.append('svg')
.attr('viewBox', [-width / 2, -width / 2, width, width])

const node = svg.append('g')
.attr('font-family', 'sans-serif')
.attr('font-size', 5)
.selectAll('g')
.data(root.leaves())
.join('g')
.attr('transform', d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`)
.append('text')
.attr('dy', '0.2em')
.attr('x', d => d.x < Math.PI ? 6 : -6)
.attr('text-anchor', d => d.x < Math.PI ? 'start' : 'end')
.attr('transform', d => d.x >= Math.PI ? 'rotate(180)' : null)
.text(d => d.data.name)
.each(function(d) { d.text = this; })
.on('mouseover', overed)
.on('mouseout', outed)
.call(text => text.append('title').text(d => `${id(d).slice(5)}
${d.outgoing.length} outgoing
${d.incoming.length} incoming`))

const link = svg.append('g')
.attr('stroke', colornone)
.attr('fill', 'none')
.selectAll('path')
.data(root.leaves().flatMap(leaf => leaf.outgoing))
.join('path')
.style('mix-blend-mode', 'multiply')
.attr('d', ([i, o]) => line(i.path(o)))
.each(function(d) { d.path = this; })


function bilink (root) {
const map = new Map(root.leaves().map(d => [id(d), d]))
for (const d of root.leaves()) {
d.incoming = []
d.outgoing = Array.isArray(d.data.imports)
? d.data.imports.map(i => [d, map.get(`root.${i}`)])
: []
}

for (const d of root.leaves()) {
for (const o of d.outgoing) {
o[1].incoming.push(o)
}
}

return root
}

function id (node) {
return `${node.parent ? id(node.parent) + '.' : ''}${node.data.name}`
}

function overed (event, d) {
link.style('mix-blend-mode', null)
d3.select(this).attr('font-weight', 'bold')
d3.selectAll(d.incoming.map(d => d.path)).attr('stroke', colorin).raise()
d3.selectAll(d.incoming.map(([d]) => d.text)).attr('fill', colorin).attr('font-weight', 'bold')
d3.selectAll(d.outgoing.map(d => d.path)).attr('stroke', colorout).raise()
d3.selectAll(d.outgoing.map(([, d]) => d.text)).attr('fill', colorout).attr('font-weight', 'bold')
}

function outed (event, d) {
link.style('mix-blend-mode', 'multiply')
d3.select(this).attr('font-weight', null)
d3.selectAll(d.incoming.map(d => d.path)).attr('stroke', null)
d3.selectAll(d.incoming.map(([d]) => d.text)).attr('fill', null).attr('font-weight', null)
d3.selectAll(d.outgoing.map(d => d.path)).attr('stroke', null)
d3.selectAll(d.outgoing.map(([, d]) => d.text)).attr('fill', null).attr('font-weight', null)
}
}

mkdirSync(join(import.meta.url, '..', 'report'), { recursive: true })
writeFileSync(
join(import.meta.url, '..', 'report', 'namespace-dependencies.html'),
html.trim(),
'utf8'
)
Loading

0 comments on commit 2f327d9

Please sign in to comment.