304 lines
14 KiB
CoffeeScript
304 lines
14 KiB
CoffeeScript
#import Component from 'ember-c3/components/c3-chart'
|
|
import Ember from 'ember'
|
|
import groupBy from 'ember-group-by'
|
|
#<text text-anchor='end' transform="rotate(-90)" dx='-80' dy='0' style="opacity: 1;" x='-90' y='662'>asdf</text>
|
|
import WorkflowActionDefinitionOffsetNodes from 'ember-panda/utils/project/workflow-action-definition-offset-nodes'
|
|
{get} = Ember
|
|
|
|
GraphWrapper = Ember.Object.extend
|
|
records: []
|
|
absolute_value: 0
|
|
title: Ember.computed 'records.length', 'records.firstObject.title', ->
|
|
length = @get('records.length')
|
|
if length > 1
|
|
optional_text = @get('records').mapBy('title').join(' & ')
|
|
if optional_text.length <= 49
|
|
optional_text
|
|
else
|
|
actions_name = t('models.plural.workflow_action_definition')
|
|
"#{length} #{actions_name}"
|
|
else
|
|
@get('records.firstObject.title')
|
|
#GraphRecordWrapper = Ember.Object.extend
|
|
# title: Ember.computed 'record.title', 'value_shift', ->
|
|
# title = @get('record.title')
|
|
# title = " #{title} (#{value_shift}d)" if value_shift = @get('value_shift')
|
|
# title
|
|
|
|
#GraphOffsetWrapper = Ember.Object.extend {}
|
|
export default Ember.Component.extend
|
|
classNames: ['action-execution-offset-diagram-component']
|
|
graph_height: 320
|
|
bar_height: 20
|
|
x_axis_space: 60
|
|
left_space: 70
|
|
svg_width: 1200
|
|
window_min: null
|
|
window_max: null
|
|
|
|
range_start_default: null
|
|
range_start: null
|
|
range_end: null
|
|
range_bounds: {min: -7, max: 7}
|
|
range_formatter: (-> [{to: @days_offset_format.bind(@)}, {to: @days_offset_format.bind(@)}]).property()
|
|
reference_date_day_of_week: Ember.computed.oneWay 'project.reference_date_day_of_week'
|
|
choose_reference_date: true # choose a specific date to display as planning. Use X with migration date at specific day otherwise
|
|
chosen_absolute_date: null
|
|
batch_size_simulation_range: Ember.computed 'project.{batch_size_lower_value,batch_size_upper_value}',->
|
|
{min: @get('project.batch_size_lower_value'), max: @get('project.batch_size_upper_value')}
|
|
batch_size_simulation_value: 0
|
|
batch_size_formatter: (-> [to: (v) -> Math.round(v)]).property()
|
|
|
|
graph_title: Ember.computed 'window_min', 'window_max', 'range_start', 'range_end', 'chosen_absolute_date', ->
|
|
range_min = @days_offset_format @get('range_start')
|
|
range_max = @days_offset_format @get('range_end')
|
|
window_min = @days_offset_format @get('window_min')
|
|
window_max = @days_offset_format @get('window_max')
|
|
range_addition = ''
|
|
if range_min isnt window_min or range_max isnt window_max
|
|
range_addition = "(visible #{range_min} t/m #{range_max})"
|
|
base = "Showing #{t('models.workflow_action_definition')} offsets #{range_addition} having total window #{window_min} t/m #{window_max}"
|
|
base += " for #{t('attributes.project_instance.reference_date')} #{@days_offset_format(0)}" if @get('chosen_absolute_date')
|
|
base
|
|
|
|
days_offset_format: (v) ->
|
|
v = Math.round(v) # could be float from slider
|
|
if @get('chosen_absolute_date')
|
|
date = moment(@get('chosen_absolute_date'))
|
|
date.add(v, 'day') if v
|
|
date.format("D-M-YYYY")
|
|
else
|
|
return '' unless v
|
|
return '' unless v and Number.isFinite(v)
|
|
if v % 7 is 0
|
|
v = v / 7
|
|
if v > 0 then "X+#{v}w" else "X#{v}w"
|
|
else
|
|
if v > 0 then "X+#{v}" else "X#{v}"
|
|
|
|
actions:
|
|
range_changed: ([range_start, range_end]) ->
|
|
[range_start, range_end] = [Math.round(range_start), Math.round(range_end)]
|
|
@setProperties
|
|
range_start_default: [range_start, range_end]
|
|
range_start: range_start
|
|
range_end: range_end
|
|
@_setup_graph() # manual setup, do not use observable patter for range_start and range_end
|
|
|
|
batch_size_simulation_change: (value) ->
|
|
@setProperties
|
|
range_start: null
|
|
range_end: null
|
|
batch_size_simulation_value: Math.round(value)
|
|
|
|
hide_absolute_date_choice: ->
|
|
@set 'chosen_absolute_date', null # important! determinse wether the labels use X or dates
|
|
@set 'choose_reference_date', false
|
|
|
|
init: ->
|
|
@_super arguments...
|
|
#@set 'batch_size_simulation_value', 0 + @get('project.batch_size_lower_value') if @get('project.batch_size_lower_value')
|
|
@set 'batch_size_simulation_value', @get('project.batch_size_lower_value') if not @get('batch_size_simulation_value') and @get('project.batch_size_lower_value')
|
|
|
|
didReceiveAttrs: ->
|
|
console.log 'Reload workflow-action-offset-diagram call from didReceiveAttrs'
|
|
Ember.run.scheduleOnce 'afterRender', @, @_setup_graph
|
|
|
|
#_graph_observer: Ember.observer 'active_workflow_action_definitions.@each.title', 'active_workflow_action_definitions.@each.offset_value', 'range_start', 'range_end', 'batch_size_simulation_value', 'batch_size_simulation_range', 'reference_date_day_of_week', 'chosen_absolute_date', 'active_workflow_action_definitions.@each.id', ->
|
|
_graph_observer: Ember.observer 'project.update_revision', ->
|
|
console.log 'Reload workflow-action-offset-diagram call from observer'
|
|
console.log "update_revision: #{@get('project.update_revision')}, range_start: #{@get('range_start')}, range_end: #{@get('range_end')}"
|
|
Ember.run.debounce @, @_setup_graph, 150
|
|
|
|
_setup_graph: ->
|
|
console.log 'Reload workflow-actions-offset-diagram'
|
|
container_selector = '.action-execution-offset-diagram-container'
|
|
@$(container_selector).html('')
|
|
component = @
|
|
|
|
return unless @get('active_workflow_action_definitions.length')
|
|
batch_size_simulation_value = @get('batch_size_simulation_value') || 0
|
|
reference_date_day_of_week = @get('reference_date_day_of_week')
|
|
unless reference_date = @get('chosen_absolute_date')
|
|
reference_date = moment()
|
|
reference_date.day reference_date_day_of_week if reference_date_day_of_week # updates reference_date instance
|
|
result_nodes = WorkflowActionDefinitionOffsetNodes.create
|
|
workflow_action_definitions: @get('active_workflow_action_definitions')
|
|
reference_date: reference_date
|
|
batch_size: batch_size_simulation_value
|
|
resolved_record_nodes = result_nodes.get_resolved()
|
|
return unless get resolved_record_nodes, 'length'
|
|
wrappers = []
|
|
x_min = -1 # at least include zero
|
|
x_max = 0
|
|
batch_size_simulation_value = @get('batch_size_simulation_value')
|
|
resolved_record_nodes.forEach (record_wrapper) ->
|
|
if wrapper = wrappers.findBy 'absolute_value', record_wrapper.get('absolute_value')
|
|
wrapper.get('records').push record_wrapper
|
|
else
|
|
absolute_value = record_wrapper.get('absolute_value')
|
|
x_min = Math.min(x_min, absolute_value)
|
|
x_max = Math.max(x_max, absolute_value)
|
|
wrappers.push GraphWrapper.create
|
|
records: Ember.A([record_wrapper])
|
|
absolute_value: absolute_value
|
|
date: record_wrapper.get('date')
|
|
@set 'window_min', x_min
|
|
@set 'window_max', x_max
|
|
|
|
##data = @get('workflow_action_definition_groups')
|
|
##return unless data.length
|
|
#x_min = d3.min(data.mapBy('absolute_value'))
|
|
#x_min = -1 if x_min > 0 # add space for execution line
|
|
#x_max = d3.max(data.mapBy('absolute_value'))
|
|
#x_max = 0 if x_max < 0
|
|
#x_max += 1 # last ending can have duration 1 when empty
|
|
|
|
if @get('range_bounds.min') isnt x_min or @get('range_bounds.max') isnt x_max
|
|
@set 'range_bounds', min: x_min, max: x_max
|
|
@set 'range_start_default', [x_min, x_max] # bounds have changed, reset starts
|
|
else if not @get('range_start_default') # uninitialized
|
|
@set 'range_start_default', [x_min, x_max]
|
|
|
|
@set 'range_start', x_min unless Number.isFinite(@get('range_start'))
|
|
@set 'range_end', x_max unless Number.isFinite(@get('range_end'))
|
|
graph_min = @get('range_start')
|
|
graph_max = @get('range_end')
|
|
|
|
graph_range_width = @get('svg_width') - @get('left_space') - 22
|
|
x = d3.scale.linear().domain([graph_min, graph_max]).range([0, graph_range_width])
|
|
#yscale = d3.scale.linear().domain([0, @get('number_of_groups')]).range([0, @get('graph_height')])
|
|
#y = d3.scale.ordinal().rangeRoundBands([@get('graph_height'), 0], 0.1).domain(workflow_groups.mapBy('name').reverse())
|
|
|
|
#x = d3.scale.linear().domain([x_min, x_max]).range([0, 872])
|
|
y = d3.scale.linear().domain([0, 1]).range([0, @get('graph_height')])
|
|
|
|
x_ticks = wrappers.mapBy 'absolute_value'
|
|
#data.forEach (group) ->
|
|
# offset = Ember.get(group, 'value')
|
|
# x_ticks.push offset unless offset is 0 or x_ticks.indexOf(offset) > -1
|
|
x_ticks = x_ticks.filter( (v) -> v >= graph_min and v <= graph_max).sort(d3.ascending)
|
|
displayed_x_ticks = []
|
|
if x_ticks.length
|
|
displayed_x_ticks.push x_ticks[0]
|
|
latest_pixel_x_for_space = x(x_ticks[0])
|
|
x_ticks.forEach (v) ->
|
|
current_pixel_x = x(v)
|
|
if current_pixel_x - latest_pixel_x_for_space > 14
|
|
latest_pixel_x_for_space = current_pixel_x
|
|
displayed_x_ticks.push v
|
|
xAxis = d3.svg.axis()
|
|
.orient('bottom')
|
|
.scale(x)
|
|
.tickSize(1)
|
|
.tickValues(displayed_x_ticks)
|
|
.tickFormat component.days_offset_format.bind(component)
|
|
@$(container_selector).append '<svg></svg>'
|
|
svg_height = @get('x_axis_space') + @get('graph_height')
|
|
canvas = d3.select("#{container_selector} svg")
|
|
.attr 'width', '100%'
|
|
.attr 'height', svg_height
|
|
.attr 'viewBox', "0 0 #{@get('svg_width')} #{svg_height}"
|
|
|
|
lines = canvas.append('g')
|
|
.attr('class', 'offset-lines')
|
|
.attr('transform', "translate(#{@get('left_space')},0)")
|
|
.selectAll('line')
|
|
.data(x_ticks) # first element is y-axis
|
|
.enter()
|
|
.append('line')
|
|
.attr(
|
|
x1: (d) -> x(d)
|
|
y1: 0
|
|
x2: (d) -> x(d)
|
|
y2: y(1)
|
|
class: 'offset-line'
|
|
)
|
|
|
|
offset_text = (record_wrapper) ->
|
|
title_text = "#{record_wrapper.get('record.workflow_group.name')} -> #{record_wrapper.get('title')}"
|
|
offset_texts = []
|
|
record_wrapper.get('offset_wrappers').forEach (offset_wrapper) ->
|
|
given_offset = offset_wrapper.get('total_offset') || 0
|
|
day_text = if Math.abs(given_offset) > 1 then t('date.unit.plural.day') else t('date.unit.day')
|
|
offset_spec = if given_offset then "#{given_offset} #{day_text}" else 'direct'
|
|
offset_spec += " (#{value_shift}d)" if value_shift = offset_wrapper.get('value_shift')
|
|
if offset_base_action_title = offset_wrapper.get('record.base_workflow_action_definition.title')
|
|
offset_base_group_title = offset_wrapper.get('record.base_workflow_action_definition.workflow_group.name')
|
|
offset_relation_text = "#{offset_spec}: #{offset_base_group_title} -> #{offset_base_action_title}"
|
|
else
|
|
offset_relation_text = "#{offset_spec} from X"
|
|
offset_texts.push "Offset: #{offset_relation_text}"
|
|
"<button class='ui tiny icon button pull-right edit-workflow-action-definition' data-workflow-action-definition='#{record_wrapper.get('record_id')}'><i class='pencil icon'/></button>#{title_text}<br>#{offset_texts.join('<br>')}"
|
|
texts = canvas.append('g')
|
|
.attr('class', 'action-descriptions')
|
|
.attr('transform', "translate(#{@get('left_space')},0)")
|
|
.selectAll('text')
|
|
.data(wrappers.filter((d) -> (d.get('absolute_value') >= graph_min) and (d.get('absolute_value') <= graph_max)))
|
|
.enter()
|
|
.append('text')
|
|
.attr 'class', 'action-description-text'
|
|
.attr('transform', 'rotate(-90)')
|
|
.html (wrapper) ->
|
|
wrapper.get('title')
|
|
#.html (group) ->
|
|
# if group.items.length <= 2
|
|
# group.items.map( (record) ->
|
|
# "<tspan class='action-description-action-part' data-record-id='#{record.id}' tooltip='#{Ember.get(record, 'workflow_group.name')}'>#{Ember.get(record, 'title')}</tspan>"
|
|
# ).join(', ')
|
|
# else
|
|
# "<tspan>#{group.items.length} #{actions_name}</tspan>"
|
|
.attr
|
|
x: 10 - @get('graph_height')
|
|
y: (d) -> x(Ember.get(d, 'absolute_value'))
|
|
dy: -5
|
|
dx: -5
|
|
.on 'click', (wrapper) ->
|
|
target = $(@)
|
|
unless target.hasClass 'tooltipstered'
|
|
target.tooltipster
|
|
content: 'Loading...'
|
|
contentAsHTML: true
|
|
selfDestruction: false
|
|
trigger: 'click'
|
|
interactive: true
|
|
functionReady: ->
|
|
$('.tooltipster-base .edit-workflow-action-definition').on 'click', ->
|
|
return target.tooltipster('close') unless id = $(@).data('workflowActionDefinition')
|
|
return target.tooltipster('close') unless target_workflow_action_definition = component.store.peekRecord('panda/workflow-action-definition', id)
|
|
component.modal 'workflow-action-definition/edit-workflow-action-definition',
|
|
model: target_workflow_action_definition
|
|
target.tooltipster('close')
|
|
|
|
record_offset_texts = wrapper.records.sortBy('record.absolute_position').map(offset_text)
|
|
if wrapper.records.length > 1
|
|
target.tooltipster 'content', record_offset_texts.join('<hr>')
|
|
else
|
|
target.tooltipster 'content', record_offset_texts[0]
|
|
target.tooltipster 'open'
|
|
|
|
if graph_min <= 0 <= graph_max
|
|
base_line = canvas.append('line').attr
|
|
x1: x(0)
|
|
y1: 0
|
|
x2: x(0)
|
|
y2: 15 + @get('graph_height')
|
|
class: 'base-line'
|
|
transform: "translate(#{@get('left_space')},0)"
|
|
|
|
x_xis = canvas.append('g')
|
|
.attr('transform', "translate(#{@get('left_space')},#{@get('graph_height')})")
|
|
.attr('class','x-axis')
|
|
.call(xAxis)
|
|
.selectAll('text')
|
|
.style('text-anchor', 'end')
|
|
.attr('dx', '-.8em')
|
|
.attr('dy', '.15em')
|
|
.attr('transform', "rotate(-40)")
|
|
|
|
canvas.selectAll('.action-description-action-part').on 'click', (d) ->
|
|
return unless workflow_action_definition_id = $(@).data('recordId')
|
|
return unless workflow_action_definition = component.store.peekRecord('panda/workflow-action-definition', workflow_action_definition_id)
|
|
component.modal 'workflow-action-definition/edit-workflow-action-definition',
|
|
model: workflow_action_definition
|