Skip to content

Commit

Permalink
Adding citelang badge (#6)
Browse files Browse the repository at this point in the history
* adding citelang html "badge"
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
  • Loading branch information
vsoch authored Mar 19, 2022
1 parent 8bfbba0 commit e702f23
Show file tree
Hide file tree
Showing 16 changed files with 617 additions and 59 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ and **Merged pull requests**. Critical items to know are:
The versions coincide with releases on pip. Only major versions will be released as tags on GitHub.

## [0.0.x](https://github.com/vsoch/citelang/tree/main) (0.0.x)
- support for citelang badge to generate interactive html page (0.0.11)
- first release, and do rounding based on credit (0.0.1)
- skeleton release (0.0.0)
22 changes: 14 additions & 8 deletions citelang/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ def get_parser():
description="list package managers available to derive citations from.",
)
deps = subparsers.add_parser("deps", description="list dependencies for a package.")

badge = subparsers.add_parser(
"badge", description="Generate an html (svg-based) badge."
)
graph = subparsers.add_parser(
"graph", description="generate a graph for some package dependencies."
)
Expand All @@ -104,7 +108,7 @@ def get_parser():
"credit", description="calculate dependency credit for a package."
)

for command in [pkg, deps, graph, credit]:
for command in [pkg, deps, graph, credit, badge]:
command.add_argument(
"package", help="package manager and name to parse", nargs=2
)
Expand All @@ -117,7 +121,7 @@ def get_parser():
)
command.add_argument("--outfile", "-o", help="Save to an output json file.")

for command in [graph, credit]:
for command in [graph, credit, badge]:
command.add_argument(
"--max-depth", help="maximum depth to parse tree (default is unset)"
)
Expand Down Expand Up @@ -225,7 +229,9 @@ def help(return_code=0):
helper = subparser
break

if args.command == "cache":
if args.command == "badge":
from .badge import main
elif args.command == "cache":
from .cache import main
elif args.command == "config":
from .config import main
Expand All @@ -244,11 +250,11 @@ def help(return_code=0):

# Pass on to the correct parser
return_code = 0
# try:
main(args=args, parser=parser, extra=extra, subparser=helper)
sys.exit(return_code)
# except UnboundLocalError:
# return_code = 1
try:
main(args=args, parser=parser, extra=extra, subparser=helper)
sys.exit(return_code)
except UnboundLocalError:
return_code = 1

help(return_code)

Expand Down
25 changes: 25 additions & 0 deletions citelang/client/badge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
__author__ = "Vanessa Sochat"
__copyright__ = "Copyright 2022, Vanessa Sochat"
__license__ = "MPL 2.0"

from citelang.main import Client
import tempfile
import os


def main(args, parser, extra, subparser):

cli = Client(quiet=args.quiet, settings_file=args.settings_file)
result = cli.badge(
name=args.package[1],
manager=args.package[0],
use_cache=not args.no_cache,
max_depth=args.max_depth,
max_deps=args.max_depth,
min_credit=args.min_credit,
credit_split=args.credit_split,
)

if not args.outfile:
args.outfile = os.path.join(tempfile.mkdtemp(), "index.html")
result.save(args.outfile)
171 changes: 171 additions & 0 deletions citelang/main/badge/template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CiteLang Credit Badge</title>
<link rel="shortcut icon" href="https://vsoch.github.io/citelang/_static/favicon.ico"/>
<style>:root{--syntax_normal:#1b1e23;--syntax_comment:#a9b0bc;--syntax_number:#20a5ba;--syntax_keyword:#c30771;--syntax_atom:#10a778;--syntax_string:#008ec4;--syntax_error:#ffbedc;--syntax_unknown_variable:#838383;--syntax_known_variable:#005f87;--syntax_matchbracket:#20bbfc;--syntax_key:#6636b4;--mono_fonts:82%/1.5 Menlo,Consolas,monospace}.observablehq--collapsed,.observablehq--expanded,.observablehq--function,.observablehq--gray,.observablehq--import,.observablehq--string:after,.observablehq--string:before{color:var(--syntax_normal)}.observablehq--collapsed,.observablehq--inspect a{cursor:pointer}.observablehq--field{text-indent:-1em;margin-left:1em}.observablehq--empty{color:var(--syntax_comment)}.observablehq--blue,.observablehq--keyword{color:#3182bd}.observablehq--forbidden,.observablehq--pink{color:#e377c2}.observablehq--orange{color:#e6550d}.observablehq--boolean,.observablehq--null,.observablehq--undefined{color:var(--syntax_atom)}.observablehq--bigint,.observablehq--date,.observablehq--green,.observablehq--number,.observablehq--regexp,.observablehq--symbol{color:var(--syntax_number)}.observablehq--index,.observablehq--key{color:var(--syntax_key)}.observablehq--prototype-key{color:#aaa}.observablehq--empty{font-style:oblique}.observablehq--purple,.observablehq--string{color:var(--syntax_string)}.observablehq--error,.observablehq--red{color:#e7040f}.observablehq--inspect{font:var(--mono_fonts);overflow-x:auto;display:block;white-space:pre}.observablehq--error .observablehq--inspect{word-break:break-all;white-space:pre-wrap}.observablehq--inspect {display: none;}#treemap{max-width:800px; margin:auto}
</style>
<body>
<div id="treemap"></div>
<script type="module">
function _chart(d3,width,height,treemap,data,name,DOM,letters)
{
const x = d3.scaleLinear().rangeRound([0, width]);
const y = d3.scaleLinear().rangeRound([0, height]);

const svg = d3.create("svg")
.attr("viewBox", [0.5, -50.5, width, height + 50])
.style("font", "14px sans-serif");

let group = svg.append("g")
.call(render, treemap(data));

function get_letter_graphic(letter) {
var defined = ["a", "b", "c", "d", "e", "f", "g", "h", "i"];
if (defined.includes(letter.toLowerCase())){
return letters + "/" + letter + ".png";
}
return ""
}
function render(group, root) {
const node = group
.selectAll("g")
.data(root.children.concat(root))
.join("g");

node.filter(d => d === root ? d.parent : d.children)
.attr("cursor", "pointer")
.on("click", (event, d) => d === root ? zoomout(root) : zoomin(d));

node.append("title")
.text(d => `${name(d)}\n${d.data.credit_rounded}`);

node.append("rect")
.attr("id", d => (d.leafUid = DOM.uid("leaf")).id)
.attr("fill", d => d === root ? "#fff" : d.data.color)
.attr("stroke", "#fff");

node.append("clipPath")
.attr("id", d => (d.clipUid = DOM.uid("clip")).id)
.append("use")
.attr("xlink:href", d => d.leafUid.href);

node.append("text")
.attr("clip-path", d => d.clipUid)
.attr("font-weight", d => d === root ? "bold" : null)
.selectAll("tspan")
.data(d => (d === root ? name(d) : d.data.name).split(/(?=[A-Z][^A-Z])/g).concat(d.data.credit_rounded))
.join("tspan")
.attr("x", 3)
.attr("y", (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`)
.attr("fill-opacity", (d, i, nodes) => i === nodes.length - 1 ? 0.7 : null)
.attr("font-weight", (d, i, nodes) => i === nodes.length - 1 ? "normal" : null)
.text(d => d);

//node.append("image")
// .attr("width", d => d === root ? width : x(d.x1) - x(d.x0))
// .attr("height", d => d === root ? 50 : y(d.y1) - y(d.y0))
// .attr("xlink:href", d => d === root ? "" : get_letter_graphic(d.data.name[0]));

group.call(position, root);
}

function position(group, root) {
group.selectAll("g")
.attr("transform", d => d === root ? `translate(0,-50)` : `translate(${x(d.x0)},${y(d.y0)})`)
.select("rect")
.attr("width", d => d === root ? width : x(d.x1) - x(d.x0))
.attr("height", d => d === root ? 50 : y(d.y1) - y(d.y0));
}

// When zooming in, draw the new nodes on top, and fade them in.
function zoomin(d) {
const group0 = group.attr("pointer-events", "none");
const group1 = group = svg.append("g").call(render, d);

x.domain([d.x0, d.x1]);
y.domain([d.y0, d.y1]);

svg.transition()
.duration(750)
.call(t => group0.transition(t).remove()
.call(position, d.parent))
.call(t => group1.transition(t)
.attrTween("opacity", () => d3.interpolate(0, 1))
.call(position, d));
}

// When zooming out, draw the old nodes on top, and fade them out.
function zoomout(d) {
const group0 = group.attr("pointer-events", "none");
const group1 = group = svg.insert("g", "*").call(render, d.parent);

x.domain([d.parent.x0, d.parent.x1]);
y.domain([d.parent.y0, d.parent.y1]);

svg.transition()
.duration(750)
.call(t => group0.transition(t).remove()
.attrTween("opacity", () => d3.interpolate(1, 0))
.call(position, d))
.call(t => group1.transition(t)
.call(position, d.parent));
}

return svg.node();
}


function _data(){
return {{data}}
}

function _treemap(d3,tile){return(
data => d3.treemap()
.tile(tile)
(d3.hierarchy(data)
.sum(d => d.size)
.sort((a, b) => b.size - a.size))
)}

function _tile(d3,width,height){return(
function tile(node, x0, y0, x1, y1) {
d3.treemapBinary(node, 0, 0, width, height);
for (const child of node.children) {
child.x0 = x0 + child.x0 / width * (x1 - x0);
child.x1 = x0 + child.x1 / width * (x1 - x0);
child.y0 = y0 + child.y0 / height * (y1 - y0);
child.y1 = y0 + child.y1 / height * (y1 - y0);
}
}
)}

function _name(){return(d => d.ancestors().reverse().map(d => d.data.name).join("/"))}

function _width(){return(954)}

function _height(){return(624)}

function _letters(){
return "https://raw.githubusercontent.com/researchapps/citelang-letters/main/black"
}

function _d3(require){return(require("d3@6"))}

export default function define(runtime, observer) {
const main = runtime.module();
main.variable(observer("chart")).define("chart", ["d3","width","height","treemap","data","name","DOM","letters"], _chart);
main.variable(observer("data")).define("data", _data);
main.variable(observer("treemap")).define("treemap", ["d3","tile"], _treemap);
main.variable(observer("tile")).define("tile", ["d3","width","height"], _tile);
main.variable(observer("name")).define("name", _name);
main.variable(observer("width")).define("width", _width);
main.variable(observer("height")).define("height", _height);
main.variable(observer("d3")).define("d3", ["require"], _d3);
main.variable(observer("letters")).define("letters", _letters);
return main;
}

import {Runtime, Library, Inspector} from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js";
const runtime = new Runtime();
const main = runtime.module(define, Inspector.into("#treemap"));
</script>
33 changes: 32 additions & 1 deletion citelang/main/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ def graph(
)
return results.Graph(root).graph(fmt=fmt)

def badge(
self,
manager,
name,
use_cache=True,
max_depth=None,
max_deps=None,
min_credit=0.01,
credit_split=0.5,
fmt=None,
):
"""
Generate a badge for a package
"""
root = self._graph(
manager=manager,
name=name,
use_cache=use_cache,
max_depth=max_depth,
max_deps=max_deps,
min_credit=min_credit,
credit_split=credit_split,
)
return results.Badge(root)

def _graph(
self,
manager,
Expand All @@ -101,8 +126,9 @@ def _graph(
self.check_manager(manager, use_cache)
pkg = package.get_package(manager, name, client=self, use_cache=use_cache)

# keep track of deps (we only care about name, not version)
# keep track of deps (we only care about name, not version) and names
seen = set()
node_names = set()

# Top node gets full credit 1.0 (to split between itself and deps)
root = graph.Node(
Expand All @@ -111,6 +137,7 @@ def _graph(
credit_split=credit_split,
depth=0,
min_credit=min_credit,
is_root=True,
)

# A pointer to the next node
Expand All @@ -125,6 +152,7 @@ def _graph(
while next_nodes and not stop_looking:
next_node = next_nodes.pop(0)
deps = next_node.obj.dependencies(return_data=True)
[node_names.add(d["name"]) for d in deps if "name" in d]

# Stopping point - exceeded max depth
if max_depth and next_node.depth > max_depth:
Expand All @@ -135,6 +163,7 @@ def _graph(
stop_looking = True

seen.add(next_node.name)
node_names.add(next_node.name)

if not deps:
continue
Expand Down Expand Up @@ -198,6 +227,8 @@ def _graph(
# Store previous if we need to update weight
previous.append(child)

# Add total node count to root
root.children_names = list(node_names)
return root

def credit(
Expand Down
Loading

0 comments on commit e702f23

Please sign in to comment.