From 9f3a14ebf72a4552a4985b055d0681e8f1ab4886 Mon Sep 17 00:00:00 2001 From: Alan Date: Tue, 9 May 2023 15:47:52 +0800 Subject: [PATCH] fix #7522 - merge insights into main branch --- g.bar.overlay.js | 227 +++++++++++++++++++++++++++++++--------------- g.bar.split.js | 92 ++++++++++++------- g.pie.circular.js | 54 ++++++++--- g.pie.sector.js | 187 ++++++++++++++++++++++++++++++-------- seed/toSVG.js | 10 +- test/bartest.html | 89 ++++++++++++++---- 6 files changed, 477 insertions(+), 182 deletions(-) diff --git a/g.bar.overlay.js b/g.bar.overlay.js index 7ad21ee..221a81a 100644 --- a/g.bar.overlay.js +++ b/g.bar.overlay.js @@ -31,8 +31,6 @@ if (typeof(Raphael) == 'undefined') { /*\ - * Paper.vbarchart - [ method ] ** * Creates a vertical bar chart ** @@ -45,18 +43,37 @@ if (typeof(Raphael) == 'undefined') { - values (array) values - opts (object) options for the chart o { - o type (string) type of endings of the bar. Default: 'square'. Other options are: 'round', 'sharp', 'soft'. - o gutter (number)(string) default '20%' (WHAT DOES IT DO?) - o vgutter (number) - o colors (array) colors be used repeatedly to plot the bars. If multicolumn bar is used each sequence of bars with use a different color. - o stacked (boolean) whether or not to tread values as in a stacked bar chart - o to - o stretch (boolean) + background (string) : background color + gutter (string) (number) : indicate width of gutters with repsect to the bars in terms of percentage (e.g. '20%' means that the width ratio of gutter to bar is 10:2) + barsteps (number) : number of bars shown + barfill (string) : color to fill the empty bar (not filling if not provided) + baroffset (number) : distance between the rightmost bar and y axis; + colors (array) : colors of the bars + axis (string) : composed of 4 '0' / '1' separated by space (indicated whether top / right / bottom / left axis should be shown) + (e.g. '0 1 1 0' indicates that the right and the bottom axis should be shown) + asixxheight (number) : height of x axis area + axisxlabels (string) : labels in x axis + asixywidth (number) : width of y axis area + axisystep (number) : number of steps in y axis + axislinecolor (string) : color of lines in y axis + axisfont (string) : font family of labels in axes + axisfontsize (number) : font size of labels in axes + axisfontweight (string) (number) : font weight of labels in axes + axisfontcolor (string) : font color of labels in axes + labelfont (string) : font family of labels + labelsize (number) : font size of labels + labelweight (string)(number) : font weight of labels + labelcolors (array) : font colors of the labels + legends (array) : legend + legendheight : height of the legend + legendkeyshape (string) : shape of the legend keys ('circle' / 'rect') + legendkeysize (number) : size of the legend keys (diameter for 'circle' and width for 'rect') + legendfont (string) : font family of the legend labels + legendfontsize (number) : font size of the legend labels + legendfontweight (string)(number) : font weight of the legend labels + legendfontcolor (string) : font color of the legend colors o } ** - = (object) path element of the popup - > Usage - | r.vbarchart(0, 0, 620, 260, [[76, 70], [67, 71]], {}) \*/ @@ -68,14 +85,36 @@ if (typeof(Raphael) == 'undefined') { chart = paper.set(), bars = paper.set(), covers = paper.set(), + len = values.length, + gutter = parseFloat(opts.gutter || "0%"), barsteps = opts.barsteps || 20, - colors = opts.colors || chartinst.colors; + barFill = opts.barfill || false, // default no fill + colors = opts.colors || chartinst.colors, + axisLineColor = opts.axislinecolor || '#BABABA', + axisFont = opts.axisfont || "'Fontin Sans', Fontin-Sans, sans-serif", + axisFontSize = opts.axisfontsize || ((barsteps > 10) ? 12 : 14), + axisFontWeight = opts.axisfontweight || 'bold', + axisFontColor = opts.axisfontcolor || '#BABABA', + labelFont = opts.labelfont || "'Fontin Sans', Fontin-Sans, sans-serif", + labelSize = opts.labelsize || ((barsteps > 10) ? 10 : 12), + labelWeight = opts.labelweight || 'bold', + labelColors = opts.labelcolors || [], + legendKeyShape = opts.legendkeyshape || 'circle', + legendKeySize = opts.legendkeysize || 16, + legendFont = opts.legendfont || "'Fontin Sans', Fontin-Sans, sans-serif", + legendFontSize = opts.legendfontsize || 14, + legendFontWeight = opts.legendfontweight || 'bold', + legendFontColor = opts.legendfontcolor || '#0C014D'; + + for(var i = 0; i < len; i++) { + labelColors.push('#FFFFFF'); + } opts.axis = opts.axis || ""; opts.baroffset = opts.baroffset || 10; - opts.asixxheight = opts.asixxheight || 100; - opts.asixywidth = opts.asixywidth || 100; + axisXHeight = opts.asixxheight || 100; + axisYWidth = opts.asixywidth || 100; opts.axisxlabels = opts.axisxlabels || []; opts.legendheight = opts.legendheight || 80; opts.legends = opts.legends || 100; @@ -84,16 +123,22 @@ if (typeof(Raphael) == 'undefined') { paper.rect(0, 0, width, height).attr({ stroke: "none", fill: (opts.background || "#F0F4F7") }); // background.toBack(); - // bar border - var barwidth = Math.floor((width - x - opts.asixywidth - opts.baroffset) / barsteps); - var barheight = height - y - opts.asixxheight - opts.legendheight || 100; - var path = ["M", x + 1, y + opts.legendheight, "l", 0, barheight] + // bar border && bar gutter + // total 'barsteps' of bars and 'barsteps - 1' of gutters + var barWidth = (width - x - axisYWidth - opts.baroffset) / (barsteps * (100 + gutter) - gutter) * 100; + var barGutter = barWidth * gutter / 100; + var barHeight = height - y - axisXHeight - opts.legendheight || 100; + var path = []; for (var i = 0; i < barsteps; i++) { - path = path.concat(["M", x + (i * barwidth), y + opts.legendheight + 1, "l", barwidth, 0, "l", 0, barheight]); + path = path.concat(["M", x + (i * (barWidth + barGutter)), height - axisXHeight, "l", 0, -1 * barHeight, "l", barWidth, 0, "l", 0, barHeight]); + + if(barFill) { + paper.rect(x + (i * (barWidth + barGutter)) + 1 , y + opts.legendheight + 1, barWidth - 2, barHeight - 2).attr({ stroke: "none", fill: barFill }); + } } - paper.path(path).attr({ stroke: "#fff", "stroke-width": 2 }); + paper.path(path).attr({ stroke: "#FFF", "stroke-width": 2}); var max = 0; @@ -103,29 +148,44 @@ if (typeof(Raphael) == 'undefined') { } value.forEach(function(v,k) { + if(k >= barsteps) { + return; + } max = (v > max) ? v : max; }); }); + // legends values.forEach(function(value,i) { if (!Raphael.is(value, "array")) { value = [value]; } + + // 10 pixels away from top of the chart + // 10 pixels away from left of the chart + if(legendKeyShape == 'rect') { + // pass top left position for 'rect' + paper.rect(x + (i * (width - x) / len) + 10, y + 10, legendKeySize, legendKeySize, 0).attr({ fill: colors[i%colors.length], "stroke": "#fff" }); + } + else { + // pass center position for 'circle' + paper.circle(x + (i * (width - x) / len) + 10 + (legendKeySize / 2), y + 10 + (legendKeySize / 2), legendKeySize / 2).attr({ fill: colors[i%colors.length], "stroke": "#fff" }); + } - paper.circle(x + (i * 400) + 8, y + 10, 8).attr({ fill: colors[i%colors.length], "stroke": "#fff" }); - - paper.text(x + (i * 400) + 18, y + 8, opts.legends[i%opts.legends.length]).attr({ - "font-size": "14", - "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", - "font-weight": "bold", + // 10 pixels away from right of the legend key + // align legend key and text horizontally + paper.text(x + (i * (width - x) / len) + 10 + legendKeySize + 10, y + 10 + (legendKeySize / 2) - legendFontSize / 10, opts.legends[i%opts.legends.length]).attr({ + "font-size": legendFontSize, + "font-family": legendFont, + "font-weight": legendFontWeight, "text-anchor": "start", - fill : "#0C014D" + fill : legendFontColor }); }); //bars - var unit = barheight / max; + var unit = barHeight / max; values.forEach(function(value,i) { if (!Raphael.is(values, "array")) { @@ -133,27 +193,32 @@ if (typeof(Raphael) == 'undefined') { } value.forEach(function(v,k) { - if(v == 0) { + if(v == 0 || k >= barsteps) { return; } - paper.rect(x + 1 + (k * barwidth) , y + opts.legendheight + 1 + (max - v) * unit, barwidth - 2, v * unit).attr({ stroke: "none", fill: colors[i%colors.length] }); - - var indicator_y = y + opts.legendheight + (max - v) * unit - 42; + paper.rect(x + (k * (barWidth + barGutter)) + 1 , y + opts.legendheight + 1 + (max - v) * unit, barWidth - 2, v * unit - 2).attr({ stroke: "none", fill: colors[i%colors.length] }); + + // width and height of indicator + var iw = barWidth * 0.8; + var ih = Math.min(30, iw * 0.5); + + // position of indicator + var ix = x + (k * (barWidth + barGutter)) + 1 + (barWidth - iw) / 2; + var iy = y + opts.legendheight + 1 + (max - v) * unit - ih - 20; if(i == 0) { - indicator_y = y + opts.legendheight - 42; + iy = y + opts.legendheight + 1 - ih - 20; } - var fontSize = barsteps > 10 ? 10 : 12; - - paper.rect(x + 1 + (k * barwidth) + 5 , indicator_y, barwidth - 10, 30, 5).attr({ stroke: "none", fill: colors[i%colors.length] }); - paper.path(["M", x + 1 + (k * barwidth) + barwidth / 4 + 10, indicator_y + 30, "l", 5, 10, "l", 5, -10]).attr({ stroke: "none", fill: colors[i%colors.length] }); - paper.text(x + 1 + (k * barwidth) + barwidth / 2, indicator_y + 14, Roo.util.Format.number(v, 0)).attr({ - "font-size": fontSize, - "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", - "font-weight": "bold", - fill : "#fff" + paper.rect(ix , iy, iw, ih, 4).attr({ stroke: "none", fill: colors[i%colors.length] }); + paper.path(["M", ix + (iw - 10) / 2, iy + ih, "l", 5, 10, "l", 5, -10]).attr({ stroke: "none", fill: colors[i%colors.length] }); + // align labels center + paper.text(ix + iw / 2, iy + ih / 2 - labelSize / 10, Roo.util.Format.number(v, 0)).attr({ + "font-size": labelSize, + "font-family": labelFont, + "font-weight": labelWeight, + fill : labelColors[i] }); }); }); @@ -164,14 +229,18 @@ if (typeof(Raphael) == 'undefined') { // right axis +ax[1] && axis.push( chartinst.rightAxis( - width - opts.asixywidth, - height - opts.asixxheight, - height - y - opts.asixxheight - opts.legendheight, + width - axisYWidth, + height - axisXHeight, + height - y - axisXHeight - opts.legendheight, max, opts.axisystep || 10, - opts.asixywidth, - barsteps, - paper + axisYWidth, + paper, + axisLineColor, + axisFont, + axisFontSize, + axisFontWeight, + axisFontColor ) ); @@ -179,13 +248,19 @@ if (typeof(Raphael) == 'undefined') { +ax[2] && axis.push( chartinst.bottomAxis( x , - height - opts.asixxheight, - width, - opts.axisxlabels.length, + height - axisXHeight, + width - x - axisYWidth / 2, opts.axisxlabels, - barwidth, - opts.asixxheight, - paper + barsteps, + barWidth, + barGutter, + axisXHeight, + paper, + axisLineColor, + axisFont, + axisFontSize, + axisFontWeight, + axisFontColor ) ); @@ -204,13 +279,12 @@ if (typeof(Raphael) == 'undefined') { return new MVBarchart(this, x, y, width, height, values, opts); }; - MVBarchart.prototype.rightAxis = function (x, y, length, max, steps, asixywidth, barsteps, paper) + MVBarchart.prototype.rightAxis = function (x, y, length, max, steps, axisYWidth, paper, axisLineColor, axisFont, axisFontSize, axisFontWeight, axisFontColor) { // Roo.log('Right Axis'); -// Roo.log([x, y, length, max, steps, asixywidth]); +// Roo.log([x, y, length, max, steps, axisYWidth]); var path = [], - color = "#bababa", text = paper.set(), d = Math.ceil(max / steps); @@ -222,14 +296,15 @@ if (typeof(Raphael) == 'undefined') { for(var i = 0; i <= steps; i++) { if(i != 0) { - paper.text(x + (asixywidth / 2), Y + 15, label).attr({ - "font-size": (barsteps > 10) ? "12" : "14", - "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", - "font-weight": "bold", + // 6 pixels away from bottom of the line + paper.text(x + (axisYWidth / 2), Y + axisFontSize / 2 + 6, label).attr({ + "font-size": axisFontSize, + "font-family": axisFont, + "font-weight": axisFontWeight, "text-anchor": "end", - fill : color + fill : axisFontColor }); - path = path.concat(["M", x, Y, "l", (asixywidth / 2), 0]); + path = path.concat(["M", x , Y, "l", axisYWidth / 2, 0]); } @@ -237,7 +312,7 @@ if (typeof(Raphael) == 'undefined') { Y -= dx; } - var res = paper.path(path).attr({ stroke: color, "stroke-width": 2 }); + var res = paper.path(path).attr({ stroke: axisLineColor, "stroke-width": 2 }); res.text = text; res.all = paper.set([res, text]); @@ -249,26 +324,28 @@ if (typeof(Raphael) == 'undefined') { return res; } - MVBarchart.prototype.bottomAxis = function (x, y, length, steps, labels, barwidth, eheight, paper) + MVBarchart.prototype.bottomAxis = function (x, y, length, labels, barsteps, barWidth, barGutter, height, paper, axisLineColor, axisFont, axisFontSize, axisFontWeight, axisFontColor) { // Roo.log('Bottom Axis'); -// Roo.log([x, y, length, steps, labels, barwidth]); +// Roo.log([x, y, length, steps, labels, barWidth]); var path = ["M", x, y, "l", length, 0], - color = "#bababa", text = paper.set(), - offset = Math.round(barwidth / 2); + offset = Math.round(barWidth / 2); labels.forEach(function(v,k) { - paper.text(x + (k * barwidth) + offset, y + (eheight / 2), v).attr({ - "font-size": (steps > 10) ? "12" : "14", - "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", - "font-weight": "bold", - fill : color + if(k >= barsteps) { + return; + } + paper.text(x + (k * (barWidth + barGutter)) + offset, y + (height / 2), v).attr({ + "font-size": axisFontSize, + "font-family": axisFont, + "font-weight": axisFontWeight, + fill : axisFontColor }); }); - var res = paper.path(path).attr({ stroke: color, "stroke-width": 2 }); + var res = paper.path(path).attr({ stroke: axisLineColor, "stroke-width": 2 }); res.text = text; res.all = paper.set([res, text]); diff --git a/g.bar.split.js b/g.bar.split.js index e665971..4084984 100644 --- a/g.bar.split.js +++ b/g.bar.split.js @@ -19,9 +19,26 @@ Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); * @param {int} r - radius * @param {Array} values * @param {Object} opts options - * cut : after this meany items - do not show a pie element? - * - * + * top (number) : top padding + * bottom (number) : bottom padding + * left (number) : left padding + * right (number) : right padding + * background (string) : background color + * borderstlye (string) : border style of empty bars + * bordercolor (string): border color of empty bars + * barwidth (number) : width of the bars + * color (string) : color of the bars + * indicatorwidth (number): width of indicator + * indicatorheight (number): height of indicator + * indicatorcolor (string): color of indicator + * labelfont (string) : font family of labels + * labelsize (number) : font size of labels + * labelweight (string)(number) : font weight of labels + * labelcolor (string) : font color of the labels + * legendfont (string) : font family of the legend labels + * legendfontsize (number) : font size of the legend labels + * legendfontweight (string)(number) : font weight of the legend labels + * legendfontcolor (string) : font color of the legend colors * */ @@ -30,47 +47,60 @@ Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); opts = opts || {}; var chartinst = this, - chart = paper.set(); - - opts.top = opts.top || 50; - opts.bottom = opts.bottom || 50; - opts.left = opts.left || 20; - opts.right = opts.right || 20; - opts.barwidth = opts.barwidth || 100 - opts.color = opts.color || '#0A2BC4'; + chart = paper.set(), + top = opts.top || 50, + bottom = opts.bottom || 50, + left = opts.left || 20, + right = opts.right || 20, + borderColor = opts.bordercolor || '#CCCCCC', + borderStyle = opts.borderstyle || '--', + barWidth = opts.barwidth || 100, + Color = opts.color || '#0A2BC4', + indicatorWidth = opts.indicatorwidth || barWidth / 2, + indicatorHeight = opts.indicatorheight || 30, + indicatorColor = opts.indicatorcolor || '#0C014D', + labelFont = opts.labelfont || "'Fontin Sans', Fontin-Sans, sans-serif", + labelSize = opts.labelsize || 16, + labelWeight = opts.labelweight || 'bold', + labelColor = opts.labelcolor || '#FFFFFF', + legendFont = opts.legendfont || "'Fontin Sans', Fontin-Sans, sans-serif", + legendFontSize = opts.legendfontsize || 22, + legendFontWeight = opts.legendfontweight || 'bold', + legendFontColor = opts.legendfontcolor || '#0C024B'; paper.rect(0, 0, width, height).attr({ stroke: "none", fill: (opts.background || "#F0F4F7") }); - var cw = width - opts.left - opts.right, - ch = height - opts.top - opts.bottom; + var cw = width - left - right, + ch = height - top - bottom; - paper.rect(opts.left, opts.top, opts.barwidth, ch).attr({ stroke: "#CCCCCC", "stroke-dasharray": "--" }); - paper.rect(opts.left + cw / 2, opts.top, opts.barwidth, ch).attr({ stroke: "#CCCCCC", "stroke-dasharray": "--" }); + paper.rect(left, top, barWidth, ch).attr({ stroke: borderColor, "stroke-dasharray": borderStyle }); + paper.rect(left + cw / 2, top, barWidth, ch).attr({ stroke: borderColor, "stroke-dasharray": borderStyle }); values.forEach(function(v,k) { v = Math.min(100, v); var bh = v * ch / 100, - bx = (k == 0) ? opts.left : opts.left + cw / 2, - by = opts.top + ch - bh; - - paper.rect(bx, by, opts.barwidth, bh).attr({ stroke: "none", fill: opts.color }); + bx = (k == 0) ? left : left + cw / 2, + by = top + ch - bh; - paper.rect(bx + opts.barwidth / 4 , by - 42, opts.barwidth / 2, 30, 5).attr({ stroke: "none", fill: "#0C014D" }); - paper.path(["M", bx + opts.barwidth / 4 + 10, by - 12, "l", 5, 10, "l", 5, -10]).attr({ stroke: "none", fill: "#0C014D" }); + paper.rect(bx, by, barWidth, bh).attr({ stroke: "none", fill: Color }); + + paper.rect(bx + (barWidth - indicatorWidth) / 2 , by - 20 - indicatorHeight, indicatorWidth, indicatorHeight, 5).attr({ stroke: "none", fill: indicatorColor }); + paper.path(["M", bx + (barWidth - indicatorWidth) / 2 + (indicatorWidth - 10) / 2, by - 20, "l", 5, 10, "l", 5, -10]).attr({ stroke: "none", fill: indicatorColor }); - paper.text(bx + opts.barwidth / 4 + 25, by - 28, v + '%').attr({ - "font-size": "16", - "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", - "font-weight": "bold", - fill : "#fff" + paper.text(bx + barWidth / 2, by - 20 - indicatorHeight / 2, v + '%').attr({ + "font-size": labelSize, + "font-family": labelFont, + "font-weight": labelWeight, + "text-anchor": "middle", + fill : labelColor }); - paper.text(bx + opts.barwidth + 25, opts.top + 50, opts.legend[k]).attr({ - "font-size": "22", - "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", - "font-weight": "bold", + paper.text(bx + barWidth + 25, top + 50, opts.legend[k]).attr({ + "font-size": legendFontSize, + "font-family": legendFont, + "font-weight": legendFontWeight, "text-anchor": "start", - fill : "#0C024B" + fill : legendFontColor }); }); diff --git a/g.pie.circular.js b/g.pie.circular.js index a3ac8d3..9190dc8 100644 --- a/g.pie.circular.js +++ b/g.pie.circular.js @@ -19,9 +19,19 @@ Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); * @param {int} r - radius * @param {Array} values * @param {Object} opts options - * cut : after this meany items - do not show a pie element? - * - * + * background (string) : background color + * colors (array) : colors of the pies + * borderwidth (number) : width of circle border + * labelfont (string) : font family of labels + * labelsize (number) : font size of labels + * labelweight (string) (number) : font weight of labels + * labelcolor (string) : font color of the labels + * linewidth (number) : width of the lines connecting the legend and the circles + * lineheight (number) : height of the lines connecting the legend and the circles + * legend (array) : legend + * legendfont (string) : font family of the legend labels + * legendfontsize (number) : font size of the legend labels + * legendfontcolor (string) : font color of the legend colors * */ @@ -32,8 +42,17 @@ Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); var chartinst = this, chart = paper.set(), len = values.length; - - opts.linewidth = opts.linewidth || 200; + borderWidth = typeof(opts.borderwidth) != 'undefined' ? opts.borderwidth : 2, // width can be 0 (no border) + labelFont = opts.labelfont || "'Fontin Sans', Fontin-Sans, sans-serif", + labelSize = opts.labelsize || 18, + labelWeight = opts.labelweight || 'bold', + labelColor = opts.labelcolor || '#FFFFFF', + lineWidth = opts.linewidth || 200, + lineHeight = opts.lineheight || 2, + lineColor = opts.linecolor || '#FFFFFF', + legendFont = opts.legendfont || "'Fontin Sans', Fontin-Sans, sans-serif", + legendFontSize = opts.legendfontsize || 18, + legendFontColor = opts.legendfontcolor || '#0C024B'; paper.rect(0, 0, width, height).attr({ stroke: "none", fill: (opts.background || "#F0F4F7") }); @@ -49,14 +68,17 @@ Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); return b.value - a.value; }); + // dx, dy: position of the center of the circle + // dr: radius of the circle var dx = cx, dy = cy, dr = r; for (i = 0; i < len; i++) { - paper.circle(dx, dy, dr).attr({ fill: opts.colors && opts.colors[i] || chartinst.colors[i] || "#3E66BC", stroke: "#fff", "stroke-width": 2 }); + paper.circle(dx, dy, dr).attr({ fill: opts.colors && opts.colors[i] || chartinst.colors[i] || "#3E66BC", stroke: "#fff", "stroke-width": borderWidth }); + // nx, ny: position of the label var nx = dx, ny = dy - dr + 20; @@ -65,18 +87,20 @@ Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); } paper.text(nx, ny, Roo.util.Format.number(values[i], 0)).attr({ - "font-size": "18", - "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", - "font-weight": "bold", - fill : "#fff" + "font-size": labelSize, + "font-family": labelFont, + "font-weight": labelWeight, + "text-anchor": "middle", + fill : labelColor }); - paper.path(["M", dx, dy - dr, "l", opts.linewidth, 0]).attr({ stroke: "#fff", "stroke-width": 2 }); - paper.text(dx + opts.linewidth + 10, dy - dr, opts.legend[values[i].origin]).attr({ - "font-size": "18", - "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", + paper.path(["M", dx, dy - dr, "l", lineWidth, 0]).attr({ stroke: lineColor, "stroke-width": lineHeight }); + + paper.text(dx + lineWidth + 10, dy - dr, opts.legend[values[i].origin]).attr({ + "font-size": legendFontSize, + "font-family": legendFont, "text-anchor": "start", - fill : "#0C024B" + fill : legendFontColor }); dy = dy + dr / 4 *3; diff --git a/g.pie.sector.js b/g.pie.sector.js index 9f7deb7..ceca7d8 100644 --- a/g.pie.sector.js +++ b/g.pie.sector.js @@ -19,10 +19,27 @@ Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); * @param {int} r - radius * @param {Array} values * @param {Object} opts options - * cut : after this meany items - do not show a pie element? - * - * - * + * background (string) : background color + * start_angle (number) : the angle of the starting position of the first pie + * barwidth (number) : width of a pie + * colors (array) : colors of the pies + * cut (number) : after showing this number of elements using this number of pies, merge and show the rest of the elements using one pie (maximum 'cut' + 1 pies in total) + * others (string) : legend label labelling the pie for the merged elements (*required if there are merged elements) + * no_sort (boolean) : sort the values in descending order if it is not set + * labels (array) : labels on the pie + * labelfont (string) : font family of the labels + * labelsize (number) : font size of the labels + * labelweight (string)(number) : font weight of the labels + * labelcolor (string) : font color of the labels + * legend (array) : legend + * legendpos (string) : position of the legend ('right' / 'bottom') + * legendkeyshape (string) : shape of the legend keys ('circle' / 'rect') + * legendkeysize (number) : size of the legend keys (diameter for 'circle' and width for 'rect') + * legendfont (string) : font family of the legend labels + * legendfontsize (number) : font size of the legend labels + * legendfontcolor (string) : font color of the legend colors + * lineheight (number) : distance between two legend labels + * legendcolumn (number) : number of columns used to show legend (1 / 2) */ function Piesectorchart(paper, width, height, cx, cy, r, values, opts) { @@ -32,12 +49,15 @@ Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); var chartinst = this, chart = paper.set(), len = values.length, - angle = opts.start_angle || 90, total = 0, - others = 0, + angle = opts.start_angle || 90, cut = opts.cut || 9, - defcut = true - lineheight = opts.lineheight || 30; + defcut = true, + labels = opts.labels || false; // default no labels + labelFont = opts.labelfont || "'Fontin Sans', Fontin-Sans, sans-serif", + labelSize = opts.labelsize || 18, + labelWeight = opts.labelweight || 'normal', + labelColor = opts.labelcolor || '#FFFFFF'; opts.barwidth = opts.barwidth || 80; @@ -56,22 +76,22 @@ Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); return {path: path, stroke: color}; }; + + for (var i = 0; i < len; i++) { + total += values[i] * 1; + values[i] = { + value: values[i], + origin: i, + valueOf: function () { return this.value; } + }; + } if (len == 1) { - total = values[0]; paper.circle(cx, cy, r + opts.barwidth / 2).attr({ fill: opts.colors && opts.colors[0] || chartinst.colors[0] || "#3E66BC", "stroke": "#fff" }); paper.circle(cx, cy, r - opts.barwidth / 2).attr({ fill: opts.background || "#F0F4F7", "stroke": "#fff" }); } else { - for (var i = 0; i < len; i++) { - total += values[i] * 1; - values[i] = { - value: values[i], - valueOf: function () { return this.value; } - }; - } - if (!opts.no_sort) { values.sort(function (a, b) { return b.value - a.value; @@ -79,6 +99,7 @@ Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); } for (i = 0; i < len; i++) { + // minimum degree of a pie shown if (defcut && values[i] * 360 / total <= 1.5) { cut = i; defcut = false; @@ -92,52 +113,138 @@ Roo = typeof(Roo) != 'undefined' ? Roo: (imports ? imports.seed.Roo.Roo: {}); } len = Math.min(cut + 1, values.length); + + var a = angle; for (i = 0; i < len; i++) { - - var p = paper.path().attr({ + paper.path().attr({ "stroke": "#fff", "stroke-width": opts.barwidth - }).attr({sector: [cx, cy, angle, angle -= 360 * values[i] / total, opts.colors && opts.colors[i] || chartinst.colors[i], r]}); - + }).attr({sector: [cx, cy, a, a -= 360 * values[i] / total, opts.colors && opts.colors[i] || chartinst.colors[i], r]}); } } + + // labels + var rad = Math.PI / 180; + + var a = angle; + + for (i = 0; i < len; i++) { + + a -= 360 * values[i] / total; + + // show the label only if the values >= 5% of total + if(labels && values[i] / total >= 0.05) { + var text = labels[i]; + + if(text.indexOf('#qty#') !== -1) { + text = text.replace('#qty#', Math.round(values[i])); + } + + if(text.indexOf('#%#') !== -1) { + text = text.replace('#%#', Math.round(values[i] / total * 100) + '%'); + } + + var tx = cx + r * Math.cos(-(a + 180 * values[i] / total) * rad), + ty = cy + r * Math.sin(-(a + 180 * values[i] / total) * rad); + + paper.text(tx, ty, text).attr({ + "font-size": labelSize, + "font-family": labelFont, + "font-weight" : labelWeight, + "text-anchor": "middle", + fill : labelColor + }); + } + } + - var ix = cx + r + opts.barwidth / 2 + 30, + legend(paper, cx, cy, r, values, opts, total, len); + + chart.cx = cx; + chart.cy = cy; + chart.r = r; + return chart; + } + + // draw legend + function legend(paper, cx, cy, r, values, opts, total, len) + { + var legendPos = opts.legendpos || 'right', + legendKeyShape = opts.legendkeyshape || 'circle', + legendKeySize = opts.legendkeysize || 12, + legendFont = opts.legendfont || "'Fontin Sans', Fontin-Sans, sans-serif", + legendFontSize = opts.legendfontsize || 18, + legendFontColor = opts.legendfontcolor || '#0C014F', + lineHeight = opts.lineheight || 30, + legendColumn = opts.legendcolumn || 1; + + // default 'legendPos' is 'right' + // ix, iy: center position of legend key + var ix = cx + r + opts.barwidth / 2 + 30, // 30 pixels away from right of the chart iy = cy - r - 30; - + + if(legendPos == 'bottom') { + ix = cx - r - opts.barwidth / 2 - 30; // 30 pixels away from left of the chart + iy = cy + r + opts.barwidth / 2 + 30; // 30 pixels away from bottom of the chart + + // default 'legendColumn' is 1 + if(legendColumn == 2) { + ix = cx - r - opts.barwidth / 2 - 90; // 90 pixels away from left of the chart + } + } + for (var i = 0; i < len; i++) { - paper.circle(ix, iy, 6).attr({ fill: opts.colors && opts.colors[i] || chartinst.colors[i] || "#3E66BC", "stroke": "#fff" }); - - var text = (values[i].others) ? opts.others : opts.legend[i] || values[i]; + if(legendKeyShape == 'rect') { + // pass top left position for 'rect' + paper.rect(ix - (legendKeySize / 2), iy - (legendKeySize / 2), legendKeySize, legendKeySize, 0).attr({ fill: opts.colors && opts.colors[i] || chartinst.colors[i] || "#3E66BC", "stroke": "#fff" }); + } + else { + // pass center position for 'circle' + paper.circle(ix, iy, legendKeySize / 2).attr({ fill: opts.colors && opts.colors[i] || chartinst.colors[i] || "#3E66BC", "stroke": "#fff" }); + } + + var text = (values[i].others) ? opts.others : opts.legend[values[i].origin] || values[i]; if(text.indexOf('#qty#') !== -1) { text = text.replace('#qty#', Math.round(values[i])); } - + if(text.indexOf('#%#') !== -1) { text = text.replace('#%#', Math.round(values[i] / total * 100) + '%'); } + + var ty = iy - legendFontSize / 10; + + if(text.includes("\n")) { + var ty = iy - legendFontSize / 10 + legendFontSize / 2; + } - paper.text(ix + 20, iy, text).attr({ - "font-size": "18", - "font-family": "'Fontin Sans', Fontin-Sans, sans-serif", + // 12 pixels away from the right of legend key + // align legend key and text horizontally + paper.text(ix + legendKeySize / 2 + 12, ty, text).attr({ + "font-size": legendFontSize, + "font-family": legendFont, "text-anchor": "start", - fill : "#0C014F" + fill : legendFontColor }); - - iy += lineheight; - + + if(legendColumn == 2) { + if(i % 2 == 0) { + ix += r + opts.barwidth / 2 + 120; + iy -= lineHeight; + } + else { + ix -= r + opts.barwidth / 2 + 120; + } + } + + iy += lineHeight; } + } - chart.cx = cx; - chart.cy = cy; - chart.r = r; - return chart; - }; - //inheritance var F = function() {}; F.prototype = Raphael.g; diff --git a/seed/toSVG.js b/seed/toSVG.js index b95cbd7..dce97c3 100644 --- a/seed/toSVG.js +++ b/seed/toSVG.js @@ -108,7 +108,8 @@ function extractStyle(node) { font: { family: typeof node.attrs['font-family'] === 'undefined' ? null : node.attrs['font-family'], size: typeof node.attrs['font-size'] === 'undefined' ? null : (node.attrs['font-size']), - anchor: typeof node.attrs['text-anchor'] === 'undefined' ? null : node.attrs['text-anchor'], + weight: typeof node.attrs['font-weight'] === 'undefined' ? null : (node.attrs['font-weight']), + anchor: typeof node.attrs['text-anchor'] === 'undefined' ? null : node.attrs['text-anchor'] } }; } @@ -122,7 +123,6 @@ function styleToString(style) { //Roo.log(JSON.stringify(style)); var r = [ 'font-family:' + style.font.family, - 'font-weight:normal', 'font-style:normal', 'font-stretch:normal', 'font-variant:normal' @@ -130,6 +130,12 @@ function styleToString(style) { if (style.font.size !== null) { r.push('font-size: ' + style.font.size + 'px') } + if (style.font.weight !== null) { + r.push('font-weight: ' + style.font.weight); + } + else { + r.push('font-weight: normal'); + } return r.join(';') diff --git a/test/bartest.html b/test/bartest.html index a90bb64..bfee2b2 100644 --- a/test/bartest.html +++ b/test/bartest.html @@ -3,40 +3,91 @@ gRaphaël Dynamic Bar Chart - - + + + + + + -
+

Demo of gRaphaël JavaScript library.

-- 2.39.2