Quintiq file version 2.0
#parent: PanelSCV/CustomDrawDataLayerSCVNode_549
Response OnDrawObject (
  shadow[SCVNode] object
) id:Response_PanelSCV_CustomDrawDataLayerSCVNode_OnDrawObject
{
  #keys: '[134266.2.4979938]'
  CanBindMultiple: false
  DefinitionID: 'Responsedef_WebCustomDrawDataLayer_OnDrawObject'
  QuillAction
  {
    Body:
    [*
      // Draw SCVNode
      
      // Drawing parameters, node label, node title, label width, title width
      scvconfig := MacroPlan.SCVConfiguration();
      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.BoundingRect().Left(), region.BoundingRect().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.SupplyChainVisualization_NodeViolation() );    
          
          textformat_violation := TextFormat::Construct( TextFormat::AlignCentered() ); 
          surface.TextColor( ColorScheme.SupplyChainVisualization_NodeViolation() );                                                                     
          surface.TextZoom( region.BoundingRect().Left(), region.BoundingRect().Top(), nodesize, nodesize, '!', textformat_violation );
        }
        else if( object.IsRoot() ) // root color, with border
        {
          surface.Pen().Color( Color::Black() )
          surface.Brush().Color( ColorScheme.SupplyChainVisualization_RootNode() ) 
        } 
        else // normal node, node color, no border
        {
          penWidth := 0;
          surface.Brush().Color( ColorScheme.SupplyChainVisualization_Node() );  
        }
        
        surface.Pen().Width( penWidth );
        circle := Shape::Circle( region.BoundingRect().Left(), region.BoundingRect().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 := object.astype( shadow[SCVPISPIPNode] ).ProductInStockingPointInPeriod().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.BoundingRect().Left() + textOffsetX, // X
                        region.BoundingRect().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(), TextFormat::WrapWords() ); 
      // Draw node label
      surface.TextZoom( region.BoundingRect().Left() + textOffsetX,
                        region.BoundingRect().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.SupplyChainVisualization_Edge() );
              
        surface.TextZoom( region.BoundingRect().Left() + 2 * -nodesize,
                          region.BoundingRect().Top(),
                          nodesize,
                          nodesize,
                          '...',
                          textformat );
      }
      
      if( isDownstreamLimitHit )
      {
        surface.Font().Bold( true );
        surface.TextColor( ColorScheme.SupplyChainVisualization_Edge() );
        
        surface.TextZoom( region.BoundingRect().Left() + 2 * nodesize,
                          region.BoundingRect().Top(),
                          nodesize,
                          nodesize,
                          '...',
                          textformat );
      }
    *]
    GroupServerCalls: false
  }
}
