def create_sankey_graph_from_resources(resources): """ Given Sankey data process it into Sankey graph data :param resources: Resource instances :return: """ unindexed_graph = R.reduce(accumulate_sankey_graph, dict(nodes=[], links=[]), resources) return index_sankey_graph(unindexed_graph)
def enforce_unique_props(property_fields, django_instance_data): """ Called in the mutate function of the Graphene Type class. Ensures that all properties marked as unique_with :param property_fields: The Graphene Type property fields dict. This is checked for unique_with, which when present points at a function that expects the django_instance_data and returns the django_instance_data modified so that the property in question has a unique value. :param django_instance_data: dict of an instance to be created or updated :param for_update if True this is for an update mutation so props are not required :return: The modified django_instance_data for any property that needs to have a unique value """ # If any prop needs to be unique then run its unique_with function, which updates it to a unique value # By querying the database for duplicate. This is mainly for non-pk fields like a key return R.reduce( lambda reduced, prop_field_tup: prop_field_tup[1]['unique_with'](reduced) if R.has(prop_field_tup[0], reduced) and R.prop_or(False, 'unique_with', prop_field_tup[1]) else reduced, django_instance_data, property_fields.items() )
def parse_literal(cls, node): """ Parses any array string :param node: :return: """ def map_value(value): """ value is either a ListValue with values that are ListValues or a ListValue with values that are FloatValues :param value: :return: """ def handle_floats(v): if hasattr(v, 'values'): # Multiple floats:w return R.map(lambda fv: float(fv.value), v.values) else: # Single float return float(v.value) return R.if_else( lambda v: R.isinstance( ListValue, R.head(v.values) if hasattr(v, 'values') else v), # ListValues lambda v: [reduce(v.values)], # FloatValues or single FloatValue lambda v: [handle_floats(v)])(value) def reduce(values): return R.reduce( lambda accum, list_values: R.concat(accum, map_value(list_values)), [], values) # Create the coordinates by reducing node.values=[node.values=[node.floats], node.value, ...] return R.reduce(lambda accum, list_values: reduce(node.values), [], reduce(node.values))
def generate_sankey_data(resource): """ Generates nodes and links for the given Resrouce object :param resource: Resource object :return: A dict containing nodes and links. nodes are a dict key by stage name Results can be assigned to resource.data.sankey and saved """ settings = R.item_path(['data', 'settings'], resource) stages = R.prop('stages', settings) stage_key = R.prop('stage_key', settings) value_key = R.prop('value_key', settings) location_key = R.prop('location_key', settings) node_name_key = R.prop('node_name_key', settings) default_location = R.prop('default_location', settings) # A dct of stages by name stage_by_name = stages_by_name(stages) def accumulate_nodes(accum, raw_node, i): """ Accumulate each node, keying by the name of the node's stage key Since nodes share stage keys these each result is an array of nodes :param accum: :param raw_node: :param i: :return: """ location_obj = resolve_location(default_location, R.prop(location_key, raw_node), i) location = R.prop('location', location_obj) is_generalized = R.prop('is_generalized', location_obj) # The key where then node is stored is the stage key key = R.prop('key', stage_by_name[raw_node[stage_key]]) # Copy all properties from resource.data except settings and raw_data # Also grab raw_node properties # This is for arbitrary properties defined in the data # We put them in properties and property_values since graphql hates arbitrary key/values properties = R.merge( R.omit(['settings', 'raw_data'], R.prop('data', resource)), raw_node) return R.merge( # Omit accum[key] since we'll concat it with the new node R.omit([key], accum), { # concat accum[key] or [] with the new node key: R.concat( R.prop_or([], key, accum), # Note that the value is an array so we can combine nodes with the same stage key [ dict(value=string_to_float(R.prop(value_key, raw_node)), type='Feature', geometry=dict(type='Point', coordinates=location), name=R.prop(node_name_key, raw_node), is_generalized=is_generalized, properties=list(R.keys(properties)), property_values=list(R.values(properties))) ]) }) raw_nodes = create_raw_nodes(resource) # Reduce the nodes nodes_by_stage = R.reduce( lambda accum, i_and_node: accumulate_nodes(accum, i_and_node[1], i_and_node[0]), {}, enumerate(raw_nodes)) nodes = R.flatten(R.values(nodes_by_stage)) return dict(nodes=nodes, nodes_by_stage=nodes_by_stage, links=create_links(stages, value_key, nodes_by_stage))
def reduce_or(q_expressions): return R.reduce(lambda qs, q: qs | q if qs else q, None, q_expressions)
def generate_sankey_data(resource): """ Generates nodes and links for the given Resouce object :param resource: Resource object :return: A dict containing nodes and links. nodes are a dict key by stage name Results can be assigned to resource.data.sankey and saved """ settings = R.item_path(['data', 'settings'], resource) stages = R.prop('stages', settings) stage_key = R.prop('stageKey', settings) value_key = R.prop('valueKey', settings) location_key = R.prop('locationKey', settings) node_name_key = R.prop('nodeNameKey', settings) node_color_key = R.prop_or(None, 'nodeColorKey', settings) default_location = R.prop('defaultLocation', settings) delineator = R.prop_or(';', 'delineator', settings) # A dct of stages by name stage_by_name = stages_by_name(stages) link_start_node_key = R.prop_or(None, 'linkStartNodeKey', settings) link_end_node_key = R.prop_or(None, 'linkEndNodeKey', settings) link_value_key = R.prop_or(None, 'linkValueKey', settings) link_color_key = R.prop_or(None, 'linkColorKey', settings) def accumulate_nodes(accum, raw_node, i): """ Accumulate each node, keying by the name of the node's stage key Since nodes share stage keys these each result is an array of nodes :param accum: :param raw_node: :param i: :return: """ location_obj = resolve_coordinates(default_location, R.prop_or(None, location_key, raw_node), i) location = R.prop('location', location_obj) is_generalized = R.prop('isGeneralized', location_obj) # The key where then node is stored is the stage key node_stage = raw_node[stage_key] # Get key from name or it's already a key key = R.prop('key', R.prop_or(dict(key=node_stage), node_stage, stage_by_name)) # Copy all properties from resource.data except settings and raw_data # Also grab raw_node properties # This is for arbitrary properties defined in the data # We put them in properties and propertyValues since graphql hates arbitrary key/values properties = R.merge( R.omit(['settings', 'rawData'], R.prop('data', resource)), raw_node ) properties[node_name_key] = humanize(properties[node_name_key]) return R.merge( # Omit accum[key] since we'll concat it with the new node R.omit([key], accum), { # concat accum[key] or [] with the new node key: R.concat( R.prop_or([], key, accum), # Note that the value is an array so we can combine nodes with the same stage key [ dict( value=string_to_float(R.prop(value_key, raw_node)), type='Feature', geometry=dict( type='Point', coordinates=location ), name=R.prop(node_name_key, raw_node), isGeneralized=is_generalized, properties=list(R.keys(properties)), propertyValues=list(R.values(properties)) ) ] ) } ) raw_nodes = create_raw_nodes(delineator, resource) # Reduce the nodes nodes_by_stage = R.reduce( lambda accum, i_and_node: accumulate_nodes(accum, i_and_node[1], i_and_node[0]), {}, enumerate(raw_nodes) ) nodes = R.flatten(R.values(nodes_by_stage)) # See if there are explicit links if R.item_path_or(False, ['data', 'settings', 'link_start_node_key'], resource): raw_links = create_raw_links(delineator, resource) node_key_key = R.prop('nodeNameKey', settings) nodes_by_key = R.from_pairs(R.map( lambda node: [prop_lookup(node, node_key_key), node], nodes )) links = R.map( lambda link: dict( source_node=nodes_by_key[link[link_start_node_key]], target_node=nodes_by_key[link[link_end_node_key]], value=link[link_value_key], color=R.prop_or(None, link_color_key, link) ), raw_links ) else: # Guess links from nodes and stages links = create_links(stages, value_key, nodes_by_stage) return dict( nodes=nodes, nodes_by_stage=nodes_by_stage, # We might have explicit links or have to generate all possible based on the nodes links=links )
def reduce(values): return R.reduce( lambda accum, list_values: R.concat(accum, map_value(list_values)), [], values)