1 /**
  2  * Constructs a Raphael axis renderer function.
  3  *
  4  * @return {D3RaphaelAxis}
  5  *
  6  * @see <a href="https://github.com/mbostock/d3/wiki/SVG-Axes">d3.svg.axis</a>
  7  */
  8 d3.raphael.axis = function() {
  9     var scale = d3.scale.linear(),
 10         orient = "bottom",
 11         tickMajorSize = 6,
 12         tickMinorSize = 6,
 13         tickEndSize = 6,
 14         tickPadding = 3,
 15         tickArguments_ = [10],
 16         tickValues = null,
 17         tickFormat_,
 18         tickSubdivide = 0;
 19 
 20     // todo: work-around because we don't have groups
 21     var top = 0,
 22         left = 0;
 23 
 24     // todo: work-around because we don't have stylesheet
 25     var classPrefix = "";
 26 
 27     // todo: figure out if we can refactor to reuse code
 28 
 29     function axis(selection) {
 30 
 31         selection.each(function() {
 32             var g = selection.root.select("");
 33 
 34             // Ticks, or domain values for ordinal scales.
 35             var ticks = tickValues == null ? (scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain()) : tickValues,
 36                 tickFormat = tickFormat_ == null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String) : tickFormat_;
 37 
 38             // Major ticks.
 39             var tick = g.selectAll("g").data(ticks, String),
 40                 tickEnter = tick.enter().append("path")
 41                     .classed(classPrefix + "path", true)
 42 //                tickEnter = tick.enter().insert("g", "path").style("opacity", 1e-6),
 43 //                tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(),
 44 //                tickUpdate = d3.transition(tick).style("opacity", 1),
 45 //                tickTransform;
 46 
 47             var text = tick.append("text")
 48                 .attr("text", tickFormat );
 49 
 50             // Domain.
 51             var range = d3_scaleRange(scale),
 52                 path = g.selectAll(".domain").data([0]),
 53                 pathEnter = path.enter().append("path")
 54                     .classed(classPrefix + "pathdomain", true)
 55 //                pathEnter = path.enter().append("path").attr("class", "domain")
 56 //                pathUpdate = d3.transition(path);
 57 
 58             // Stash a snapshot of the new scale, and retrieve the old snapshot.
 59             var scale1 = scale.copy(),
 60                 scale0 = this.__chart__ || scale1;
 61             this.__chart__ = scale1;
 62 
 63             switch (orient) {
 64                 case "top": {
 65                     tick.attr("path", function(d) { return d3_raphael_pathArrayToString([["M", [left + scale1(d), top]],["l", [0, -tickMajorSize]]]); });
 66                     text.attr("x", function(d) { return scale1(d) + left + (scale1.rangeBand? scale1.rangeBand() / 2.0 : 0); })
 67                         .attr("y", top- 7 ) // todo add dy support to raphael
 68                         .attr("text-anchor", "middle")
 69 //                    path.attr("path", "M" + (-tickEndSize + left) + "," + (range[0] + top) + "h" + tickEndSize + "v" + (range[1] + top) + "h" + -tickEndSize)
 70                     path.attr("path", "M" + (range[0] + left) + "," + (-tickEndSize + top) + "v" + tickEndSize + "H" + (range[1] + left) + "v" + -tickEndSize)
 71 
 72                     break;
 73                 }
 74 
 75                 case "bottom": {
 76                     tick.attr("path", function(d) { return d3_raphael_pathArrayToString([["M", [left + scale1(d), top]],["l", [0, tickMajorSize]]]); });
 77                     text.attr("x", function(d) { return scale1(d) + left + (scale1.rangeBand? scale1.rangeBand() / 2.0 : 0); })
 78                         .attr("y", top + tickMajorSize + 7 ) // todo add dy support to raphael
 79                         .attr("text-anchor", "middle")
 80 //                    path.attr("path", "M" + (-tickEndSize + left) + "," + (range[0] + top) + "h" + tickEndSize + "v" + (range[1] + top) + "h" + -tickEndSize)
 81                     path.attr("path", "M" + (range[0] + left) + "," + (tickEndSize + top) + "v" + -tickEndSize + "H" + (range[1] + left) + "v" + tickEndSize)
 82 
 83                     break;
 84                 }
 85 
 86 
 87                 case "left": {
 88                     tick.attr("path", function(d) { return d3_raphael_pathArrayToString([["M", [left, scale1(d) + top]],["l", [-tickMajorSize,0]]]); });
 89                     path.attr("path", "M" + (-tickEndSize + left) + "," + (range[0] + top) + "h" + tickEndSize + "V" + (range[1] + top) + "h" + -tickEndSize)
 90                     text.attr("x", left - 5)
 91                         .attr("y", function(d) { return scale1(d) + top + (scale1.rangeBand? scale1.rangeBand() / 2.0 : 0); })
 92                         .attr("text-anchor", "end")
 93 
 94                     break;
 95                 }
 96 
 97                 default: {
 98                     throw "Unsupported " + orient;
 99                 }
100             }
101 
102 //            // For quantitative scales:
103 //            // - enter new ticks from the old scale
104 //            // - exit old ticks to the new scale
105 //            if (scale.ticks) {
106 //                tickEnter.call(tickTransform, scale0);
107 //                tickUpdate.call(tickTransform, scale1);
108 //                tickExit.call(tickTransform, scale1);
109 //                subtickEnter.call(tickTransform, scale0);
110 //                subtickUpdate.call(tickTransform, scale1);
111 //                subtickExit.call(tickTransform, scale1);
112 //            }
113 //
114 //            // For ordinal scales:
115 //            // - any entering ticks are undefined in the old scale
116 //            // - any exiting ticks are undefined in the new scale
117 //            // Therefore, we only need to transition updating ticks.
118 //            else {
119 //                var dx = scale1.rangeBand() / 2, x = function(d) { return scale1(d) + dx; };
120 //                tickEnter.call(tickTransform, x);
121 //                tickUpdate.call(tickTransform, x);
122 //            }
123 
124 
125 
126 
127 //            tick.attr("path", function(d) { return d3_raphael_pathArrayToString(
128 //                [["M", [left, scale(d) + top]],["l", [-6,0]]]
129 //            ); });
130 //
131 //            tick.append("text")
132 //                .attr("x", left - 2)
133 //                .attr("y", function(d) { return scale(d) + top + scale.rangeBand() / 2; })
134 //                .attr("text-anchor", "end")
135 //                .attr("text", function(d) { return d;} )
136 //
137 //            var range = d3_scaleRange(scale);
138 //            console.log(range);
139 //            g.select("rect")
140 //                .append("path")
141 //                .attr("path", d3_raphael_pathArrayToString([["M", [left, range[0] + top]],["L",[left, range[1] + top]]]))
142         })
143     }
144 
145     /**
146      * Get or set the associated scale. If scale is specified, sets the scale and returns the axis. If scale is not specified, returns the current scale which defaults to a linear scale.
147      *
148      * @param {d3.Scale} x scale
149      * @return {D3RaphaelAxis} this
150      *
151      * @see <code><a href="https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-axis_scale">d3.svg.axis().scale()</a></code>
152      *
153      * @function
154      * @name D3RaphaelAxis#scale
155      */
156     axis.scale = function(x) {
157         if (!arguments.length) return scale;
158         scale = x;
159         return axis;
160     };
161 
162     /**
163      * Get or set the axis orientation. If orientation is not specified, returns the current orientation, which defaults to "bottom".
164      *
165      * @param {String} x orientation, one of top, bottom, or left.  NOTE: right currently unsupported.  top/bottom for horizontal axis, and left for vertical.
166      * @return {D3RaphaelAxis} this
167      *
168      * @see <code><a href="https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-axis_orient">d3.svg.axis().orient()</a></code>
169      *
170      * @function
171      * @name D3RaphaelAxis#orient
172      */
173     axis.orient = function(x) {
174         if (!arguments.length) return orient;
175         orient = x;
176         return axis;
177     };
178 
179     /**
180      * Get or set the size of major, minor and end ticks.
181      *
182      * @param {Number} x major tick size
183      * @param {Number} y minor tick size (optional)
184      * @param {Number} z end tick size (optional)
185      * @return {D3RaphaelAxis} this
186      *
187      * @see <code><a href="https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-axis_tickSize">d3.svg.axis().tickSize()</a></code>
188      *
189      * @function
190      * @name D3RaphaelAxis#tickSize
191      */
192     axis.tickSize = function(x, y, z) {
193         if (!arguments.length) return tickMajorSize;
194         var n = arguments.length - 1;
195         tickMajorSize = +x;
196         tickMinorSize = n > 1 ? +y : tickMajorSize;
197         tickEndSize = n > 0 ? +arguments[n] : tickMajorSize;
198         return axis;
199     };
200 
201     /**
202      * Get or set the top offset for axis rendering.  This is a work-around for the fact Raphael doesn't have a group element.
203      *
204      * @param {Number} val top offset, in pixels
205      * @return {D3RaphaelAxis} this
206      *
207      * @function
208      * @name D3RaphaelAxis#top
209      */
210     axis.top = function(val) {
211         if(typeof val === "undefined")
212             return top;
213         else
214             top = val;
215 
216         return this;
217     }
218 
219     /**
220      * Get or set the left offset for the axis rendering.  This is a work-around for the fact Raphael doesn't have a group element.
221      *
222      * @param {Number} val left offset, in pixels
223      * @return {D3RaphaelAxis} this
224      *
225      * @function
226      * @name D3RaphaelAxis#left
227      */
228     axis.left = function(val) {
229         if(typeof val === "undefined")
230             return left;
231         else
232             left = val;
233 
234         return this;
235     }
236 
237     /**
238      * Get or set the class name prefix appended to the class names used to differentiate parts of the rendered axis.<br />
239      * <br />
240      * Class name suffixes used internally are:
241      * <dl>
242      *     <dt>path</dt>
243      *     <dd>axis ticks</dd>
244      *     <dt>pathdomain</dt>
245      *     <dd>axis domain line (and end-ticks)</dd>
246      * </dl>
247      *
248      * So, for example, if you specified a class name prefix <code>xaxis_</code>, you would want to specify CSS selectors, <code>.xaxis_path</code> and <code>.xaxis_pathdomain</code>
249      *
250      * @param {String} val
251      * @return {D3RaphaelAxis} this
252      *
253      * @function
254      * @name D3RaphaelAxis#classPrefix
255      */
256     axis.classPrefix = function(val) {
257         if(typeof val === "undefined")
258             return classPrefix;
259         else
260             classPrefix = val;
261 
262         return this;
263     }
264 
265     return axis;
266 };