Current section

Files

Jump to
clean_mixer lib ui arch_map_rendering plantuml_renderer.ex
Raw

lib/ui/arch_map_rendering/plantuml_renderer.ex

defmodule CleanMixer.UI.ArchMapRendering.PlantUMLRenderer do
alias CleanMixer.ArchMap
alias CleanMixer.ArchMap.Component
alias CleanMixer.ArchMap.Dependency
alias CleanMixer.Metrics.MetricsMap
alias CleanMixer.Metrics.ComponentMetrics.FanIn
alias CleanMixer.Metrics.ComponentMetrics.FanOut
alias CleanMixer.Metrics.ComponentMetrics.Instability
alias CleanMixer.Metrics.ComponentMetrics.Stability
alias CleanMixer.Metrics.ComponentMetrics.Abstractness
alias CleanMixer.Metrics.ComponentMetrics.Distance
alias CleanMixer.Metrics.ComponentMetrics.PublicFiles
alias CleanMixer.Metrics.ComponentMetrics.AbstractIn
alias CleanMixer.Metrics.ComponentMetrics.AbstractOut
alias CleanMixer.Metrics.DependencyMetrics.Usage
@spec render(ArchMap.t(), MetricsMap.t(Component.t()), MetricsMap.t(Dependency.t()), map) :: String.t()
def render(arch_map, component_metrics, dependency_metrics, params) do
[
"@startuml",
"skinparam legend {\n FontSize 20\n }",
"legend bottom left\n #{legend(params)} \n endlegend",
Enum.map(arch_map.components, &format_component(&1, component_metrics)),
Enum.map(arch_map.dependencies, &format_dependency(&1, component_metrics, dependency_metrics, params)),
"@enduml"
]
|> List.flatten()
|> Enum.join("\n")
end
defp legend(params) do
main_legend = [
"I = Instability = out / (in + out)",
"S = Stability = 1 - I",
"Pf = Public files",
"A = Abstractness = behaviours / total_modules",
"D = Distance = |A+I-1|",
"Ain = Abstract in",
"Aout = Abstract out"
]
full_legend =
if params[:verbose] do
[main_legend, "U = Usage = UsedFiles / Pf"]
else
main_legend
end
full_legend |> List.flatten() |> Enum.join("\n")
end
defp format_component(comp, metrics_map) do
"rectangle #{sanitize(comp.name)} [ =#{comp.name} \n\n #{component_desc(comp, metrics_map)} ]"
end
defp component_desc(comp, metrics_map) do
metrics = MetricsMap.metrics_of(metrics_map, comp)
fan_in = metrics[FanIn]
fan_out = metrics[FanOut]
instability = format_metric(metrics[Instability])
stability = format_metric(metrics[Stability])
abstractness = format_metric(metrics[Abstractness])
public_files = metrics[PublicFiles]
abstract_in = metrics[AbstractIn]
abstract_out = metrics[AbstractOut]
distance = format_metric(metrics[Distance])
distance_sigmas = format_metric(MetricsMap.sigmas_count(metrics_map, comp, Distance))
"In=#{fan_in} Out=#{fan_out} S=#{stability} (I=#{instability}) \n" <>
"Pf=#{public_files} A=#{abstractness} D=#{distance} (#{distance_sigmas}σ) \n" <>
"Ain=#{abstract_in} Aout=#{abstract_out}"
end
defp format_dependency(%Dependency{} = dep, component_metrics, dependency_metrics, params) do
link = "[#{sanitize(dep.source.name)}] -[#{link_style(dep, component_metrics)}]-> [#{sanitize(dep.target.name)}]"
if params[:verbose] do
usage = dependency_metrics |> MetricsMap.metric(dep, Usage) |> format_metric()
link <> " : " <> "U=#{usage}"
else
link
end
end
defp link_style(%Dependency{} = dep, component_metrics) do
source_stability = MetricsMap.metric(component_metrics, dep.source, Stability)
target_stability = MetricsMap.metric(component_metrics, dep.target, Stability)
if source_stability > target_stability do
"#red,bold"
else
"#black"
end
end
defp format_metric(value) do
Float.round(value, 2)
end
defp sanitize(name) do
name
|> String.replace(" ", "_")
|> String.replace("/", "_")
|> String.replace("-", "_")
end
end