diff --git a/lib/galaxy/dependencies/pinned-hashed-requirements.txt b/lib/galaxy/dependencies/pinned-hashed-requirements.txt index d35ad28c5db..86eb640a84c 100644 --- a/lib/galaxy/dependencies/pinned-hashed-requirements.txt +++ b/lib/galaxy/dependencies/pinned-hashed-requirements.txt @@ -135,7 +135,7 @@ pbr==1.8.0 --hash:sha256=355a0c2ba71120dce1c522387e494c60eb4f44fa9d03067cb981618 six==1.9.0 --hash:sha256=cf806ab65d5e16561df30c2cea2fb2cf3e6aa82e48f22036dff177e20f61e188 Parsley==1.3 --hash:sha256=32fd648334146e4005dae41d559d32edfff9f3d5f503f8b5d233dbfe3a15a28b nose==1.3.7 --hash:sha256=f36054c5786e84ff6b131f3249a3cce82787f1a930ab053287c0edaa9f840d0f -SVGFig==1.1.6 --hash:sha256=300cf4b37b4773498d4ea96c703cdb5f09215978b02828dadb786fb248e0b54b +svgwrite==1.1.6 --hash:sha256=e404424dad5eaa41efec6e68daa5c0603e3f4f576ada5dca730669103ab83660 # Fabric and dependencies Fabric==1.10.2 --hash:sha256=8fe1eb0b0f271e2d41445e7da4470548d3bdf882aab1c1f143505c599996793b diff --git a/lib/galaxy/dependencies/pinned-requirements.txt b/lib/galaxy/dependencies/pinned-requirements.txt index 35516aaf539..8d4b0825d6d 100644 --- a/lib/galaxy/dependencies/pinned-requirements.txt +++ b/lib/galaxy/dependencies/pinned-requirements.txt @@ -51,7 +51,7 @@ pbr==1.8.0 six==1.9.0 Parsley==1.3 nose==1.3.7 -SVGFig==1.1.6 +svgwrite==1.1.6 # Fabric and dependencies Fabric==1.10.2 diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index dc11b5b5719..c92a5fbf878 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -514,7 +514,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix def gen_image( self, trans, id ): stored = self.get_stored_workflow( trans, id, check_ownership=True ) trans.response.set_content_type("image/svg+xml") - return self._workflow_to_svg_canvas( trans, stored ).standalone_xml() + return self._workflow_to_svg_canvas( trans, stored ).tostring() @web.expose @web.require_login( "use Galaxy workflows" ) @@ -749,7 +749,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix workflow_name=workflow_dict['name'], workflow_description=workflow_dict['annotation'], workflow_content=workflow_content, - workflow_svg=self._workflow_to_svg_canvas( trans, stored ).standalone_xml() + workflow_svg=self._workflow_to_svg_canvas( trans, stored ).tostring() ) # strip() b/c myExperiment XML parser doesn't allow white space before XML; utf-8 handles unicode characters. request = unicodify( request_raw.strip(), 'utf-8' ) @@ -1290,7 +1290,6 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix def _workflow_to_svg_canvas( self, trans, stored ): workflow = stored.latest_workflow workflow_canvas = WorkflowCanvas() - canvas = workflow_canvas.canvas for step in workflow.steps: # Load from database representation module = module_factory.from_workflow_step( trans, step ) @@ -1303,10 +1302,8 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix module_data_inputs, module_data_outputs, ) - - workflow_canvas.add_steps( ) - workflow_canvas.finish( ) - return canvas + workflow_canvas.add_steps() + return workflow_canvas.finish() def _build_workflow_on_str(instance_ds_names): diff --git a/lib/galaxy/workflow/render.py b/lib/galaxy/workflow/render.py index 52f3db8c406..4b51f3a315f 100644 --- a/lib/galaxy/workflow/render.py +++ b/lib/galaxy/workflow/render.py @@ -1,4 +1,4 @@ -import svgfig +import svgwrite MARGIN = 5 LINE_SPACING = 15 @@ -7,11 +7,11 @@ LINE_SPACING = 15 class WorkflowCanvas( object ): def __init__( self ): - self.canvas = svgfig.canvas( style="stroke:black; fill:none; stroke-width:1px; stroke-linejoin:round; text-anchor:left" ) - self.text = svgfig.SVG( "g" ) - self.connectors = svgfig.SVG( "g" ) - self.boxes = svgfig.SVG( "g" ) - svgfig.Text.defaults[ "font-size" ] = "10px" + self.canvas = svgwrite.Drawing(profile='full') + + self.connectors = [] + self.boxes = [] + self.text = [] self.in_pos = {} self.out_pos = {} self.widths = {} @@ -21,34 +21,35 @@ class WorkflowCanvas( object ): self.data = [] def finish( self ): - max_x, max_y, max_width = self.max_x, self.max_y, self.max_width - canvas = self.canvas - canvas.append( self.connectors ) - canvas.append( self.boxes ) - canvas.append( self.text ) - width, height = ( max_x + max_width + 50 ), max_y + 300 - canvas[ 'width' ] = "%spx" % width - canvas[ 'height' ] = "%spx" % height - canvas[ 'viewBox' ] = "0 0 %s %s" % ( width, height ) + # max_x, max_y, max_width = self.max_x, self.max_y, self.max_width + for box in self.boxes: + self.canvas.add(box) + for connector in self.connectors: + self.canvas.add(connector) + text_style_layer = self.canvas.g(style="font-family: Helvetica, Arial, FreeSans, Sans, sans, sans-serif;") + for text in self.text: + text_style_layer.add(text) + self.canvas.add(text_style_layer) + return self.canvas def add_boxes( self, step_dict, width, name_fill ): x, y = step_dict[ 'position' ][ 'left' ], step_dict[ 'position' ][ 'top' ] - self.boxes.append( svgfig.Rect( x - MARGIN, y, x + width - MARGIN, y + 30, fill=name_fill ).SVG() ) + self.boxes.append(svgwrite.shapes.Rect((x - MARGIN, y), (width, 30), fill=name_fill, stroke='#000000')) box_height = ( len( step_dict[ 'data_inputs' ] ) + len( step_dict[ 'data_outputs' ] ) ) * LINE_SPACING + MARGIN # Draw separator line. if len( step_dict[ 'data_inputs' ] ) > 0: - box_height += 15 + box_height += LINE_SPACING sep_y = y + len( step_dict[ 'data_inputs' ] ) * LINE_SPACING + 40 - self.text.append( svgfig.Line( x - MARGIN, sep_y, x + width - MARGIN, sep_y ).SVG() ) + self.text.append( svgwrite.shapes.Line( (x - MARGIN, sep_y), (x + width - MARGIN, sep_y ), stroke=svgwrite.rgb(0, 0, 0)) ) # Define an input/output box. - self.boxes.append( svgfig.Rect( x - MARGIN, y + 30, x + width - MARGIN, y + 30 + box_height, fill="#ffffff" ).SVG() ) + self.boxes.append( svgwrite.shapes.Rect( (x - MARGIN, y + 30), (width, box_height), fill="#ffffff", stroke=svgwrite.rgb(0, 0, 0) )) def add_text( self, module_data_inputs, module_data_outputs, step, module_name ): left, top = step.position[ 'left' ], step.position[ 'top' ] x, y = left, top order_index = step.order_index max_len = len( module_name ) * 1.5 - self.text.append( svgfig.Text( x, y + 20, module_name, **{ "font-size": "14px" } ).SVG() ) + self.text.append(svgwrite.text.Text(module_name, (x, y + 20), style='font-size:14px')) y += 45 count = 0 in_pos = self.in_pos @@ -58,17 +59,17 @@ class WorkflowCanvas( object ): if order_index not in in_pos: in_pos[ order_index ] = {} in_pos[ order_index ][ di[ 'name' ] ] = ( x, cur_y ) - self.text.append( svgfig.Text( x, cur_y, di[ 'label' ] ).SVG() ) + self.text.append( svgwrite.text.Text( di[ 'label' ], (x, cur_y), style='font-size:10px' ) ) count += 1 max_len = max( max_len, len( di[ 'label' ] ) ) if len( module_data_inputs ) > 0: - y += 15 + y += LINE_SPACING for do in module_data_outputs: cur_y = y + count * LINE_SPACING if order_index not in out_pos: out_pos[ order_index ] = {} out_pos[ order_index ][ do[ 'name' ] ] = ( x, cur_y ) - self.text.append( svgfig.Text( x, cur_y, do[ 'name' ] ).SVG() ) + self.text.append( svgwrite.text.Text( do[ 'name' ], (x, cur_y), style='font-size:10px' )) count += 1 max_len = max( max_len, len( do['name' ] ) ) self.widths[ order_index ] = max_len * 5.5 @@ -95,16 +96,25 @@ class WorkflowCanvas( object ): key = out_conn_index_dict.keys()[0] out_conn_pos = self.out_pos[ out_pos_index ][ key ] adjusted = ( out_conn_pos[ 0 ] + self.widths[ output_dict[ 'id' ] ], out_conn_pos[ 1 ] ) - self.text.append( svgfig.SVG( "circle", - cx=out_conn_pos[ 0 ] + self.widths[ output_dict[ 'id' ] ] - MARGIN, - cy=out_conn_pos[ 1 ] - MARGIN, - r=5, - fill="#ffffff" ) ) - self.connectors.append( svgfig.Line( adjusted[ 0 ], - adjusted[ 1 ] - MARGIN, - in_coords[ 0 ] - 10, - in_coords[ 1 ], - arrow_end="conn_%s" % (len(self.connectors.keys()) + 1) ).SVG() ) + self.text.append( svgwrite.shapes.Circle(center=(out_conn_pos[ 0 ] + self.widths[ output_dict[ 'id' ] ] - MARGIN, + out_conn_pos[ 1 ] - MARGIN), + r=5, + fill="#ffffff", + stroke="#000000" ) ) + marker = self.canvas.marker(overflow='visible', + refX="0", refY="5", + viewBox="0 0 10 5", + markerWidth="8", + markerHeight="10", + markerUnits="strokeWidth", + orient="auto", stroke="none", fill="black") + marker.add(self.canvas.path(d="M 0 0 L 10 5 L 0 10 z")) + self.canvas.defs.add(marker) + conn = svgwrite.shapes.Line( (adjusted[ 0 ], adjusted[ 1 ] - MARGIN), + (in_coords[ 0 ] - 10, in_coords[ 1 ]), + stroke="#000000") + conn['marker-end'] = marker.get_funciri() + self.connectors.append( conn ) def add_steps( self, highlight_errors=False ): # Only highlight missing tools if displaying in the tool shed. @@ -114,7 +124,6 @@ class WorkflowCanvas( object ): fill = "#EBBCB2" else: fill = "#EBD9B2" - width = self.widths[ step_dict[ 'id' ] ] self.add_boxes( step_dict, width, fill ) for conn, output_dict in step_dict[ 'input_connections' ].iteritems(): @@ -129,12 +138,10 @@ class WorkflowCanvas( object ): } if tool_errors: step_dict[ 'tool_errors' ] = tool_errors - input_conn_dict = {} for conn in step.input_connections: input_conn_dict[ conn.input_name ] = \ dict( id=conn.output_step.order_index, output_name=conn.output_name ) step_dict['input_connections'] = input_conn_dict - self.data.append(step_dict) self.add_text( module_data_inputs, module_data_outputs, step, module_name ) diff --git a/lib/tool_shed/util/workflow_util.py b/lib/tool_shed/util/workflow_util.py index 32e69d19465..53bcc95cbdc 100644 --- a/lib/tool_shed/util/workflow_util.py +++ b/lib/tool_shed/util/workflow_util.py @@ -202,7 +202,7 @@ def generate_workflow_image( trans, workflow_name, repository_metadata_id=None, workflow_canvas.add_steps( highlight_errors=True ) workflow_canvas.finish( ) trans.response.set_content_type( "image/svg+xml" ) - return canvas.standalone_xml() + return canvas.tostring() def get_workflow_data_inputs( step, module ): diff --git a/test/unit/workflows/test_render.py b/test/unit/workflows/test_render.py index 27bab7caf2e..b7afcd28cd1 100644 --- a/test/unit/workflows/test_render.py +++ b/test/unit/workflows/test_render.py @@ -66,4 +66,4 @@ def test_render(): ) workflow_canvas.add_steps() workflow_canvas.finish() - assert workflow_canvas.canvas.standalone_xml() + assert workflow_canvas.canvas.tostring()