def make_map_with_donuts(counts, outprefix, europe=False, debug=False): donut_coords = { 'Australia': (369, 193), 'Belgium': (195, 123), 'Canada': (88, 91), 'China': (330, 123), 'Germany': (255, 120), 'Italy': (285, 207), 'Netherlands': (210, 105), 'Pakistan': (288, 132), 'Peru': (115, 180), 'Russia': (314, 91), 'Serbia': (372, 194), 'Sierra Leone': (190, 150), 'South Africa': (233, 201), 'Spain': (110, 230), 'Swaziland': (258, 210), 'Thailand': (328, 143), 'UK': (131, 96), 'Uzbekistan': (282, 114), 'Europe': (210, 110), } no_donuts_svg = f'{outprefix}.tmp.svg' final_svg = f'{outprefix}.svg' final_pdf = final_svg.replace('.svg', '.pdf') donut_files = make_donuts(counts, outprefix) make_map_no_donuts(no_donuts_svg, europe=europe) donut_size = 40 if europe else 32 with open(no_donuts_svg) as f: svg_lines = [x.rstrip() for x in f] assert svg_lines[-1] == '</svg>' last_svg_line = svg_lines.pop() # line to point to Swaziland if not europe: svg_lines.append( '<line x1="280" y1="230" x2="262" y2="212" style="stroke:rgb(0,0,0);stroke-width:1" />' ) for country in donut_files: x, y = donut_coords[country] filename = os.path.abspath(donut_files[country]) svg_lines.append( f'<image x="{x}" y="{y}" width="{donut_size}" height="{donut_size}" xlink:href="file:{filename}"></image>' ) svg_lines.append(last_svg_line) with open(final_svg, 'w') as f: print(*svg_lines, sep='\n', file=f) svg.svg2pdf(final_svg, final_pdf) if not debug: os.unlink(no_donuts_svg) os.unlink(final_svg) for filename in donut_files.values(): os.unlink(filename)
def make_legend(outprefix, debug=False): svg_file = f'{outprefix}.svg' pdf_file = f'{outprefix}.pdf' s = r''' <svg height="70pt" width="70pt"> <text x="10" y="11">Dataset</text> <circle cx="11" cy="30" r="10" stroke="black" stroke-width="0.5" fill="''' + mykrobe_colour + r'''" /> <text x="23" y="35">Training</text> <circle cx="11" cy="55" r="10" stroke="black" stroke-width="0.5" fill="''' + validate_colour + r'''" /> <text x="23" y="60">Validation</text> <circle cx="11" cy="80" r="10" stroke="black" stroke-width="0.5" fill="''' + test_colour + r'''" /> <text x="23" y="85">Test</text> </svg>''' with open(svg_file, 'w') as f: print(textwrap.dedent(s), file=f) svg.svg2pdf(svg_file, pdf_file) if not debug: os.unlink(svg_file)
def make_legend(tools, outfile, header=None): font_size = 14 square_len = 20 y_space = 3 svg_lines = [] y = 10 square_left = 5 square_right = square_left + square_len total_width = 200 if header is not None: svg_lines.append( svg.svg_text(square_right, y, header, font_size + 1, position='start', vertical_align='middle')) y += 15 for tool in tools: svg_lines.append( svg.svg_rectangle(square_left, y, square_right, y + square_len, common_data.tool_colours[tool], 'black')) svg_lines.append( svg.svg_text(square_right + 5, y + 0.5 * square_len, common_data.tool_names[tool], font_size, position='start', vertical_align='middle')) y += square_len + y_space f = open(outfile, 'w') print(r'''<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC " -//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg width="''' + str(total_width) + '" height="' + str(y) + '">', file=f) print(*svg_lines, sep='\n', file=f) print('</svg>', file=f) f.close() svg.svg2pdf(outfile, outfile.replace('.svg', '.pdf'))
def plot_one_tool(data, outfile, ignore=None, y_scale=0.8): assert outfile.endswith('.svg') to_ignore = {} if ignore is not None: data = copy.deepcopy(data) for k1, k2 in ignore: if k1 in data and k2 in data[k1]: if k1 not in to_ignore: to_ignore[k1] = {} to_ignore[k1][k2] = data[k1][k2] del data[k1][k2] if len(data[k1]) == 1: del data[k1] for k in data: try: del data[k][99] except: pass colours = [ '#f0e8e3', '#e2d3c9', '#f5cfb6', '#FDD49E', '#FDBB84', '#FCA964', '#FC8D59', '#E34A33', '#d81529', '#CE1256', '#980043', '#400013' ] truth_nodes = {} called_nodes = {} edges = [] for truth in data: if truth not in truth_nodes: truth_nodes[truth] = {} for called in data[truth]: if called not in called_nodes: called_nodes[called] = {} truth_nodes[truth][called] = data[truth][called] called_nodes[called][truth] = data[truth][called] edges.append((truth, called, data[truth][called])) plot_width = 1050 drugs = OrderedDict([ ('H', 'Isoniazid'), ('R', 'Rifampicin'), ('Z', 'Pyrazinamide'), ('E', 'Ethambutol'), ('Rfb', 'Rifabutin'), ('Rpt', 'Rifapentine'), ('Lfx', 'Levofloxacin'), ('Mfx', 'Moxifloxacin'), ('Gfx', 'Gatifloxacin'), ('S', 'Streptomycin'), ('Km', 'Kanamycin'), ('Am', 'Amikacin'), ('Cm', 'Capreomycin'), ('PAS', 'Para-aminosalicylate/Para-aminosalicylate-sodium'), ('Eto', 'Ethionamide'), ('Pto', 'Prothionamide'), ('Cs', 'Cycloserine'), ('Trd', 'Terizidone'), ('Cfz', 'Clofazimide'), ('Lzd', 'Linezolid'), ('hH', 'High dose Isoniazid'), ('hZ', 'High dose Pyrazinamide'), ('hE', 'High dose Ethambutol'), ('X', 'Amox-Clavulanate, Imipenem/Cilastatin, Meropenem, High dose Isoniazid (if possible)'), ]) regimen_to_drug = { 1: {'req': ('H', 'R', 'Z', 'E')}, 2: {'req': ('R', 'Z', 'E'), 'opt': ((1, 'Lfx', 'Mfx', 'Gfx'),)}, 3: {'req': ('R', 'Z', 'E')}, 4: {'req': ('R', 'E', (1, 'Lfx', 'Mfx', 'Gfx'))}, 5: {'req': ('R', 'Z', (1, 'Lfx', 'Mfx', 'Gfx'))}, 6: {'req': ('R', 'E', (1, 'Lfx', 'Mfx', 'Gfx'), (1, 'Km', 'Am', 'Cm'), (1, 'Eto', 'Pto', 'PAS', 'Cs', 'Trd'))}, 7: {'req': ('R', 'E', 'S', (1, 'Eto', 'Pto', 'PAS', 'Cs', 'Trd'))}, 8: {'req': ('H', 'R', 'E')}, 9: {'req': ('H', 'R', 'Z')}, 10: {'req': ('Z', (1, 'Lfx', 'Mfx', 'Gfx'), (1, 'Km', 'Am', 'Cm'), (2, 'Eto', 'Pto', 'Cs', 'Trd', 'Cfz', 'Lzd')), 'opt': ('hH', 'hE')}, 11: {'req': ((1, 'Gfx', 'Mfx'), 'Km', 'Pto', 'Cfz', 'hH', 'hE', 'hZ', )}, 12: {'req': ('Rfb', 'Rpt', 'E', 'Z', (1, 'Lfx', 'Mfx', 'Gfx'), (1, 'Km', 'Am', 'Cm', 'S'), 'Eto', 'Pto', 'PAS', 'Cs', 'Trd', 'hH', 'X')}, } regimen_to_pheno = { 1: {'H': 'S', 'R': 'S', 'Z': 'S', 'E': 'S'}, 2: {'H': 'R', 'R': 'S', 'Z': 'S', 'E': 'S'}, 3: {'H': 'R', 'R': 'S', 'Z': 'S', 'E': 'S', 'Mfx': 'R'}, 4: {'H': 'R', 'R': 'S', 'Z': 'R', 'E': 'S'}, 5: {'H': 'R', 'R': 'S', 'Z': 'S', 'E': 'R'}, 6: {'H': 'R', 'R': 'S', 'Z': 'R', 'E': 'R'}, 7: {'H': 'R', 'R': 'S', 'Z': 'R', 'E': 'R', 'Km': 'R', 'Am': 'R', 'Cm': 'R', 'S': 'S'}, 8: {'H': 'S', 'R': 'S', 'Z': 'R', 'E': 'S'}, 9: {'H': 'S', 'R': 'S', 'Z': 'S', 'E': 'R'}, 10: {'H': '(R)', 'R': 'R'}, 11: {'H': '(R)', 'R': 'R', 'Km': 'S', 'Mfx': 'S'}, 12: {'H': 'R', 'R': 'R', 'Mfx': 'R'}, } drug_col_width = 26 svg_lines = [] x = 10 headings_y = 20 drug_to_x_centre = {} for drug in drugs: drug_to_x_centre[drug] = x + 0.5 * drug_col_width svg_lines.append(svg.svg_text(x + 0.5 * drug_col_width, headings_y, drug, 12, vertical_align='middle')) x += drug_col_width x += 80 right_half_x_left = x + 10 node_to_edge_space = 50 left_node_x = right_half_x_left + node_to_edge_space node_width = 20 right_node_x = plot_width - node_to_edge_space - node_width y_start = 40 node_y_gap = 30 svg_node_lines = [] truth_nodes_y_tops = {} truth_nodes_y_bottoms = {} called_nodes_y_tops = {} truth_node_to_y_centre = {} # Nodes on the left y = y_start svg_lines.append(svg.svg_text(left_node_x - 110, headings_y, 'Regimen', 11, position='middle', font_family='arial', vertical_align='middle')) svg_lines.append(svg.svg_text(left_node_x - 5, headings_y, 'Samples', 11, position='end', font_family='arial', vertical_align='middle')) for node in regimen_to_drug: if node-1 not in truth_nodes: truth_nodes[node-1] = {} if node-1 not in called_nodes: called_nodes[node-1] = {} for node, node_counts in sorted(truth_nodes.items()): truth_nodes_y_tops[node] = y if len(node_counts) > 0: total_samples = sum(node_counts.values()) else: total_samples = 0 node_y_bottom = y + y_scale * total_samples truth_node_to_y_centre[node] = 0.5 * (y + node_y_bottom) svg_node_lines.append(svg.svg_rectangle(left_node_x, y, left_node_x + node_width, node_y_bottom, colours[int(node)], colours[int(node)], border_width=1)) #svg_lines.append(svg.svg_text(left_node_x - 110, 0.5 * (y + node_y_bottom), # who_treatment.regimens[node+1].definition, 11, position='start', font_family='arial', vertical_align='middle')) svg_lines.append(svg.svg_text(left_node_x - 110, 0.5 * (y + node_y_bottom), str(node+1), 11, position='middle', font_family='arial', vertical_align='middle')) svg_lines.append(svg.svg_text(left_node_x - 5, 0.5 * (y + node_y_bottom), str(total_samples), 11, position='end', font_family='arial', vertical_align='middle')) if node in to_ignore: total_ignored = sum(to_ignore[node].values()) svg_lines.append(svg.svg_text(left_node_x - 5, 0.5 * (y + node_y_bottom) + 12, f'(+{total_ignored})', 11, position='end', font_family='arial', vertical_align='middle')) y = node_y_bottom + node_y_gap truth_nodes_y_bottoms[node] = node_y_bottom y = y_start # Nodes on the right for node, node_counts in sorted(called_nodes.items()): called_nodes_y_tops[node] = y total_samples = sum(node_counts.values()) node_y_bottom = y + y_scale * total_samples svg_node_lines.append(svg.svg_rectangle(right_node_x, y, right_node_x + node_width, node_y_bottom, colours[int(node)], colours[int(node)], border_width=1)) svg_lines.append(svg.svg_text(right_node_x + node_width + 3, 0.5 * (y + node_y_bottom), str(total_samples), 11, position='start', font_family='arial', vertical_align='middle')) if node in to_ignore: total_ignored = sum(to_ignore[node].values()) svg_lines.append(svg.svg_text(right_node_x + node_width + 3, 0.5 * (y + node_y_bottom) + 12, f'(+{total_ignored})', 11, position='start', font_family='arial', vertical_align='middle')) y = node_y_bottom + node_y_gap # Ribbons sample_total_counts = {'truth': {}, 'called': {}} for truth_node, called_node, samples in edges: if truth_node not in sample_total_counts['truth']: sample_total_counts['truth'][truth_node] = 0 if called_node not in sample_total_counts['called']: sample_total_counts['called'][called_node] = 0 y11 = truth_nodes_y_tops[truth_node] + y_scale * sample_total_counts['truth'][truth_node] sample_total_counts['truth'][truth_node] += samples y12 = truth_nodes_y_tops[truth_node] + y_scale * sample_total_counts['truth'][truth_node] y21 = called_nodes_y_tops[called_node] + y_scale * sample_total_counts['called'][called_node] sample_total_counts['called'][called_node] += samples y22 = called_nodes_y_tops[called_node] + y_scale * sample_total_counts['called'][called_node] svg_lines.append(svg.svg_ribbon(left_node_x + node_width, y11, y12, right_node_x, y21, y22, colours[int(truth_node)], colours[int(truth_node)], border_width=0.5, opacity=0.8)) # Regimen circles and R/S svg_regimen_lines = [] circle_radius = 0.1 * drug_col_width y_circle_R_S_offset = 7 pheno_letters_lines = [] for node, regime in regimen_to_drug.items(): y_centre = truth_node_to_y_centre[node-1] y_circle_centre = y_centre + y_circle_R_S_offset y_R_or_S = y_centre - y_circle_R_S_offset for req_or_opt in regime: if req_or_opt not in regime: continue if req_or_opt == 'req': line_colour = 'black' else: line_colour = 'lightgrey' for drugs_tuple in regime[req_or_opt]: if type(drugs_tuple) is tuple: x_min = float('Inf') x_max = 0 to_append = [] number_of_lines = drugs_tuple[0] if number_of_lines == 1: lines_y = [y_circle_centre] line_width = 1.5 else: assert number_of_lines == 2 lines_y = [y_circle_centre - circle_radius / 2, y_circle_centre + circle_radius / 2] line_width = 1 for drug in drugs_tuple[1:]: to_append.append(svg.svg_circle(drug_to_x_centre[drug], y_circle_centre, 0.2 * drug_col_width, colours[node-1], line_colour, stroke_width=1)) x_min = min(x_min, drug_to_x_centre[drug]) x_max = max(x_max, drug_to_x_centre[drug]) for line_y in lines_y: svg_regimen_lines.append(svg.svg_line(x_min, line_y, x_max, line_y, line_colour, line_width)) svg_regimen_lines.append(to_append) else: svg_regimen_lines.append(svg.svg_circle(drug_to_x_centre[drugs_tuple], y_circle_centre, 0.2 * drug_col_width, colours[node-1], line_colour, stroke_width=1)) for drug, pheno in regimen_to_pheno[node].items(): pheno_letters_lines.append(svg.svg_text(drug_to_x_centre[drug], y_R_or_S, pheno, 10, vertical_align='middle')) # Make vertical lines to separate drugs y_top = min(truth_nodes_y_tops.values()) y_top = 5 y_bottom = max(truth_nodes_y_bottoms.values()) y_bottom = y - 5 for drug in drugs: x_pos = drug_to_x_centre[drug] - 0.5 * drug_col_width svg_lines.append(svg.svg_line(x_pos, y_top, x_pos, y_bottom, 'lightgrey', 1)) if drug == 'X': x_pos = drug_to_x_centre[drug] + 0.5 * drug_col_width svg_lines.append(svg.svg_line(x_pos, y_top, x_pos, y_bottom, 'lightgrey', 1)) # horizontal lines to separate regimens x_left = min(drug_to_x_centre.values()) - 0.5 * drug_col_width x_right = left_node_x y_top_line = truth_nodes_y_tops[0] - 10 svg_lines.append(svg.svg_line(x_left, y_top, x_right, y_top, 'lightgrey', 1)) svg_lines.append(svg.svg_line(x_left, y_top_line, x_right, y_top_line, 'lightgrey', 1)) svg_lines.append(svg.svg_line(x_left, y_bottom, x_right, y_bottom, 'lightgrey', 1)) for i in range(0, len(truth_nodes_y_bottoms) - 1, 1): y_pos = 0.5 * (truth_nodes_y_bottoms[i] + truth_nodes_y_tops[i+1]) svg_lines.append(svg.svg_line(x_left, y_pos, x_right, y_pos, 'lightgrey', 1)) f = open(outfile, 'w') print(r'''<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC " -//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg width="''' + str(plot_width) + '" height="' + str(y) + '">', file=f) # plot nodes last so that borders are on top of ribbons and therefore still visible print(*svg_lines, svg_node_lines, svg_regimen_lines, pheno_letters_lines, sep='\n', file=f) print('</svg>', file=f) f.close() svg.svg2pdf(outfile, outfile.replace('.svg', '.pdf'))
def make_plot(stats_dict, tools, drugs, outfile, first_line=False, ten_k=False, how_to_scale='not at all', susc_gap=None, susc_xticks=None, res_xticks=None, plot_gap_size=30, extra_width=0): if susc_gap is not None: susc_gap_width = susc_gap[1] - susc_gap[0] stats_dict = copy.deepcopy(stats_dict) for drug in drugs: for tool in tools: if stats_dict[drug][tool]['TN'] > susc_gap[1]: stats_dict[drug][tool][ 'TN'] -= susc_gap_width - plot_gap_size max_count_res_keys = ['FN', 'TP', 'UNK_R', 'FAIL_R'] max_count_susc_keys = ['FP', 'TN', 'UNK_S', 'FAIL_S'] sample_count = {} for drug in drugs: res_counts = set() susc_counts = set() for tool in tools: if tool == '10k_predict': continue res_count = sum( [stats_dict[drug][tool][x] for x in max_count_res_keys]) susc_count = sum( [stats_dict[drug][tool][x] for x in max_count_susc_keys]) res_counts.add(res_count) susc_counts.add(susc_count) assert len(res_counts) == len(susc_counts) == 1 sample_count[drug] = { 'res': res_counts.pop(), 'susc': susc_counts.pop() } max_count_res = max([sample_count[x]['res'] for x in sample_count]) max_count_susc = max([sample_count[x]['susc'] for x in sample_count]) centre_space = 20 x_margin = 10 if how_to_scale == 'not at all': total_width = 600 total_bar_width = total_width - 2 * (centre_space + x_margin) susc_bar_width = total_bar_width * max_count_susc / (max_count_res + max_count_susc) res_bar_width = total_bar_width * max_count_res / (max_count_res + max_count_susc) x_centre = x_margin + susc_bar_width + centre_space elif how_to_scale == 'res susc independent': total_width = 1000 total_bar_width = total_width - 2 * (centre_space + x_margin) susc_bar_width = 0.5 * total_bar_width res_bar_width = 0.5 * total_bar_width x_centre = total_width / 2 elif how_to_scale == 'all to 100': total_width = 500 total_bar_width = total_width - 2 * (centre_space + x_margin) susc_bar_width = 0.5 * total_bar_width - 40 res_bar_width = 0.5 * total_bar_width - 40 x_centre = total_width / 2 bar_height = 13 drug_spacer = 10 x_susc_bar_left = x_centre - centre_space - susc_bar_width x_res_bar_right = x_centre + centre_space + res_bar_width y = 20 svg_lines = [] svg_lines.append( svg.svg_text(0.5 * (x_susc_bar_left + x_centre - centre_space), y, 'Susceptible samples', 20)) svg_lines.append( svg.svg_text(0.5 * (x_centre + centre_space + x_res_bar_right), y, 'Resistant samples', 20)) y = 45 susc_tick_zeros = len(str(max_count_susc)) - 1 susc_tick_gap = int('1' + '0' * susc_tick_zeros) susc_ticks = [ -x for x in range(round(-max_count_susc, -susc_tick_zeros), 1, susc_tick_gap) if x > -max_count_susc ] res_tick_zeros = len(str(max_count_res)) - 1 res_tick_gap = int('1' + '0' * res_tick_zeros) res_ticks = list( range(0, round(max_count_res, -res_tick_zeros) + 1, res_tick_gap)) if how_to_scale == 'all to 100': susc_ticks = [ 0, 0.25 * max_count_susc, 0.5 * max_count_susc, 0.75 * max_count_susc, max_count_susc ] res_ticks = [ 0, 0.25 * max_count_res, 0.5 * max_count_res, 0.75 * max_count_res, max_count_res ] susc_ticks_labels = ['0', '25', '50', '75', '100'] res_ticks_labels = ['0', '25', '50', '75', '100'] else: if susc_xticks is not None and susc_gap is not None: susc_ticks_labels = [str(x) for x in susc_xticks] susc_ticks = [] for i in susc_xticks: if i < susc_gap[0]: susc_ticks.append(i) else: susc_ticks.append(i - (susc_gap_width - plot_gap_size)) else: susc_ticks_labels = [str(x) for x in susc_ticks] if res_xticks is not None: res_ticks = res_xticks res_ticks_labels = [str(x) for x in res_ticks] svg_lines.append( svg.svg_line(x_susc_bar_left, y, x_centre - centre_space, y, 'black', 1)) svg_lines.append( svg.svg_line(x_centre + centre_space, y, x_res_bar_right, y, 'black', 1)) y_tick = y y += 5 for drug in drugs: y_drug_top = y for tool in tools: if drug not in common_data.first_line_drugs and tool == '10k_predict': continue stats = stats_dict[drug][tool] y_bottom = y + bar_height if how_to_scale == 'all to 100': if sample_count[drug]['res'] == 0: x_fail = x_fn = x_right = x_centre + centre_space else: x_fail = x_centre + centre_space + res_bar_width * ( stats['UNK_R'] + stats['FAIL_R']) / sample_count[drug]['res'] x_fn = x_fail + res_bar_width * (stats['FN'] / sample_count[drug]['res']) x_right = x_centre + centre_space + res_bar_width else: x_fail = x_centre + centre_space + res_bar_width * ( (stats['UNK_R'] + stats['FAIL_R']) / max_count_res) x_fn = x_fail + res_bar_width * (stats['FN'] / max_count_res) x_right = x_centre + centre_space + res_bar_width * ( sample_count[drug]['res'] / max_count_res) svg_lines.append( svg.svg_rectangle(x_centre + centre_space, y, x_fail, y_bottom, common_data.other_colours['UNK_R'], 'black')) svg_lines.append( svg.svg_rectangle(x_fail, y, x_fn, y_bottom, common_data.other_colours['FN'], 'black')) svg_lines.append( svg.svg_rectangle(x_fn, y, x_right, y_bottom, common_data.tool_colours[tool], 'black')) if how_to_scale == 'all to 100': if sample_count[drug]['susc'] == 0: x_fail = x_fn = x_right = x_centre - centre_space else: x_fail = x_centre - centre_space - susc_bar_width * ( (stats['UNK_S'] + stats['FAIL_S']) / sample_count[drug]['susc']) x_fp = x_fail - susc_bar_width * stats[ 'FP'] / sample_count[drug]['susc'] x_left = x_centre - centre_space - susc_bar_width else: x_fail = x_centre - centre_space - susc_bar_width * ( (stats['UNK_S'] + stats['FAIL_S']) / max_count_susc) x_fp = x_fail - susc_bar_width * (stats['FP'] / max_count_susc) x_left = x_centre - centre_space - susc_bar_width * ( sample_count[drug]['susc'] / max_count_susc) svg_lines.append( svg.svg_rectangle(x_fail, y, x_centre - centre_space, y_bottom, common_data.other_colours['UNK_S'], 'black')) svg_lines.append( svg.svg_rectangle(x_fp, y, x_fail, y_bottom, common_data.other_colours['FN'], 'black')) svg_lines.append( svg.svg_rectangle(x_left, y, x_fp, y_bottom, common_data.tool_colours[tool], 'black')) y += bar_height svg_lines.append( svg.svg_text(x_centre, 0.5 * (y_drug_top + y), common_data.drug_abbreviations[drug], 15, vertical_align='middle')) if how_to_scale == 'all to 100': svg_lines.append( svg.svg_text(x_susc_bar_left - 3, 0.5 * (y_drug_top + y), str(sample_count[drug]['susc']), 15, position='end', vertical_align='middle')) svg_lines.append( svg.svg_text(x_res_bar_right + 3, 0.5 * (y_drug_top + y), str(sample_count[drug]['res']), 15, position='start', vertical_align='middle')) y += drug_spacer grid_lines = [] for x, label in zip(res_ticks, res_ticks_labels): x_pos = x_centre + centre_space + res_bar_width * x / max_count_res svg_lines.append( svg.svg_line(x_pos, y_tick - 4, x_pos, y_tick, 'black', 1)) svg_lines.append(svg.svg_text(x_pos, y_tick - 7, label, 15)) grid_lines.append( svg.svg_line(x_pos, y_tick, x_pos, y - drug_spacer, 'lightgray', 1)) for x, label in zip(susc_ticks, susc_ticks_labels): x_pos = x_centre - centre_space - susc_bar_width * x / max_count_susc svg_lines.append( svg.svg_line(x_pos, y_tick - 4, x_pos, y_tick, 'black', 1)) svg_lines.append(svg.svg_text(x_pos, y_tick - 7, label, 15)) grid_lines.append( svg.svg_line(x_pos, y_tick, x_pos, y - drug_spacer, 'lightgray', 1)) if susc_gap is not None: svg_lines.append(r'''<defs> <linearGradient id="gradwhiteleft" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="50%" style="stop-color:rgb(255,255,255);stop-opacity:-1" /> <stop offset="100%" style="stop-color:rgb(255,255,255);stop-opacity:1" /> </linearGradient> </defs>''') svg_lines.append(r'''<defs> <linearGradient id="gradwhiteright" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="50%" style="stop-color:rgb(255,255,255);stop-opacity:1" /> <stop offset="100%" style="stop-color:rgb(255,255,255);stop-opacity:-1" /> </linearGradient> </defs>''') x_left = x_centre - centre_space - susc_bar_width * ( (susc_gap[0] + plot_gap_size) / max_count_susc) x_right = x_centre - centre_space - susc_bar_width * (susc_gap[0] / max_count_susc) x_middle = 0.5 * (x_left + x_right) svg_lines.append( svg.svg_rectangle(x_left, y_tick + 1, x_middle, y + 1, 'url(#gradwhiteleft)', 'white', border_width=0)) svg_lines.append( svg.svg_rectangle(x_middle, y_tick + 1, x_right, y + 1, 'url(#gradwhiteright)', 'white', border_width=0)) svg_lines.append( svg.svg_line(x_left + 3, y_tick, x_right, y_tick, 'white', 1)) svg_lines.append( svg.svg_line(x_right - 3, y_tick + 3, x_right + 3, y_tick - 3, 'black', 1)) svg_lines.append( svg.svg_line(x_left, y_tick + 3, x_left + 6, y_tick - 3, 'black', 1)) with open(outfile, 'w') as f: print(r'''<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC " -//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg width="''' + str(total_width + extra_width) + '" height="' + str(y) + '">', file=f) print(*grid_lines, sep='\n', file=f) print(*svg_lines, sep='\n', file=f) print('</svg>', file=f) svg.svg2pdf(outfile, outfile.replace('.svg', '.pdf'))