Quintiq file version 2.0
#parent: CustomDrawComponentSupplyChainVisualization/CustomDrawDataLayerNode
Response OnDrawObject (
  shadow[SCVNode] object
) id:Response_CustomDrawComponentSupplyChainVisualization_CustomDrawDataLayerNode_OnDrawObject
{
  #keys: '[132894.0.851837207]'
  Body:
  [*
    // Draw SCVNode
    
    // Drawing parameters, node label, node title, label width, title width
    scvconfig := DataHolderSCVConfiguration.Data();
    nodesize := scvconfig.NodeSize();
    
    nodetitle := ''
    nodelabel := ''
    iconimage := '';
    textformat := TextFormat::Construct( TextFormat::AlignBottom(), TextFormat::AlignCenter() ); 
    
    // Specify labels, iconimage based on draw type
    if( object.istype( shadow[SCVPISPIPNode] ) )
    {
      pisp := object.astype( shadow[SCVPISPIPNode] ).ProductInStockingPointInPeriod().ProductInStockingPoint_MP();
      nodetitle := pisp.Product_MP().Name();
      nodelabel := pisp.StockingPoint_MP().Name()
      iconimage := ifexpr( pisp.Product_MP().IconName() <> '', pisp.Product_MP().IconName(), 'PRODUCT2' );
    
    }           
    else if( object.istype( shadow[SCVRoutingStepNode] ) )
    {
      routingStep := select( object.astype( shadow[SCVRoutingStepNode] ), OperationInNode.Operation.RoutingStep, rs, true );
      nodelabel := routingStep.Name();
      iconimage := ifexpr( routingStep.Operation( relsize ) > 1, 'SIZES', 'SHAPE_SQUARE_BLUE' );
    }
    else if( object.istype( shadow[SCVLaneNode] ) )
    {
      lane := object.astype( shadow[SCVLaneNode] ).Lane();
      nodelabel := lane.Name();
      iconimage := 'DELIVERY_AMBER';
    }
    else if( object.istype( shadow[SCVISNode] ) )
    {
      nodelabel := object.Name();
      iconimage := 'HAND_TRUCK_BOX';
    }
    else if( object.istype( shadow[SCVSDNode] ) )
    {
      node := object.astype( shadow[SCVSDNode] );
      nodelabel := object.Name();
      iconimage := ifexpr( node.ProductInStockingPointInPeriod().GetAreAllAssociatedSDIPsFulfilled(), 'DOCUMENT', 'DOCUMENT_DIRTY' );
    }
    
    // Text width, derived from horizontal offset
    textWidth := floor( scvconfig.OffsetX() / 2 );
    textOffsetX := floor( ( nodesize - textWidth ) / 2 );
    textHeight := floor( scvconfig.OffsetY() / 2 );
    
    // Draw node
    if( scvconfig.IsUsingIconName() or not object.istype( shadow[SCVPISPIPNode] ) )
    {
      surface.Image( region.Left(), region.Top(), nodesize, nodesize, iconimage )
    }
    else
    {
      node := object.astype( shadow[SCVPISPIPNode] );
      penWidth := 2;
      
      if( node.ProductInStockingPointInPeriod().istype( ProductInStockingPointInPeriodPlanning )
          and not node.ProductInStockingPointInPeriod().astype( ProductInStockingPointInPeriodPlanning ).IsValidPlannedBalance() ) // violation, no color with red border
      {
        // Draw planning violation indicator
        surface.Pen().Color( ColorScheme.SCVNodeViolation() );    
        
        textformat := TextFormat::Construct( TextFormat::AlignCentered() ); 
        surface.TextColor( ColorScheme.SCVNodeViolation() );                                                                     
        surface.TextZoom( region.Left(), region.Top(), nodesize, nodesize, '!', textformat );  
      }
      else if( object.IsRoot() ) // root color, with border
      {
        surface.Pen().Color( Color::Black() )
        surface.Brush().Color( ColorScheme.SCVRootNode() ) 
      } 
      else // normal node, node color, no border
      {
        penWidth := 0;
        surface.Brush().Color( ColorScheme.SCVNode() );  
      }
      
      surface.Pen().Width( penWidth );
      circle := Shape::Circle( region.Left(), region.Top(), nodesize );
      surface.Fill( circle );
      surface.Draw( circle );
    }
    
    // Update pen parameters
    surface.Font().Name( scvconfig.FontName() )
                  .Size(scvconfig.FontSize() );
    surface.TextColor( Color::Black() );
    
    // Draw text
    if( object.IsRoot() )
    {
      surface.Font().Bold( true );
     
      // Draw period below the root node
      periodStart := scvconfig.Period_MP().StartDate();
      periodString := periodStart.Format( 'D-MM-YOnMax(-)OnMin(-)' );
      
      nodelabel := nodelabel + String::NewLine() + periodString;
    }
    
    // For node title, we define a region above the top of the node
    // then align it bottom, so that the text sticks to the top of the node
    // Draw node title
    surface.TextZoom( region.Left() + textOffsetX, // X
                      region.Top() - textHeight,   // Y
                      textWidth,                   // Width
                      textHeight,                  // Height
                      nodetitle,                   // Text
                      textformat );                // Text format
    
    // For text label, we make the draw region starts at the bottom of the node then align it top, so that
    // it sticks to the bottom of the node                      
    textformat := TextFormat::Construct( TextFormat::AlignTop(), TextFormat::AlignCenter() ); 
    // Draw node label
    surface.TextZoom( region.Left() + textOffsetX,
                      region.Bottom(),             
                      textWidth,                   
                      textHeight,                  
                      nodelabel,
                      textformat );
    
    // Draw indication for stream limit hit
    isUpstreamLimitHit := object.IsUpstream() and object.IsDepthLimitMet();
    isDownstreamLimitHit := not object.IsUpstream() and object.IsDepthLimitMet();
    
    // Special handling for root
    if( object.IsRoot() )
    {
      node := object.astype( shadow[SCVPISPIPNode] );
      isUpstreamLimitHit := scvconfig.UpstreamDepthLimit() = 0 
                            and ( ( FormSupplyChainVisualization.GetInputOutputs( node.ProductInStockingPointInPeriod(), true /*isupstream*/ ).Size() > 0 )
                                    or node.ProductInStockingPointInPeriod().InventorySupplyQuantity() > 0 );
      isDownstreamLimitHit := scvconfig.DownstreamDepthLimit() = 0 
                              and ( ( FormSupplyChainVisualization.GetInputOutputs( node.ProductInStockingPointInPeriod(), false /*isupstream*/ ).Size() > 0 )
                                      or node.ProductInStockingPointInPeriod().SalesDemandInPeriodBase( relsize ) > 0 );                                                                        
    }
    
    textformat := TextFormat::Construct( TextFormat::AlignCentered() );
    
    if( isUpstreamLimitHit )
    {
      surface.Font().Bold( true );
      surface.TextColor( ColorScheme.SCVEdge() );
            
      surface.TextZoom( region.Left() + 2 * -nodesize,
                        region.Top(),
                        nodesize,
                        nodesize,
                        '...',
                        textformat );
    }
    
    if( isDownstreamLimitHit )
    {
      surface.Font().Bold( true );
      surface.TextColor( ColorScheme.SCVEdge() );
      
      surface.TextZoom( region.Left() + 2 * nodesize,
                        region.Top(),
                        nodesize,
                        nodesize,
                        '...',
                        textformat );
    }
  *]
  CanBindMultiple: false
  DefinitionID: 'Responsedef_CustomDrawDataLayer_OnDrawObject'
}
