charts_radarchart = (obj, data, options) -> | |
cfg = { | |
radius: 5, | |
w: 360, | |
h: 360, | |
factor: 1, | |
factorLegend: .85, | |
levels: 4, | |
maxValue: 100, | |
radians: 2 * Math.PI, | |
opacityArea: 0.5, | |
ToRight: 5, | |
TranslateX: 30, | |
TranslateY: 30, | |
ExtraWidthX: 200, | |
ExtraWidthY: 100, | |
color: genColors(16, 0.55, 0.475, true) | |
} | |
LegendOptions = [] | |
d = data | |
if data.indicators and data.data | |
d = [] | |
for el, i in data.data | |
li = [] | |
LegendOptions.push(el.name) | |
for indicator, j in data.indicators | |
li.push({axis: indicator, value: el.value[j]}) | |
d.push(li) | |
cfg.maxValue = Math.max(cfg.maxValue, d3.max(d, (i) => d3.max(i.map((o) => o.value)))) | |
axes = (d[0].map((i, j) => i.axis)) | |
total = axes.length; | |
radius = cfg.factor*Math.min(cfg.w/2, cfg.h/2); | |
Format = (edge) => | |
Math.floor((edge/24)+0.5) + "↑ (" + (5**(edge/24)).pretty() + ")" | |
d3.select(obj).select("svg").remove(); | |
rect = obj.getBoundingClientRect() | |
g = d3.select(obj) | |
.append("svg") | |
.attr("preserveAspectRatio", "xMinYMin meet") | |
.attr("viewBox", "0 0 1000 500") | |
.append("g") | |
.attr("transform", "translate(" + cfg.TranslateX + "," + cfg.TranslateY + ")"); | |
; | |
# Indicator lines | |
for j in [0...cfg.levels] | |
levelFactor = cfg.factor * radius * ( (j+1) / cfg.levels) | |
g.selectAll(".levels") | |
.data(axes) | |
.enter() | |
.append("svg:line") | |
.attr("x1", (d, i) => levelFactor*(1-cfg.factor*Math.sin(i*cfg.radians/total))) | |
.attr("y1", (d, i) => levelFactor*(1-cfg.factor*Math.cos(i*cfg.radians/total))) | |
.attr("x2", (d, i) => levelFactor*(1-cfg.factor*Math.sin((i+1)*cfg.radians/total))) | |
.attr("y2", (d, i) => levelFactor*(1-cfg.factor*Math.cos((i+1)*cfg.radians/total))) | |
.attr("class", "line") | |
.style("stroke", "grey") | |
.style("stroke-opacity", "0.75") | |
.style("stroke-width", "0.3px") | |
.attr("transform", "translate(" + (cfg.w/2-levelFactor) + ", " + (cfg.h/2-levelFactor) + ")") | |
# Levels | |
for j in [0...cfg.levels] | |
levelFactor = cfg.factor*radius*((j+1)/cfg.levels); | |
g.selectAll(".levels") | |
.data([1]) | |
.enter() | |
.append("svg:text") | |
.attr("x", (d) => levelFactor*(1-cfg.factor*Math.sin(0))) | |
.attr("y", (d) => levelFactor*(1-cfg.factor*Math.cos(0))) | |
.attr("class", "legend") | |
.style("font-family", "sans-serif") | |
.style("font-size", "10px") | |
.attr("transform", "translate(" + (cfg.w/2-levelFactor + cfg.ToRight) + ", " + (cfg.h/2-levelFactor) + ")") | |
.attr("fill", "#737373") | |
.text(Format((j+1)*cfg.maxValue/cfg.levels)) | |
series = 0 | |
axis = g.selectAll(".axis") | |
.data(axes) | |
.enter() | |
.append("g") | |
.attr("class", "axis") | |
axis.append("line") | |
.attr("x1", cfg.w/2) | |
.attr("y1", cfg.h/2) | |
.attr("x2", (d,i) => cfg.w/2*(1-cfg.factor*Math.sin(i*cfg.radians/total))) | |
.attr("y2", (d,i) => cfg.h/2*(1-cfg.factor*Math.cos(i*cfg.radians/total))) | |
.attr("class", "line") | |
.style("stroke", "grey") | |
.style("stroke-width", "1px"); | |
axis.append("text") | |
.attr("class", "legend") | |
.text((d) => d) | |
.style("font-family", "sans-serif") | |
.style("font-size", "11px") | |
.attr("text-anchor", "middle") | |
.attr("dy", "1.5em") | |
.attr("transform", (d,i) => "translate(0, -10)") | |
.attr("x", (d,i) => cfg.w/2*(1-cfg.factorLegend*Math.sin(i*cfg.radians/total))-60*Math.sin(i*cfg.radians/total)) | |
.attr("y", (d,i) => cfg.h/2*(1-Math.cos(i*cfg.radians/total))-20*Math.cos(i*cfg.radians/total)) | |
d.forEach((y,x) -> | |
dataValues = [] | |
g.selectAll(".nodes") | |
.data(y, (j,i) -> | |
#alert(j,i) | |
dataValues.push(\ | |
[\ | |
cfg.w/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)),\ | |
cfg.h/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total))\ | |
]\ | |
) | |
) | |
dataValues.push(dataValues[0]) | |
g.selectAll(".area") | |
.data([dataValues]) | |
.enter() | |
.append("polygon") | |
.attr("class", "radar-chart-serie"+series) | |
.style("stroke-width", "2px") | |
.style("stroke", cfg.color[series]) | |
.attr("points", (d) -> | |
str="" | |
#alert(d) | |
for pt in d | |
str=str+pt[0]+","+pt[1]+" "; | |
return str | |
) | |
.style("fill", (j,i) => cfg.color[series]) | |
.style("fill-opacity", cfg.opacityArea) | |
.on('mouseover', (d) -> | |
z = "polygon."+d3.select(this).attr("class"); | |
g.selectAll("polygon") | |
.transition(200) | |
.style("fill-opacity", 0.1); | |
g.selectAll(z) | |
.transition(200) | |
.style("fill-opacity", .7); | |
) | |
.on('mouseout', () -> | |
g.selectAll("polygon") | |
.transition(200) | |
.style("fill-opacity", cfg.opacityArea); | |
) | |
series++ | |
) | |
series = 0 | |
d.forEach( (y,x) -> | |
g.selectAll(".nodes") | |
.data(y).enter() | |
.append("svg:circle") | |
.attr("class", "radar-chart-serie"+series) | |
.attr('r', cfg.radius) | |
.attr("alt", (j) => Math.max(j.value, 0)) | |
.attr("cx", (j,i) -> | |
dataValues = dataValues || [] | |
dataValues.push([ | |
cfg.w/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)), | |
cfg.h/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total)) | |
]) | |
return cfg.w/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)) | |
) | |
.attr("cy", (j,i) -> | |
return cfg.h/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total)) | |
) | |
.attr("data-id", (j) => j.axis) | |
.style("fill", cfg.color[series]).style("fill-opacity", .9) | |
.on('mouseover', (d) -> | |
newX = parseFloat(d3.select(this).attr('cx')) - 10 | |
newY = parseFloat(d3.select(this).attr('cy')) - 5 | |
tooltip | |
.attr('x', newX) | |
.attr('y', newY) | |
.text(Format(d.value)) | |
.transition(200) | |
.style('opacity', 1) | |
z = "polygon."+d3.select(this).attr("class"); | |
g.selectAll("polygon") | |
.transition(200) | |
.style("fill-opacity", 0.1) | |
g.selectAll(z) | |
.transition(200) | |
.style("fill-opacity", .7) | |
) | |
.on('mouseout', () -> | |
tooltip | |
.transition(200) | |
.style('opacity', 0); | |
g.selectAll("polygon") | |
.transition(200) | |
.style("fill-opacity", cfg.opacityArea); | |
) | |
.append("svg:title") | |
.text((j) => Math.max(j.value, 0)); | |
series++ | |
); | |
# Tooltip | |
tooltip = g.append('text') | |
.style('opacity', 0) | |
.style('font-family', 'sans-serif') | |
.style('font-size', '13px'); | |
legend = g.append("g") | |
.attr("class", "legend") | |
.attr("height", 100) | |
.attr("width", 200) | |
.attr('transform', 'translate(90,20)') | |
legend.selectAll('rect') | |
.data(LegendOptions) | |
.enter() | |
.append("rect") | |
.attr("x", cfg.w - 65) | |
.attr("y", (d,i) => i * 20) | |
.attr("width", 10) | |
.attr("height", 10) | |
.style("fill", (d,i) => cfg.color[i]) | |
legend.selectAll('text') | |
.data(LegendOptions) | |
.enter() | |
.append("text") | |
.attr("x", cfg.w - 52) | |
.attr("y",(d, i) => (i * 20 + 9)) | |
.attr("font-size", "11px") | |
.attr("fill", "#737373") | |
.text((d) => d) | |
g.resize = () -> return true | |
return [g, {}] | |