diff --git a/source/base.js b/source/base.js index 0d48a19..a57123c 100644 --- a/source/base.js +++ b/source/base.js @@ -79,6 +79,7 @@ Bluff.Base = new JS.Class({ DATA_LABEL_INDEX: 0, DATA_VALUES_INDEX: 1, DATA_COLOR_INDEX: 2, + DATA_DASH_STYLE_INDEX: 3, // Space around text elements. Mostly used for vertical spacing LEGEND_MARGIN: 20, @@ -138,7 +139,10 @@ Bluff.Base = new JS.Class({ // Get or set the list of colors that will be used to draw the bars or lines. colors: null, - + + // Get or set the list of dash styles that will be used to draw lines. + dash_styles : null, + // The large title of the graph displayed at the top title: null, @@ -152,6 +156,9 @@ Bluff.Base = new JS.Class({ // Prevent drawing of the legend hide_legend: null, + + // Draws lines in legend instead of boxes (e.g. when dashing is used) + line_legend: null, // Prevent drawing of the title hide_title: null, @@ -328,6 +335,39 @@ Bluff.Base = new JS.Class({ this.colors = color_list || []; this._color_index = 0; }, + + // Add a dash style to the list of available dash styles for lines. + // A dash style is specified by an array like [x, y, z, ...] meaning + // Draw x pixels, then leave y pixels free, then draw z pixels etc. + // It repeats from the beginning if the line is longer than the sum of the length + // of the specified dashes + // + // Example: + // add_dash_style([3, 1]) + add_dash_style: function(dash_style) { + this.dash_styles.push(dash_style); + }, + + // Replace the entire dash style list with a new array of dash styles. Also + // aliased as the dash_styles= setter method. + // + // If you specify fewer colors than the number of datasets you intend + // to draw, 'increment_dash_style' will cycle through the array, reusing + // dash styles as needed. + // + // Note that (as with the 'set_theme' method), you should set up the dash style + // list before you send your data (via the 'data' method). Calls to the + // 'data' method made prior to this call will use whatever dash style scheme + // was in place at the time data was called. + // + // Example: + // replace_dash_styles [[9999], [10, 5], [6, 3, 20, 10]] + // The [9999] style means a solid line, since most drawn lines will be most likely + // shorter than 9999 pixels + replace_dash_styles: function(dash_style_list) { + this.dash_styles = dash_style_list || []; + this._dash_style_index = 0; + }, // You can set a theme manually. Assign a hash to this method before you // send your data. @@ -335,7 +375,8 @@ Bluff.Base = new JS.Class({ // graph.set_theme({ // colors: ['orange', 'purple', 'green', 'white', 'red'], // marker_color: 'blue', - // background_colors: ['black', 'grey'] + // background_colors: ['black', 'grey'], + // dash_styles: [[9999], [10,5]] // }) // // background_image: 'squirrel.png' is also possible. @@ -351,11 +392,13 @@ Bluff.Base = new JS.Class({ marker_color: 'white', font_color: 'black', background_colors: null, - background_image: null + background_image: null, + dash_styles : [[9999]] }; for (var key in options) this._theme_options[key] = options[key]; this.colors = this._theme_options.colors; + this.dash_styles = this._theme_options.dash_styles; this.marker_color = this._theme_options.marker_color; this.font_color = this._theme_options.font_color || this.marker_color; this._additional_line_colors = this._theme_options.additional_line_colors; @@ -501,19 +544,20 @@ Bluff.Base = new JS.Class({ // graph. // // If the color argument is nil, the next color from the default theme will - // be used. + // be used, same counts for the dash_style argument. // // NOTE: If you want to use a preset theme, you must set it before calling // data(). // // Example: - // data("Bart S.", [95, 45, 78, 89, 88, 76], '#ffcc00') - data: function(name, data_points, color) { + // data("Bart S.", [95, 45, 78, 89, 88, 76], '#ffcc00', [9999]) + data: function(name, data_points, color, dash_style) { data_points = (data_points === undefined) ? [] : data_points; color = color || null; - + dash_style = dash_style || null; + data_points = Bluff.array(data_points); // make sure it's an array - this._data.push([name, data_points, (color || this._increment_color())]); + this._data.push([name, data_points, (color || this._increment_color()), (dash_style || this._increment_dash_style())]); // Set column count if this is larger than previous counts this._column_count = (data_points.length > this._column_count) ? data_points.length : this._column_count; @@ -603,7 +647,7 @@ Bluff.Base = new JS.Class({ else norm_data_points.push((data_point - this.minimum_value) / this._spread); }, this); - this._norm_data.push([data_row[this.klass.DATA_LABEL_INDEX], norm_data_points, data_row[this.klass.DATA_COLOR_INDEX]]); + this._norm_data.push([data_row[this.klass.DATA_LABEL_INDEX], norm_data_points, data_row[this.klass.DATA_COLOR_INDEX], data_row[this.klass.DATA_DASH_STYLE_INDEX]]); }, this); } }, @@ -832,10 +876,19 @@ Bluff.Base = new JS.Class({ // Now draw box with color of this dataset this._d.stroke = 'transparent'; this._d.fill = this._data[index][this.klass.DATA_COLOR_INDEX]; - this._d.rectangle(current_x_offset, + if (this.line_legend) { + this._d.stroke = this._data[index][this.klass.DATA_COLOR_INDEX]; + this._d.dashed_line(current_x_offset, + current_y_offset - legend_square_width / 2.0, + current_x_offset + legend_square_width, + current_y_offset + legend_square_width / 2.0, + this._data[index][this.klass.DATA_DASH_STYLE_INDEX]); + } else { + this._d.rectangle(current_x_offset, current_y_offset - legend_square_width / 2.0, current_x_offset + legend_square_width, current_y_offset + legend_square_width / 2.0); + } this._d.pointsize = this.legend_font_size; var metrics = this._d.get_type_metrics(legend_label); @@ -1074,6 +1127,13 @@ Bluff.Base = new JS.Class({ this._color_index = (this._color_index + 1) % this.colors.length; return this.colors[offset]; }, + + // Returns the next dash style in your dash style list. + _increment_dash_style: function() { + var offset = this._dash_style_index; + this._dash_style_index = (this._dash_style_index + 1) % this.dash_styles.length; + return this.dash_styles[offset]; + }, // Return a formatted string representing a number value that should be // printed as a label. diff --git a/source/line.js b/source/line.js index 62f9a6e..1a7151b 100644 --- a/source/line.js +++ b/source/line.js @@ -90,7 +90,7 @@ Bluff.Line = new JS.Class(Bluff.Base, { this._clip_value_if_greater_than(this._columns / (this._norm_data[0][this.klass.DATA_VALUES_INDEX].length * 2), 7.0); if (!this.hide_lines && prev_x !== null && prev_y !== null) { - this._d.line(prev_x, prev_y, new_x, new_y); + this._d.dashed_line(prev_x, prev_y, new_x, new_y, data_row[this.klass.DATA_DASH_STYLE_INDEX]); } else if (this._one_point) { // Show a circle if there's just one point this._d.circle(new_x, new_y, new_x - circle_radius, new_y); diff --git a/source/renderer.js b/source/renderer.js index 8b01a05..2c023e5 100644 --- a/source/renderer.js +++ b/source/renderer.js @@ -145,6 +145,38 @@ Bluff.Renderer = new JS.Class({ this._ctx.lineTo(this._sx * ex, this._sy * ey); this._ctx.stroke(); }, + + dashed_line: function(sx, sy, ex, ey, dashstyle) { + if (!dashstyle) { + dashstyle = [20, 10]; + } + var count = dashstyle.length; + this._ctx.strokeStyle = this.stroke; + this._ctx.lineWidth = this.stroke_width; + this._ctx.beginPath(); + this._ctx.moveTo(this._sx * sx, this._sy * sy); + var dx = (ex - sx), dy = (ey - sy); + var slope = dy / dx; + var rdist = Math.sqrt(dx * dx + dy * dy); + var index = 0, draw = true; + while (rdist >= 0.1) { + var length = dashstyle[index++ % count]; + if (length > rdist) { + length = rdist; + } + var step = Math.sqrt(length * length / (1+slope * slope)); + sx += step; + sy += slope * step; + if (draw) { + this._ctx.lineTo(this._sx * sx, this._sy * sy); + this._ctx.stroke(); + } else { + this._ctx.moveTo(this._sx * sx, this._sy * sy); + } + rdist -= length; + draw = !draw; + } + }, polyline: function(points) { this._ctx.fillStyle = this.fill;