Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

refactor Minimap and rename to Rickshaw.Graph.RangeSlider.Preview; cl…

…oses #273
  • Loading branch information...
commit 6ddfd18eb008a693e5186dfe2bbb3bd8d6ef139b 1 parent 4b52c47
@dchester dchester authored
View
2  Makefile
@@ -36,8 +36,8 @@ JS_FILES=\
src/js/Rickshaw.Graph.HoverDetail.js\
src/js/Rickshaw.Graph.JSONP.js\
src/js/Rickshaw.Graph.Legend.js\
- src/js/Rickshaw.Graph.Minimap.js\
src/js/Rickshaw.Graph.RangeSlider.js\
+ src/js/Rickshaw.Graph.RangeSlider.Preview.js\
src/js/Rickshaw.Graph.Renderer.js\
src/js/Rickshaw.Graph.Renderer.Line.js\
src/js/Rickshaw.Graph.Renderer.Stack.js\
View
4 README.md
@@ -100,9 +100,9 @@ Once you have a basic graph, extensions let you add functionality. See the [ove
* __Rickshaw.Graph.RangeSlider__ - dynamically zoom on the x-axis with a slider
-* __Rickshaw.Graph.Axis.Time__ - add an x-axis and grid lines with time labels
+* __Rickshaw.Graph.RangeSlider.Preview__ - control pan and zoom via graphical preview of entire data set
-* __Rickshaw.Graph.Minimap__ - control pan and zoom via minimap of entire data set
+* __Rickshaw.Graph.Axis.Time__ - add an x-axis and grid lines with time labels
* __Rickshaw.Graph.Axis.X__ - add an x-axis and grid lines with arbitrary labels
View
2  examples/css/extensions.css
@@ -10,7 +10,7 @@ div, span, p, td {
#chart path {
-webkit-transition: opacity 0.2s linear;
}
-#minimap {
+#preview {
margin-top: 10px;
}
#legend {
View
24 examples/extensions.html
@@ -1,10 +1,10 @@
-<!doctype>
+<!doctype html>
<head>
<link type="text/css" rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css">
<link type="text/css" rel="stylesheet" href="../src/css/graph.css">
<link type="text/css" rel="stylesheet" href="../src/css/detail.css">
<link type="text/css" rel="stylesheet" href="../src/css/legend.css">
- <link type="text/css" rel="stylesheet" href="css/extensions.css">
+ <link type="text/css" rel="stylesheet" href="css/extensions.css?v=2">
<script src="../vendor/d3.v3.js"></script>
@@ -20,8 +20,9 @@
<script src="../src/js/Rickshaw.Graph.Renderer.Line.js"></script>
<script src="../src/js/Rickshaw.Graph.Renderer.Bar.js"></script>
<script src="../src/js/Rickshaw.Graph.Renderer.ScatterPlot.js"></script>
+ <script src="../src/js/Rickshaw.Graph.Renderer.Stack.js"></script>
<script src="../src/js/Rickshaw.Graph.RangeSlider.js"></script>
- <script src="../src/js/Rickshaw.Graph.Minimap.js"></script>
+ <script src="../src/js/Rickshaw.Graph.RangeSlider.Preview.js"></script>
<script src="../src/js/Rickshaw.Graph.HoverDetail.js"></script>
<script src="../src/js/Rickshaw.Graph.Annotate.js"></script>
<script src="../src/js/Rickshaw.Graph.Legend.js"></script>
@@ -30,7 +31,6 @@
<script src="../src/js/Rickshaw.Graph.Behavior.Series.Order.js"></script>
<script src="../src/js/Rickshaw.Graph.Behavior.Series.Highlight.js"></script>
<script src="../src/js/Rickshaw.Graph.Smoother.js"></script>
- <script src="../src/js/Rickshaw.Graph.Unstacker.js"></script>
<script src="../src/js/Rickshaw.Fixtures.Time.js"></script>
<script src="../src/js/Rickshaw.Fixtures.Time.Local.js"></script>
<script src="../src/js/Rickshaw.Fixtures.Number.js"></script>
@@ -104,7 +104,7 @@
<div id="chart_container">
<div id="chart"></div>
<div id="timeline"></div>
- <div id="minimap"></div>
+ <div id="preview"></div>
</div>
</div>
@@ -126,7 +126,7 @@
var graph = new Rickshaw.Graph( {
element: document.getElementById("chart"),
- width: 800,
+ width: 900,
height: 500,
renderer: 'area',
stroke: true,
@@ -166,9 +166,9 @@
graph.render();
-var minimap = new Rickshaw.Graph.Minimap( {
+var preview = new Rickshaw.Graph.RangeSlider.Preview( {
graph: graph,
- element: document.getElementById('minimap')
+ element: document.getElementById('preview'),
} );
var hoverDetail = new Rickshaw.Graph.HoverDetail( {
@@ -262,6 +262,14 @@
addAnnotation(true);
setTimeout( function() { setInterval( addAnnotation, 6000 ) }, 6000 );
+var previewXAxis = new Rickshaw.Graph.Axis.Time({
+ graph: preview.previews[0],
+ timeFixture: new Rickshaw.Fixtures.Time.Local(),
+ ticksTreatment: ticksTreatment
+});
+
+previewXAxis.render();
+
</script>
</body>
View
409 src/js/Rickshaw.Graph.Minimap.js
@@ -1,409 +0,0 @@
-Rickshaw.namespace('Rickshaw.Graph.Minimap');
-
-Rickshaw.Graph.Minimap = Rickshaw.Class.create({
-
- initialize: function(args) {
- if (!args.element) throw "Rickshaw.Graph needs a reference to an element";
- if (!args.graph && !args.graphs) throw "Rickshaw.Graph needs a reference to an graph or an array of graphs";
-
- this.element = args.element;
-
- if (args.graph === undefined) {
- this.graphs = args.graphs;
- } else {
- this.graphs = [args.graph];
- }
-
- this.defaults = {
- height: 50,
- width: 800,
- frameTopThickness: 5,
- frameHandleThickness: 40,
- frameColor: "slategray",
- frameOpacity: 0.7
- };
-
- this.configure(args);
- },
-
- configure: function(args) {
- this.config = {};
-
- Rickshaw.keys(this.defaults).forEach(function(k) {
- this.config[k] = k in args ? args[k] : k in this.config ? this.config[k] : this.defaults[k];
- }, this);
-
- this.update();
- },
-
- update: function() {
-
- var mainElement = d3.select(this.element);
-
- var graphsHeight = this.config.height - (this.config.frameTopThickness * 2);
- var individualGraphHeight = graphsHeight / this.graphs.length;
- var graphsWidth = this.config.width - (this.config.frameHandleThickness * 2);
-
- var minimap = this;
-
- var constructGraph = function(datum, index) {
- datum.minimapGraph = {
- container: minimap,
- height: individualGraphHeight,
- width: graphsWidth
- };
-
- var minimapGraphConfiguration = {};
- for (var key in datum.configuration)
- minimapGraphConfiguration[key] = datum.configuration[key];
-
- minimapGraphConfiguration.element = this.appendChild(document.createElement("div"));
- minimapGraphConfiguration.height = datum.minimapGraph.height;
- minimapGraphConfiguration.width = datum.minimapGraph.width;
- minimapGraphConfiguration.series = datum.series;
-
- datum.minimapGraph.graph = new Rickshaw.Graph(minimapGraphConfiguration);
-
- datum.onUpdate(function() {
- datum.minimapGraph.container.update();
- }.bind(this));
-
- datum.onConfigure(function(args) {
- datum.minimapGraph.graph.configure(args);
- }.bind(this));
-
- datum.minimapGraph.graph.render();
- };
-
- var graphContainerBlock = mainElement.selectAll("div.rickshaw_minimap")
- .data(this.graphs);
-
- var translateCommand = "translate(" +
- this.config.frameHandleThickness + "px, " +
- this.config.frameTopThickness + "px)";
-
- graphContainerBlock.enter()
- .append("div")
- .classed("rickshaw_minimap", true)
- .style("transform", translateCommand)
- .style("-webkit-transform", translateCommand)
- .each(constructGraph);
-
- graphContainerBlock.exit()
- .remove();
-
- // Use the first graph as the "master" for the frame state
- var masterGraph = this.graphs[0];
- var domainScale = d3.scale.linear();
- domainScale.interpolate(d3.interpolateRound);
- domainScale.domain([0, graphsWidth]);
- domainScale.range(masterGraph.dataDomain());
- var currentWindow = [masterGraph.window.xMin, masterGraph.window.xMax];
- minimap.currentFrame = [0, graphsWidth];
- for (var i = 0; i < currentWindow.length; i++) {
- if (currentWindow[i] !== undefined) {
- minimap.currentFrame[i] = domainScale.invert(currentWindow[i]);
- }
- minimap.currentFrame[i] = Math.round(minimap.currentFrame[i]);
- }
-
- var registerMouseEvents = function() {
- var drag = {
- target: null,
- start: null,
- stop: null,
- left: false,
- right: false,
- rigid: false
- };
-
- function onMousemove(datum, index) {
- drag.stop = getClientXFromEvent(d3.event);
- var distanceTraveled = drag.stop - drag.start;
- var frameAfterDrag = minimap.frameBeforeDrag.slice(0);
- var minimumFrameWidth = 5;
- if (drag.rigid) {
- minimumFrameWidth = minimap.frameBeforeDrag[1] - minimap.frameBeforeDrag[0];
- }
- if (drag.left) {
- frameAfterDrag[0] += distanceTraveled;
- }
- if (drag.right) {
- frameAfterDrag[1] += distanceTraveled;
- }
- if (frameAfterDrag[0] <= 0) {
- frameAfterDrag[0] = 0;
- }
- if (frameAfterDrag[1] >= graphsWidth) {
- frameAfterDrag[1] = graphsWidth;
- }
- var currentFrameWidth = frameAfterDrag[1] - frameAfterDrag[0];
- if (currentFrameWidth < minimumFrameWidth) {
- if (drag.left) {
- frameAfterDrag[0] = frameAfterDrag[1] - minimumFrameWidth;
- }
- if (drag.right) {
- frameAfterDrag[1] = frameAfterDrag[0] + minimumFrameWidth;
- }
- if (frameAfterDrag[0] <= 0) {
- frameAfterDrag[1] -= frameAfterDrag[0];
- frameAfterDrag[0] = 0;
- }
- if (frameAfterDrag[1] >= graphsWidth) {
- frameAfterDrag[0] -= (frameAfterDrag[1] - graphsWidth);
- frameAfterDrag[1] = graphsWidth;
- }
- }
- minimap.graphs.forEach(function(graph) {
- var domainScale = d3.scale.linear();
- domainScale.interpolate(d3.interpolateRound);
- domainScale.domain([0, graphsWidth]);
- domainScale.range(graph.dataDomain());
- var windowAfterDrag = [
- domainScale(frameAfterDrag[0]),
- domainScale(frameAfterDrag[1])
- ];
- if (frameAfterDrag[0] === 0) {
- windowAfterDrag[0] = undefined;
- }
- if (frameAfterDrag[1] === graphsWidth) {
- windowAfterDrag[1] = undefined;
- }
- graph.window.xMin = windowAfterDrag[0];
- graph.window.xMax = windowAfterDrag[1];
- graph.update();
-
- });
- }
-
- function getClientXFromEvent(event) {
- var clientX;
- switch (event.type) {
- case 'touchstart':
- case 'touchmove':
- var touchList = event.changedTouches;
- var touch = null;
- for (var touchIndex = 0; touchIndex < touchList.length; touchIndex++) {
- if (touchList[touchIndex].target === drag.target) {
- touch = touchList[touchIndex];
- break;
- }
- }
- if (touch !== null) {
- return touch.clientX;
- }
- return undefined;
-
- default:
- return event.clientX;
- }
- }
-
- function onMousedown() {
- drag.target = d3.event.target;
- drag.start = getClientXFromEvent(d3.event);
- minimap.frameBeforeDrag = minimap.currentFrame.slice();
- d3.event.preventDefault ? d3.event.preventDefault() : d3.event.returnValue = false;
- d3.select(document).on("mousemove.rickshaw_minimap", onMousemove);
- d3.select(document).on("mouseup.rickshaw_minimap", onMouseup);
- d3.select(document).on("touchmove.rickshaw_minimap", onMousemove);
- d3.select(document).on("touchend.rickshaw_minimap", onMouseup);
- d3.select(document).on("touchcancel.rickshaw_minimap", onMouseup);
- }
-
- function onMousedownLeftHandle(datum, index) {
- drag.left = true;
- onMousedown();
- }
-
- function onMousedownRightHandle(datum, index) {
- drag.right = true;
- onMousedown();
- }
-
- function onMousedownMiddleHandle(datum, index) {
- drag.left = true;
- drag.right = true;
- drag.rigid = true;
- onMousedown();
- }
-
- function onMouseup(datum, index) {
- d3.select(document).on("mousemove.rickshaw_minimap", null);
- d3.select(document).on("mouseup.rickshaw_minimap", null);
- d3.select(document).on("touchmove.rickshaw_minimap", null);
- d3.select(document).on("touchend.rickshaw_minimap", null);
- d3.select(document).on("touchcancel.rickshaw_minimap", null);
- delete minimap.frameBeforeDrag;
- drag.left = false;
- drag.right = false;
- drag.rigid = false;
- }
-
- mainElement.select("path.rickshaw_minimap_lefthandle").on("mousedown", onMousedownLeftHandle);
- mainElement.select("path.rickshaw_minimap_righthandle").on("mousedown", onMousedownRightHandle);
- mainElement.select("path.rickshaw_minimap_middlehandle").on("mousedown", onMousedownMiddleHandle);
- mainElement.select("path.rickshaw_minimap_lefthandle").on("touchstart", onMousedownLeftHandle);
- mainElement.select("path.rickshaw_minimap_righthandle").on("touchstart", onMousedownRightHandle);
- mainElement.select("path.rickshaw_minimap_middlehandle").on("touchstart", onMousedownMiddleHandle);
- };
-
- var svgBlock = mainElement.selectAll("svg.rickshaw_minimap")
- .data([this]);
-
- svgBlock.enter()
- .append("svg")
- .classed("rickshaw_minimap", true);
-
- svgBlock
- .style("height", (this.config.height) + "px")
- .style("width", (this.config.width + 50) + "px")
- .style("position", "relative")
- .style("top", (-graphsHeight) + "px");
-
- var dimmingPathBlock = svgBlock.selectAll("path.rickshaw_minimap_dimming")
- .data([this]);
-
- dimmingPathBlock.enter()
- .append("path")
- .classed("rickshaw_minimap_dimming", true);
-
- var pathDescriptor = "";
- pathDescriptor += " M " + this.config.frameHandleThickness + " " + this.config.frameTopThickness;
- pathDescriptor += " h " + graphsWidth;
- pathDescriptor += " v " + graphsHeight;
- pathDescriptor += " h " + -graphsWidth;
- pathDescriptor += " z";
- pathDescriptor += " M " + (this.config.frameHandleThickness + minimap.currentFrame[0]) +
- " " + this.config.frameTopThickness;
- pathDescriptor += " H " + (this.config.frameHandleThickness + minimap.currentFrame[1]);
- pathDescriptor += " v " + graphsHeight;
- pathDescriptor += " H " + (this.config.frameHandleThickness + minimap.currentFrame[0]);
- pathDescriptor += " z";
-
- dimmingPathBlock
- .attr("d", pathDescriptor)
- .attr("fill", "white")
- .attr("fill-opacity", "0.5")
- .attr("fill-rule", "evenodd");
-
- var framePathBlock = svgBlock.selectAll("path.rickshaw_minimap_frame")
- .data([this]);
-
- framePathBlock.enter()
- .append("path")
- .classed("rickshaw_minimap_frame", true);
-
- pathDescriptor = "";
- pathDescriptor += " M " + minimap.currentFrame[0] + " 0";
- pathDescriptor += " H " + (minimap.currentFrame[1] + (this.config.frameHandleThickness * 2));
- pathDescriptor += " V " + this.config.height;
- pathDescriptor += " H " + (minimap.currentFrame[0]);
- pathDescriptor += " z";
- pathDescriptor += " M " + (minimap.currentFrame[0] + this.config.frameHandleThickness) + " " +
- this.config.frameTopThickness;
- pathDescriptor += " H " + (minimap.currentFrame[1] + this.config.frameHandleThickness);
- pathDescriptor += " v " + graphsHeight;
- pathDescriptor += " H " + (minimap.currentFrame[0] + this.config.frameHandleThickness);
- pathDescriptor += " z";
-
- framePathBlock
- .attr("d", pathDescriptor)
- .attr("stroke", "white")
- .attr("stroke-width", "2px")
- .attr("stroke-linejoin", "round")
- .attr("fill", this.config.frameColor)
- .attr("fill-opacity", this.config.frameOpacity)
- .attr("fill-rule", "evenodd");
-
- var gripperColor = d3.rgb(this.config.frameColor).darker().toString();
- var gripperBlock = svgBlock.selectAll("path.rickshaw_minimap_gripper")
- .data([this]);
-
- gripperBlock.enter()
- .append("path")
- .classed("rickshaw_minimap_gripper", true);
-
- pathDescriptor = "";
- var spacings = [0.4, 0.5, 0.6];
- var spacingIndex;
- for (spacingIndex = 0; spacingIndex < spacings.length; spacingIndex++) {
- pathDescriptor += " M " + Math.round((minimap.currentFrame[0] + (this.config.frameHandleThickness * spacings[spacingIndex]))) +
- " " + Math.round(this.config.height * 0.3);
- pathDescriptor += " V " + Math.round(this.config.height * 0.7);
- }
- for (spacingIndex = 0; spacingIndex < spacings.length; spacingIndex++) {
- pathDescriptor += " M " + Math.round((minimap.currentFrame[1] + (this.config.frameHandleThickness * (1 + spacings[spacingIndex])))) +
- " " + Math.round(this.config.height * 0.3);
- pathDescriptor += " V " + Math.round(this.config.height * 0.7);
- }
-
- gripperBlock
- .attr("d", pathDescriptor)
- .attr("stroke", gripperColor);
-
- var leftHandleBlock = svgBlock.selectAll("path.rickshaw_minimap_lefthandle")
- .data([this]);
-
- leftHandleBlock.enter()
- .append("path")
- .classed("rickshaw_minimap_lefthandle", true)
- .each(registerMouseEvents);
-
- pathDescriptor = "";
- pathDescriptor += " M " + minimap.currentFrame[0] + " 0";
- pathDescriptor += " h " + this.config.frameHandleThickness;
- pathDescriptor += " v " + this.config.height;
- pathDescriptor += " h " + -this.config.frameHandleThickness;
- pathDescriptor += " z";
-
- leftHandleBlock
- .attr("d", pathDescriptor)
- .style("cursor", "ew-resize")
- .style("fill-opacity", "0");
-
- var rightHandleBlock = svgBlock.selectAll("path.rickshaw_minimap_righthandle")
- .data([this]);
-
- rightHandleBlock.enter()
- .append("path")
- .classed("rickshaw_minimap_righthandle", true)
- .each(registerMouseEvents);
-
- pathDescriptor = "";
- pathDescriptor += " M " + (minimap.currentFrame[1] + this.config.frameHandleThickness) + " 0";
- pathDescriptor += " h " + this.config.frameHandleThickness;
- pathDescriptor += " v " + this.config.height;
- pathDescriptor += " h " + -this.config.frameHandleThickness;
- pathDescriptor += " z";
-
- rightHandleBlock
- .attr("d", pathDescriptor)
- .style("cursor", "ew-resize")
- .style("fill-opacity", "0");
-
- var middleHandleBlock = svgBlock.selectAll("path.rickshaw_minimap_middlehandle")
- .data([this]);
-
- middleHandleBlock.enter()
- .append("path")
- .classed("rickshaw_minimap_middlehandle", true)
- .each(registerMouseEvents);
-
- pathDescriptor = "";
- pathDescriptor += " M " + (minimap.currentFrame[0] + this.config.frameHandleThickness) + " 0";
- pathDescriptor += " H " + (minimap.currentFrame[1] + this.config.frameHandleThickness);
- pathDescriptor += " v " + this.config.height;
- pathDescriptor += " H " + (minimap.currentFrame[0] + this.config.frameHandleThickness);
- pathDescriptor += " z";
-
- middleHandleBlock
- .attr("d", pathDescriptor)
- .style("cursor", "move")
- .style("fill-opacity", "0");
-
- this.graphs.forEach(function(datum) {
- datum.minimapGraph.graph.update();
- });
- }
-});
View
440 src/js/Rickshaw.Graph.RangeSlider.Preview.js
@@ -0,0 +1,440 @@
+Rickshaw.namespace('Rickshaw.Graph.RangeSlider.Preview');
+
+Rickshaw.Graph.RangeSlider.Preview = Rickshaw.Class.create({
+
+ initialize: function(args) {
+
+ if (!args.element) throw "Rickshaw.Graph.RangeSlider.Preview needs a reference to an element";
+ if (!args.graph && !args.graphs) throw "Rickshaw.Graph.RangeSlider.Preview needs a reference to an graph or an array of graphs";
+
+ this.element = args.element;
+ this.graphs = args.graph ? [ args.graph ] : args.graphs;
+
+ this.defaults = {
+ height: 75,
+ width: 400,
+ gripperColor: undefined,
+ frameTopThickness: 3,
+ frameHandleThickness: 10,
+ frameColor: "#d4d4d4",
+ frameOpacity: 1,
+ minimumFrameWidth: 0
+ };
+
+ this.defaults.gripperColor = d3.rgb(this.defaults.frameColor).darker().toString();
+
+ this.configureCallbacks = [];
+ this.previews = [];
+
+ args.width = args.width || this.graphs[0].width || this.defaults.width;
+ args.height = args.height || this.graphs[0].height / 5 || this.defaults.height;
+
+ this.configure(args);
+ this.render();
+ },
+
+ onConfigure: function(callback) {
+ this.configureCallbacks.push(callback);
+ },
+
+ configure: function(args) {
+
+ this.config = {};
+
+ this.configureCallbacks.forEach(function(callback) {
+ callback(args);
+ });
+
+ Rickshaw.keys(this.defaults).forEach(function(k) {
+ this.config[k] = k in args ? args[k]
+ : k in this.config ? this.config[k]
+ : this.defaults[k];
+ }, this);
+
+ if (args.width) {
+ this.previews.forEach(function(preview) {
+ var width = args.width - this.config.frameHandleThickness * 2;
+ preview.setSize({ width: width });
+ }, this);
+ }
+
+ if (args.height) {
+ this.previews.forEach(function(preview) {
+ var height = this.previewHeight / this.graphs.length;
+ preview.setSize({ height: height });
+ }, this);
+ }
+ },
+
+ render: function() {
+
+ var self = this;
+
+ this.svg = d3.select(this.element)
+ .selectAll("svg.rickshaw_range_slider_preview")
+ .data([null]);
+
+ this.previewHeight = this.config.height - (this.config.frameTopThickness * 2);
+ this.previewWidth = this.config.width - (this.config.frameHandleThickness * 2);
+
+ this.currentFrame = [0, this.previewWidth];
+
+ var buildGraph = function(parent, index) {
+
+ var graphArgs = Rickshaw.extend({}, parent.config);
+ var height = self.previewHeight / self.graphs.length;
+
+ Rickshaw.extend(graphArgs, {
+ element: this.appendChild(document.createElement("div")),
+ height: height,
+ width: self.previewWidth,
+ series: parent.series
+ });
+
+ var graph = new Rickshaw.Graph(graphArgs);
+ self.previews.push(graph);
+
+ parent.onUpdate(function() { graph.render(); self.render() });
+
+ parent.onConfigure(function(args) {
+ // don't propagate height
+ delete args.height;
+ graph.configure(args);
+ graph.render();
+ });
+
+ graph.render();
+ };
+
+ var graphContainer = d3.select(this.element)
+ .selectAll("div.rickshaw_range_slider_preview_container")
+ .data(this.graphs);
+
+ var translateCommand = "translate(" +
+ this.config.frameHandleThickness + "px, " +
+ this.config.frameTopThickness + "px)";
+
+ graphContainer.enter()
+ .append("div")
+ .classed("rickshaw_range_slider_preview_container", true)
+ .style("-webkit-transform", translateCommand)
+ .style("-moz-transform", translateCommand)
+ .style("-ms-transform", translateCommand)
+ .style("transform", translateCommand)
+ .each(buildGraph);
+
+ graphContainer.exit()
+ .remove();
+
+ // Use the first graph as the "master" for the frame state
+ var masterGraph = this.graphs[0];
+
+ var domainScale = d3.scale.linear()
+ .domain([0, this.previewWidth])
+ .range(masterGraph.dataDomain());
+
+ var currentWindow = [masterGraph.window.xMin, masterGraph.window.xMax];
+
+ this.currentFrame[0] = currentWindow[0] === undefined ?
+ 0 : Math.round(domainScale.invert(currentWindow[0]));
+
+ if (this.currentFrame[0] < 0) this.currentFrame[0] = 0;
+
+ this.currentFrame[1] = currentWindow[1] === undefined ?
+ this.previewWidth : domainScale.invert(currentWindow[1]);
+
+ if (this.currentFrame[1] - this.currentFrame[0] < self.config.minimumFrameWidth) {
+ this.currentFrame[1] = (this.currentFrame[0] || 0) + self.config.minimumFrameWidth;
+ }
+
+ this.svg.enter()
+ .append("svg")
+ .classed("rickshaw_range_slider_preview", true)
+ .style("height", this.config.height + "px")
+ .style("width", this.config.width + "px")
+ .style("position", "relative")
+ .style("top", -this.previewHeight + "px");
+
+ this._renderDimming();
+ this._renderFrame();
+ this._renderGrippers();
+ this._renderHandles();
+ this._renderMiddle();
+
+ this._registerMouseEvents();
+ },
+
+ _renderDimming: function() {
+
+ var element = this.svg
+ .selectAll("path.dimming")
+ .data([null]);
+
+ element.enter()
+ .append("path")
+ .attr("fill", "white")
+ .attr("fill-opacity", "0.7")
+ .attr("fill-rule", "evenodd")
+ .classed("dimming", true);
+
+ var path = "";
+ path += " M " + this.config.frameHandleThickness + " " + this.config.frameTopThickness;
+ path += " h " + this.previewWidth;
+ path += " v " + this.previewHeight;
+ path += " h " + -this.previewWidth;
+ path += " z ";
+ path += " M " + Math.max(this.currentFrame[0], this.config.frameHandleThickness) + " " + this.config.frameTopThickness;
+ path += " H " + Math.min(this.currentFrame[1] + this.config.frameHandleThickness * 2, this.previewWidth + this.config.frameHandleThickness);
+ path += " v " + this.previewHeight;
+ path += " H " + Math.max(this.currentFrame[0], this.config.frameHandleThickness);
+ path += " z";
+
+ element.attr("d", path);
+ },
+
+ _renderFrame: function() {
+
+ var element = this.svg
+ .selectAll("path.frame")
+ .data([null]);
+
+ element.enter()
+ .append("path")
+ .attr("stroke", "white")
+ .attr("stroke-width", "1px")
+ .attr("stroke-linejoin", "round")
+ .attr("fill", this.config.frameColor)
+ .attr("fill-opacity", this.config.frameOpacity)
+ .attr("fill-rule", "evenodd")
+ .classed("frame", true);
+
+ var path = "";
+ path += " M " + this.currentFrame[0] + " 0";
+ path += " H " + (this.currentFrame[1] + (this.config.frameHandleThickness * 2));
+ path += " V " + this.config.height;
+ path += " H " + (this.currentFrame[0]);
+ path += " z";
+ path += " M " + (this.currentFrame[0] + this.config.frameHandleThickness) + " " + this.config.frameTopThickness;
+ path += " H " + (this.currentFrame[1] + this.config.frameHandleThickness);
+ path += " v " + this.previewHeight;
+ path += " H " + (this.currentFrame[0] + this.config.frameHandleThickness);
+ path += " z";
+
+ element.attr("d", path);
+ },
+
+ _renderGrippers: function() {
+
+ var gripper = this.svg.selectAll("path.gripper")
+ .data([null]);
+
+ gripper.enter()
+ .append("path")
+ .attr("stroke", this.config.gripperColor)
+ .classed("gripper", true);
+
+ var path = "";
+
+ [0.4, 0.6].forEach(function(spacing) {
+ path += " M " + Math.round((this.currentFrame[0] + (this.config.frameHandleThickness * spacing))) + " " + Math.round(this.config.height * 0.3);
+ path += " V " + Math.round(this.config.height * 0.7);
+ path += " M " + Math.round((this.currentFrame[1] + (this.config.frameHandleThickness * (1 + spacing)))) + " " + Math.round(this.config.height * 0.3);
+ path += " V " + Math.round(this.config.height * 0.7);
+ }.bind(this));
+
+ gripper.attr("d", path);
+ },
+
+ _renderHandles: function() {
+
+ var leftHandle = this.svg.selectAll("rect.left_handle")
+ .data([null]);
+
+ leftHandle.enter()
+ .append("rect")
+ .attr('width', this.config.frameHandleThickness)
+ .attr('height', this.config.height)
+ .style("cursor", "ew-resize")
+ .style("fill-opacity", "0")
+ .classed("left_handle", true);
+
+ leftHandle.attr('x', this.currentFrame[0]);
+
+ var rightHandle = this.svg.selectAll("rect.right_handle")
+ .data([null]);
+
+ rightHandle.enter()
+ .append("rect")
+ .attr('width', this.config.frameHandleThickness)
+ .attr('height', this.config.height)
+ .style("cursor", "ew-resize")
+ .style("fill-opacity", "0")
+ .classed("right_handle", true);
+
+ rightHandle.attr('x', this.currentFrame[1] + this.config.frameHandleThickness);
+ },
+
+ _renderMiddle: function() {
+
+ var middleHandle = this.svg.selectAll("rect.middle_handle")
+ .data([null]);
+
+ middleHandle.enter()
+ .append("rect")
+ .attr('height', this.config.height)
+ .style("cursor", "move")
+ .style("fill-opacity", "0")
+ .classed("middle_handle", true);
+
+ middleHandle
+ .attr('width', Math.max(0, this.currentFrame[1] - this.currentFrame[0]))
+ .attr('x', this.currentFrame[0] + this.config.frameHandleThickness);
+ },
+
+ _registerMouseEvents: function() {
+
+ var element = d3.select(this.element);
+
+ var drag = {
+ target: null,
+ start: null,
+ stop: null,
+ left: false,
+ right: false,
+ rigid: false
+ };
+
+ var self = this;
+
+ function onMousemove(datum, index) {
+
+ drag.stop = self._getClientXFromEvent(d3.event);
+ var distanceTraveled = drag.stop - drag.start;
+ var frameAfterDrag = self.frameBeforeDrag.slice(0);
+ var minimumFrameWidth = self.config.minimumFrameWidth;
+
+ if (drag.rigid) {
+ minimumFrameWidth = self.frameBeforeDrag[1] - self.frameBeforeDrag[0];
+ }
+ if (drag.left) {
+ frameAfterDrag[0] = Math.max(frameAfterDrag[0] + distanceTraveled, 0);
+ }
+ if (drag.right) {
+ frameAfterDrag[1] = Math.min(frameAfterDrag[1] + distanceTraveled, self.previewWidth);
+ }
+
+ var currentFrameWidth = frameAfterDrag[1] - frameAfterDrag[0];
+
+ if (currentFrameWidth <= minimumFrameWidth) {
+
+ if (drag.left) {
+ frameAfterDrag[0] = frameAfterDrag[1] - minimumFrameWidth;
+ }
+ if (drag.right) {
+ frameAfterDrag[1] = frameAfterDrag[0] + minimumFrameWidth;
+ }
+ if (frameAfterDrag[0] <= 0) {
+ frameAfterDrag[1] -= frameAfterDrag[0];
+ frameAfterDrag[0] = 0;
+ }
+ if (frameAfterDrag[1] >= self.previewWidth) {
+ frameAfterDrag[0] -= (frameAfterDrag[1] - self.previewWidth);
+ frameAfterDrag[1] = self.previewWidth;
+ }
+ }
+
+ self.graphs.forEach(function(graph) {
+
+ var domainScale = d3.scale.linear()
+ .interpolate(d3.interpolateRound)
+ .domain([0, self.previewWidth])
+ .range(graph.dataDomain());
+
+ var windowAfterDrag = [
+ domainScale(frameAfterDrag[0]),
+ domainScale(frameAfterDrag[1])
+ ];
+
+ if (frameAfterDrag[0] === 0) {
+ windowAfterDrag[0] = undefined;
+ }
+ if (frameAfterDrag[1] === self.previewWidth) {
+ windowAfterDrag[1] = undefined;
+ }
+ graph.window.xMin = windowAfterDrag[0];
+ graph.window.xMax = windowAfterDrag[1];
+
+ graph.update();
+ });
+ }
+
+ function onMousedown() {
+ drag.target = d3.event.target;
+ drag.start = self._getClientXFromEvent(d3.event);
+ self.frameBeforeDrag = self.currentFrame.slice();
+ d3.event.preventDefault ? d3.event.preventDefault() : d3.event.returnValue = false;
+ d3.select(document).on("mousemove.rickshaw_range_slider_preview", onMousemove);
+ d3.select(document).on("mouseup.rickshaw_range_slider_preview", onMouseup);
+ d3.select(document).on("touchmove.rickshaw_range_slider_preview", onMousemove);
+ d3.select(document).on("touchend.rickshaw_range_slider_preview", onMouseup);
+ d3.select(document).on("touchcancel.rickshaw_range_slider_preview", onMouseup);
+ }
+
+ function onMousedownLeftHandle(datum, index) {
+ drag.left = true;
+ onMousedown();
+ }
+
+ function onMousedownRightHandle(datum, index) {
+ drag.right = true;
+ onMousedown();
+ }
+
+ function onMousedownMiddleHandle(datum, index) {
+ drag.left = true;
+ drag.right = true;
+ drag.rigid = true;
+ onMousedown();
+ }
+
+ function onMouseup(datum, index) {
+ d3.select(document).on("mousemove.rickshaw_range_slider_preview", null);
+ d3.select(document).on("mouseup.rickshaw_range_slider_preview", null);
+ d3.select(document).on("touchmove.rickshaw_range_slider_preview", null);
+ d3.select(document).on("touchend.rickshaw_range_slider_preview", null);
+ d3.select(document).on("touchcancel.rickshaw_range_slider_preview", null);
+ delete self.frameBeforeDrag;
+ drag.left = false;
+ drag.right = false;
+ drag.rigid = false;
+ }
+
+ element.select("rect.left_handle").on("mousedown", onMousedownLeftHandle);
+ element.select("rect.right_handle").on("mousedown", onMousedownRightHandle);
+ element.select("rect.middle_handle").on("mousedown", onMousedownMiddleHandle);
+ element.select("rect.left_handle").on("touchstart", onMousedownLeftHandle);
+ element.select("rect.right_handle").on("touchstart", onMousedownRightHandle);
+ element.select("rect.middle_handle").on("touchstart", onMousedownMiddleHandle);
+ },
+
+ _getClientXFromEvent: function(event) {
+
+ switch (event.type) {
+ case 'touchstart':
+ case 'touchmove':
+ var touchList = event.changedTouches;
+ var touch = null;
+ for (var touchIndex = 0; touchIndex < touchList.length; touchIndex++) {
+ if (touchList[touchIndex].target === drag.target) {
+ touch = touchList[touchIndex];
+ break;
+ }
+ }
+ return touch !== null ? touch.clientX : undefined;
+
+ default:
+ return event.clientX;
+ }
+ }
+});
+
View
66 src/js/Rickshaw.Graph.js
@@ -2,53 +2,52 @@ Rickshaw.namespace('Rickshaw.Graph');
Rickshaw.Graph = function(args) {
- if (!args.element) throw "Rickshaw.Graph needs a reference to an element";
- if (args.element.nodeType !== 1) throw "Rickshaw.Graph element was defined but not an HTML element";
-
- this.element = args.element;
- this.series = args.series;
-
- this.defaults = {
- interpolation: 'cardinal',
- offset: 'zero',
- renderer: 'stack',
- min: undefined,
- max: undefined,
- preserve: false
- };
+ var self = this;
- Rickshaw.keys(this.defaults).forEach( function(k) {
- this[k] = args[k] || this.defaults[k];
- }, this );
+ this.initialize = function(args) {
- this.window = {};
+ if (!args.element) throw "Rickshaw.Graph needs a reference to an element";
+ if (args.element.nodeType !== 1) throw "Rickshaw.Graph element was defined but not an HTML element";
- this.updateCallbacks = [];
- this.configureCallbacks = [];
+ this.element = args.element;
+ this.series = args.series;
+ this.window = {};
- var self = this;
+ this.updateCallbacks = [];
+ this.configureCallbacks = [];
- this.initialize = function(args) {
+ this.defaults = {
+ interpolation: 'cardinal',
+ offset: 'zero',
+ min: undefined,
+ max: undefined,
+ preserve: false
+ };
+ this._loadRenderers();
+ this.configure(args);
this.validateSeries(args.series);
this.series.active = function() { return self.series.filter( function(s) { return !s.disabled } ) };
-
+ this.setSize({ width: args.width, height: args.height });
this.element.classList.add('rickshaw_graph');
+
this.vis = d3.select(this.element)
.append("svg:svg")
.attr('width', this.width)
.attr('height', this.height);
+ this.discoverRange();
+ };
+
+ this._loadRenderers = function() {
+
for (var name in Rickshaw.Graph.Renderer) {
if (!name || !Rickshaw.Graph.Renderer.hasOwnProperty(name)) continue;
var r = Rickshaw.Graph.Renderer[name];
if (!r || !r.prototype || !r.prototype.render) continue;
self.registerRenderer(new r( { graph: self } ));
}
-
- this.configure(args);
- this.discoverRange();
};
this.validateSeries = function(series) {
@@ -123,6 +122,7 @@ Rickshaw.Graph = function(args) {
this.updateCallbacks.forEach( function(callback) {
callback();
} );
+
};
this.update = this.render;
@@ -243,23 +243,27 @@ Rickshaw.Graph = function(args) {
this._renderers[renderer.name] = renderer;
};
- this.configuration = null;
this.configure = function(args) {
+ this.config = this.config || {};
+
if (args.width || args.height) {
this.setSize(args);
}
Rickshaw.keys(this.defaults).forEach( function(k) {
- this[k] = k in args ? args[k]
+ this.config[k] = k in args ? args[k]
: k in this ? this[k]
: this.defaults[k];
}, this );
- this.setRenderer(args.renderer || this.renderer.name, args);
+ Rickshaw.keys(this.config).forEach( function(k) {
+ this[k] = this.config[k];
+ }, this );
+
+ var renderer = args.renderer || (this.renderer && this.renderer.name) || 'stack';
+ this.setRenderer(renderer, args);
- this.configuration = args;
-
this.configureCallbacks.forEach( function(callback) {
callback(args);
} );
View
27 tests/Rickshaw.Graph.Minimap.js → tests/Rickshaw.Graph.RangeSlider.Preview.js
@@ -1,14 +1,23 @@
-var fs = require('fs');
+exports.setUp = function(callback) {
-exports.basic = function(test) {
+ Rickshaw = require('../rickshaw');
- var jsdom = require("jsdom").jsdom;
- global.document = jsdom("<html><head></head><body></body></html>");
- global.window = global.document.createWindow();
+ global.document = d3.select('html')[0][0].parentNode;
+ global.window = document.defaultView;
- var Rickshaw = require('../rickshaw');
new Rickshaw.Compat.ClassList();
+ callback();
+};
+
+exports.tearDown = function(callback) {
+
+ delete require.cache.d3;
+ callback();
+};
+
+exports.basic = function(test) {
+
var el = document.createElement("div");
var graph = new Rickshaw.Graph({
@@ -30,10 +39,10 @@ exports.basic = function(test) {
graph.renderer.dotSize = 6;
graph.render();
- var minimapElement = document.createElement("div");
+ var previewElement = document.createElement("div");
test.doesNotThrow(function() {
- var minimap = new Rickshaw.Graph.Minimap({
- element: minimapElement,
+ var preview = new Rickshaw.Graph.RangeSlider.Preview({
+ element: previewElement,
graph: graph
});
});
View
49 tests/Rickshaw.Graph.js
@@ -146,6 +146,55 @@ exports.inconsistent = function(test) {
test.done();
};
+exports.configure = function(test) {
+
+ var el = document.createElement('div');
+
+ var graph = new Rickshaw.Graph({
+ element: el,
+ width: 960,
+ height: 500,
+ padding: { top: 0.2 },
+ renderer: 'stack',
+ series: [ { data: [ { x: 1, y: 40 } ] } ]
+ });
+
+ test.deepEqual(graph.renderer.padding, { bottom: 0.01, right: 0, left: 0, top: 0.2 },
+ "padding makes it through to renderer from constructor");
+
+ test.strictEqual(typeof graph.padding, "undefined",
+ "padding not set on graph from constructor");
+
+ graph.configure({ padding: { top: 0.25, bottom: 0.25, right: 0.25, left: 0.25 } });
+
+ test.deepEqual(graph.renderer.padding, { bottom: 0.25, right: 0.25, left: 0.25, top: 0.25 },
+ "padding makes it through to renderer from configure");
+
+ test.strictEqual(typeof graph.padding, "undefined",
+ "padding not set on graph from configure");
+
+ var callback = function(args) {
+ if (callback.called) return;
+ test.deepEqual(args, { interpolation: 'step-after' }, "configure args match");
+ callback.called = true;
+ };
+
+ graph.onConfigure(callback);
+ graph.configure({ interpolation: 'step-after' });
+
+ test.equal(graph.interpolation, 'step-after', "interpolation set on graph");
+ test.ok(callback.called, "configure callback was called");
+ test.equal(graph.config.interpolation, 'step-after', "configuration object has interpolation set");
+
+ graph.configure({ width: 900, height: 100 });
+
+ test.deepEqual([ graph.width, graph.height ], [ 900, 100 ], "graph dimensions take");
+ test.deepEqual(graph.vis[0][0].getAttribute('width'), 900, "width set on svg");
+ test.deepEqual(graph.vis[0][0].getAttribute('height'), 100, "height set on svg");
+
+ test.done();
+};
+
exports.rendererAutodiscover = function(test) {
var Rickshaw = require('../rickshaw');
Please sign in to comment.
Something went wrong with that request. Please try again.