var _bumpchart = { defaultOption : { "chart_div_options": {}, "inner_div_options": {}, "title": null, "categories": "categories", "rank_table": "rank_table", "name": "name", "ranks": "ranks", "important_factor": 0.25, "importants": [], "tick_skip": 1, "height": 0 }, example : [ { "name": "예제1", "description": "bumpchart 예제\n", "sample": { "type": "bumpchart", "processing": false, "id": "test.bumpchart", "visible": true, "options" : { "title": "화장품 랭킹 변화", "chart_div_options": {"classes": "customChartArea", "styles": {"height": "400px"}}, "inner_div_options": {"classes": "customChart"}, "xlsx": true }, "data": { "categories": ["2018년 1월", "2018년 2월", "2018년 3월", "2018년 4월", "2018년 5월", "2018년 6월", "2018년 7월", "2018년 8월", "2018년 9월", "2018년 10월", "2018년 11월", "2018년 12월"], "rank_table": [ {"ranks": [0, 0, 1, 1, 2, 2, 1, 1, 1, 0, 0, 0], "name": "이니스프리(innisfree)"}, {"ranks": [5, 3, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1], "name": "토니모리"}, {"ranks": [16, 12, 12, 5, 8, 3, 5, 4, 3, 5, 7, 2], "name": "라운드랩"}, {"ranks": [1, 1, 2, 2, 1, 0, 2, 2, 2, 3, 3, 3], "name": "마몽드"}, {"ranks": [18, 18, 18, 18, 19, 19, 18, 18, 5, 2, 5, 4], "name": "라네즈"}, {"ranks": [2, 2, 4, 3, 3, 4, 3, 3, 4, 6, 4, 5], "name": "에뛰드하우스"}, {"ranks": [19, 19, 19, 19, 17, 9, 19, 19, 15, 4, 2, 6], "name": "플로우(FFLOW)"}, {"ranks": [3, 4, 5, 6, 11, 8, 13, 7, 9, 8, 10, 7], "name": "하다라보"}, {"ranks": [7, 8, 7, 7, 5, 5, 11, 6, 11, 13, 8, 8], "name": "시드물"}, {"ranks": [6, 9, 8, 10, 4, 6, 7, 15, 10, 16, 12, 9], "name": "나츄리에(naturie)"}, {"ranks": [4, 10, 3, 4, 7, 12, 10, 9, 13, 15, 17, 10], "name": "클레어스(Dear,klairs)"}, {"ranks": [10, 7, 6, 14, 10, 7, 6, 5, 6, 10, 14, 11], "name": "프리메라"}, {"ranks": [9, 5, 10, 15, 13, 14, 8, 10, 12, 11, 9, 12], "name": "닥터벨머(Dr.Belmeur)"}, {"ranks": [12, 11, 16, 11, 9, 10, 14, 11, 17, 14, 16, 13], "name": "식물나라"}, {"ranks": [13, 14, 11, 16, 16, 17, 17, 17, 18, 18, 18, 14], "name": "어퓨"}, {"ranks": [14, 16, 14, 12, 15, 13, 12, 12, 14, 17, 13, 15], "name": "이즈앤트리"}, {"ranks": [15, 15, 9, 9, 12, 16, 9, 14, 7, 9, 6, 16], "name": "한율"}, {"ranks": [8, 6, 13, 8, 6, 11, 15, 8, 16, 12, 15, 17], "name": "스킨푸드"}, {"ranks": [17, 17, 17, 17, 18, 18, 4, 13, 8, 7, 11, 18], "name": "리얼베리어"}, {"ranks": [11, 13, 15, 13, 14, 15, 16, 16, 19, 19, 19, 19], "name": "빌리프"} ] } } } ], extract_important_names: function (data, options) { var name = options.name; var ranks = options.ranks; var rank_table = data[options.rank_table]; var n = rank_table[0][ranks].length; function delta_cmp(a, b) { return Math.abs(b[ranks][0] - b[ranks][n - 1]) - Math.abs(a[ranks][0] - a[ranks][n - 1]); } var deltas = rank_table.sort(delta_cmp).filter(function (d) { return d[ranks][0] != d[ranks][n - 1]; }); var max_important = Math.floor(rank_table.length * options.important_factor); return deltas.slice(0, max_important).map(function (d) { return d[name]; }); }, expand_rank_table: function (data, options) { var name = options.name; var ranks = options.ranks; var org_rank_table = data[options.rank_table]; var rank_table = org_rank_table.map(function (item) { return { name: item[name], ranks: item[ranks].reduce(function (total, d, i, a) { if (i > 0) return total.concat([a[i - 1], (a[i - 1] + d) / 2, d, d]); else return total.concat([d]); },[]) }; }); return rank_table; }, render_chart: function ($innerDiv, unit) { var data = unit.data; var options = unit.options; // 데이터 전처리 var importants = options.importants.concat(_bumpchart.extract_important_names(data, options)); var rank_table = _bumpchart.expand_rank_table(data, options); // 여기서부터 그리기 작업 var target = d3.select($innerDiv[0]); // 각종 옵션들 var important_factor = options.important_factor; var tick_skip = options.tick_skip; var width = target.node().getBoundingClientRect().width; var height = target.node().getBoundingClientRect().height; var label_width = Math.max.apply(null, rank_table.map(function (d) { return d.name.width_in_pixel("13px NanumBarunGothic"); })); var bump_margin = {left: label_width + 10, right: label_width + 10, top: 40, bottom: 5}; var vh = rank_table.length * 20 + bump_margin.top + bump_margin.bottom; var vw; if (vh < height) { vw = width; vh = height; } else vw = width * vh / height; var svg = target.append("svg") .attr("viewBox", `0 0 ${vw} ${vh}`) .attr("width", width) .attr("height", height) .append("g"); var bump_width = vw - bump_margin.left - bump_margin.right; var bump_height = vh - bump_margin.top - bump_margin.bottom; var bump_area = svg.append("g") .attr("class", "bumpchart") .attr("transform", "translate(" + bump_margin.left + "," + bump_margin.top + ")"); var colors = d3.scale.category20(); var x_scale = d3.scale.linear() .domain([0, rank_table[0].ranks.length - 1]) .range([0, bump_width]); var y_scale = d3.scale.linear() .domain([0, rank_table.length]) .range([0, bump_height]); var line = d3.svg.line() .interpolate("monotone") .x(function (d, i) { return x_scale(i); }) .y(function (d, i) { return y_scale(d + 0.5); }); var bumps = bump_area.selectAll(".item") .data(rank_table) .enter().append("g") .attr("class", "item") .classed("important", function (d) { return importants.indexOf(d.name) >= 0; }) .on("mouseover", function (d, i) { d3.select(this).classed("focus", true); }) .on("mouseout", function (d, i) { d3.select(this).classed("focus", false); }) .on("click", function (d, i) { d3.select(this).classed("important", !d3.select(this).classed("important")); }); bumps.append("path") .attr("class", "rank") .attr("d", function (d) { return line(d.ranks); }) .style("stroke", function (d, i) { return colors(i); }); bumps.append("text") .attr("class", "left") .attr("x", -4) .attr("y", function (d) { return y_scale(d.ranks[0] + 0.5); }) .style("fill", function (d, i) { return colors(i); }) .style("stroke", function (d, i) { return colors(i); }) .text(function (d) { return d.name; }); bumps.append("text") .attr("class", "right") .attr("x", bump_width + 4) .attr("y", function (d) { return y_scale(d.ranks[d.ranks.length - 1] + 0.5); }) .style("fill", function (d, i) { return colors(i); }) .style("stroke", function (d, i) { return colors(i); }) .text(function (d) { return d.name; }); // x축 그리기 var category_values = data[options.categories]; var rmd = (category_values.length - 1) % (tick_skip + 1); var tick_values = category_values.map(function (d, i) { if ((i % (tick_skip + 1)) == rmd) return d; else return ""; }); var category_scale = d3.scale.ordinal() .domain(category_values) .rangePoints([0, bump_width]); var category_axis = d3.svg.axis() .scale(category_scale) .orient("top") .tickFormat(function (d, i) { return ((i % (tick_skip + 1)) == rmd) ? d : ""; }); /* 눈금까지 없애고 싶을 경우 if (tick_skip) { var rmd = (category_values.length - 1) % (tick_skip + 1); var tick_values = category_values.filter(function (d, i) { return (i % (tick_skip + 1)) == rmd; }); category_axis.tickValues(tick_values); } */ bump_area.append("g") .attr("class", "x axis") .attr("transform", "translate(" + 0 + "," + (-12) +")") .call(category_axis) .selectAll("text") .attr("y", -12); }, render : function($dom, unit, with_chart_area = true) { if (with_chart_area) { var $chartDiv = $("
"); $dom.append($chartDiv); } else { var $chartDiv = $dom; } var options = unit.options; if (!options) options = unit.options = jQuery.extend(true, {}, _bumpchart.defaultOption); else options = unit.options = jQuery.extend(true, {}, _bumpchart.defaultOption, unit.options); var chart_div_options = options.chart_div_options; var inner_div_options = options.inner_div_options; if (options.height) $chartDiv.css({"height": options.height, "min-height": options.height}); if (chart_div_options.classes) $chartDiv.addClass(chart_div_options.classes); if (chart_div_options.attrs) $chartDiv.attr(chart_div_options.attrs); if (chart_div_options.styles) $chartDiv.css(chart_div_options.styles); var chartDivId = ReportRenderer.getUniqueDivId(); var $innerDiv = $(""); if (options.title) { var $titleDiv = $(""); $chartDiv.append($titleDiv); $titleDiv.append($("").text(options.title)); } if (inner_div_options.classes) $innerDiv.addClass(inner_div_options.classes); if (inner_div_options.attrs) $innerDiv.attr(inner_div_options.attrs); if (inner_div_options.styles) $innerDiv.css(inner_div_options.styles); $chartDiv.append($innerDiv); _bumpchart.render_chart($innerDiv, unit) return $chartDiv; } }; UnitRendererFactory.add("bumpchart", _bumpchart.render); UnitRendererFactory.addExample("bumpchart", _bumpchart.example);