def test_invalid_backend(self): png_rend = GraphRenderer(backend=PngBackend()) json_rend = GraphRenderer(backend=JsonBackend()) with pytest.raises(TypeError): GifAnimator(renderer=json_rend) with pytest.raises(TypeError): JsonAnimator(renderer=png_rend)
def test_png_is_default(self, dataset, tmpdir): out = tmpdir.join("mygraph.png") rend = GraphRenderer(backend=None) assert isinstance(rend.backend, PngBackend) rend.render(dataset, out) with open(str(out), "rb") as f: assert is_valid_png(f)
def test_entity_positioning(self, dataset): cs = ExampleColourScheme() rend = GraphRenderer(width=100, node_radius=10, spacing=14, colours=cs) ents = list(rend.compile(dataset)) assert len(ents) == ( 1 # background + 4 * 2 # sources + 6 # edges from sources to claims + 4 * 2 # claims + 4 # edges from claims to variables + 3 * 2 # variables + 2 * (6 + 4) # arrow heads ) colours = ExampleColourScheme.colours bgs = [e for e in ents if e.colour == colours["background"]] assert len(bgs) == 1 assert isinstance(bgs[0], Rectangle) assert bgs[0].x == 0 assert bgs[0].y == 0 assert bgs[0].width == 100 # The max number of verical nodes is 4, so height should be # 2*radius*4 + spacing*3 assert bgs[0].height == 2 * 10 * 4 + 14 * 3 # Should be 4 sources, 4 claims, and 3 variables source_ents = [e for e in ents if e.colour == colours[NodeType.SOURCE]] claim_ents = [e for e in ents if e.colour == colours[NodeType.CLAIM]] var_ents = [e for e in ents if e.colour == colours[NodeType.VARIABLE]] for e in source_ents: print(e.x, e.y, e.colour) assert len(source_ents) == 4 assert len(claim_ents) == 4 assert len(var_ents) == 3 # Should be 4 + 4 + 3 border circles border_ents = [e for e in ents if e.colour == colours["border"]] assert len(border_ents) == 4 + 4 + 3 # All nodes should be Circle objects for e in source_ents + claim_ents + var_ents + border_ents: assert isinstance(e, Circle) # There should be 6 edges from sources to claims, and 4 from claims to # variables. For each edge there should be two lines for the arrow # heads edge_ents = [e for e in ents if e.colour == colours["edge"]] assert len(edge_ents) == (6 + 4) * 3 for e in edge_ents: assert isinstance(e, Line) # Sources should be aligned in x coordinates source_coords = [(ent.x, ent.y) for ent in source_ents] assert len(set(x for x, y in source_coords)) == 1 # Check y positioning y_coords = sorted(y for x, y in source_coords) assert y_coords == [10, 44, 78, 112]
def test_progress_bar(self, dataset): w = 200 rend = GraphRenderer(width=w, backend=JsonBackend()) anim = JsonAnimator(renderer=rend) buf = StringIO() it = FixedIterator(20) alg = Sums(iterator=it) anim.animate(buf, alg, dataset, show_progress=True) buf.seek(0) obj = json.load(buf) # Get the frame for the 5th iteration, which is 1 / 4 through frame = obj["frames"][5] rects = [ e for e in frame["entities"] if e["type"] == "rectangle" and e["width"] != w ] assert len(rects) == 1 assert rects[0]["x"] == 0 assert rects[0]["width"] == w / 4 # Test without progress buf2 = StringIO() anim.animate(buf2, alg, dataset, show_progress=False) buf2.seek(0) obj2 = json.load(buf2) frame2 = obj2["frames"][5] rects2 = [ e for e in frame2["entities"] if e["type"] == "rectangle" and e["width"] != w ] assert not len(rects2)
def test_renderer(self): # Custom renderer should be used if provided custom_renderer = GraphRenderer(font_size=10000) anim = GifAnimator(renderer=custom_renderer) assert anim.renderer == custom_renderer # Otherwise a default renderer anim2 = GifAnimator() assert isinstance(anim2.renderer, GraphRenderer)
def test_single_source(self): one_source_dataset = Dataset( (("source 1", "x", 1), ("source 1", "y", 1))) one_var_dataset = Dataset((("source 1", "x", 1), ("source 2", "x", 2))) one_claim_dataset = Dataset( (("source 1", "x", 1), ("source 2", "x", 1))) rend = GraphRenderer(node_radius=66, spacing=13, colours=ExampleColourScheme()) # Check the positioning of nodes for the one-source dataset ents = list(rend.compile(one_source_dataset)) colours = ExampleColourScheme.colours sources = [e for e in ents if e.colour == colours[NodeType.SOURCE]] assert len(sources) == 1 assert sources[0].y == (2 * 66 * 2 + 13) / 2 # Check the other two don't raise any exceptions list(rend.compile(one_var_dataset)) list(rend.compile(one_claim_dataset))
def test_progress_bar(self, dataset): w = 200 anim_colour = (0.34, 0.99, 0.34) class MyColourScheme(GraphColourScheme): def get_animation_progress_colour(self): return anim_colour rend = GraphRenderer(width=w, colours=MyColourScheme()) ents = list(rend.compile(dataset, animation_progress=0.25)) rects = [e for e in ents if e.colour == anim_colour] assert len(rects) == 1 assert rects[0].x == 0 assert rects[0].width == 50 # Test without progress bar ents2 = list(rend.compile(dataset, animation_progress=None)) rects2 = [e for e in ents2 if e.colour == anim_colour] assert not len(rects2)
def test_long_labels(self): dataset = Dataset( (("a-source-with-an-extremely-long-name", "x", 1000000000000000000), ("source 2", "quite a complicated variable name", 100))) ents = list(GraphRenderer().compile(dataset)) assert len(ents) == ( 1 + # background 6 * 2 + # 6 nodes, each represented by two circles 4 * 3 # 4 edges and arrowheads )
def test_image_size(self, dataset): buf = BytesIO() w = 142 nr = 13 sp = 15 h = 2 * nr * 4 + sp * 3 print(dataset.num_sources) print(dataset.num_claims) print(dataset.num_variables) rend = GraphRenderer(width=w, node_radius=nr, spacing=sp, backend=PngBackend()) rend.render(dataset, buf) buf.seek(0) img_data = imageio.imread(buf) got_w, got_h, _ = img_data.shape assert (got_h, got_w) == (w, h)
def test_json_backend(self): w = 123 h = 2 * 10 * 2 + 5 rend = GraphRenderer(width=w, node_radius=10, spacing=5, backend=JsonBackend()) buf = StringIO() data = Dataset((("s1", "x", 0), ("s2", "y", 1))) rend.render(data, buf) buf.seek(0) obj = json.load(buf) assert isinstance(obj, dict) assert "width" in obj assert "height" in obj assert "entities" in obj assert obj["width"] == w assert obj["height"] == h ents = obj["entities"] assert isinstance(ents, list) # Background assert len([e for e in ents if e["type"] == "rectangle"]) == 1 # Nodes and borders assert len([e for e in ents if e["type"] == "circle"]) == 6 * 2 # Edges and arrow heads assert len([e for e in ents if e["type"] == "line"]) == 3 * 4 assert ents[0] == { "type": "rectangle", "x": 0, "y": 0, "colour": list(rend.colours.get_background_colour()), "width": w, "height": h }
def test_gif_animation(self, dataset): w, h = 123, 95 renderer = GraphRenderer(width=w, node_radius=10, spacing=5) animator = GifAnimator(renderer=renderer) alg = Sums() buf = BytesIO() animator.animate(buf, alg, dataset) buf.seek(0) assert is_valid_gif(buf) # Check dimensions are as expected buf.seek(0) img_data = imageio.imread(buf) got_w, got_h, _ = img_data.shape assert (got_h, got_w) == (w, h)
def test_json_animation(self, dataset): w, h = 123, 95 renderer = GraphRenderer(width=w, node_radius=10, spacing=5, backend=JsonBackend()) animator = JsonAnimator(renderer=renderer, frame_duration=1 / 9) alg = Sums(iterator=FixedIterator(4)) buf = StringIO() animator.animate(buf, alg, dataset) buf.seek(0) obj = json.load(buf) assert "fps" in obj assert obj["fps"] == 9 assert "frames" in obj assert isinstance(obj["frames"], list) assert len(obj["frames"]) == 5 assert isinstance(obj["frames"][0], dict) assert "width" in obj["frames"][0] assert "height" in obj["frames"][0] assert "entities" in obj["frames"][0] assert obj["frames"][0]["width"] == w assert obj["frames"][0]["height"] == h
def test_results_based_valid_png(self, dataset, tmpdir): cs = ResultsGradientColourScheme(Sums().run(dataset)) out = tmpdir.join("mygraph.png") GraphRenderer(backend=PngBackend(), colours=cs).render(dataset, out) with open(str(out), "rb") as f: assert is_valid_png(f)
def test_valid_png(self, dataset, tmpdir): out = tmpdir.join("mygraph.png") GraphRenderer(backend=PngBackend()).render(dataset, out) with open(str(out), "rb") as f: assert is_valid_png(f)
def test_base_backend(self, dataset, tmpdir): out = tmpdir.join("mygraph.png") with pytest.raises(NotImplementedError): GraphRenderer(backend=BaseBackend()).render(dataset, out)
plain = False if len(sys.argv) == 3 and sys.argv[1] == "--plain": outpath = sys.argv[2] plain = True elif len(sys.argv) == 2: outpath = sys.argv[1] else: print("usage: {} [--plain] DEST".format(sys.argv[0]), file=sys.stderr) sys.exit(1) tuples = [ ("source 1", "x", 4), ("source 1", "y", 7), ("source 2", "y", 7), ("source 2", "z", 5), ("source 3", "x", 3), ("source 3", "z", 5), ("source 4", "x", 3), ("source 4", "y", 6), ("source 4", "z", 8), ("my really long source name", "mylongvar", "extremelylongvalue"), ] mydata = Dataset(tuples) results = Sums().run(mydata) colour_scheme = (PlainColourScheme() if plain else ResultsGradientColourScheme(results)) renderer = GraphRenderer(width=1000, colours=colour_scheme) with open(outpath, "wb") as imgfile: renderer.render(mydata, imgfile)