From 4620f53df63c142661a81b5a3837f06278199374 Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Thu, 19 Jun 2014 12:02:31 +0200 Subject: [PATCH] icons for left and right axis --- dist/vis.css | 8 +- dist/vis.js | 302 ++++++++++++++-------- examples/Graph2d/01_basic.html | 13 +- src/timeline/component/DataAxis.js | 173 +++++++------ src/timeline/component/GraphGroup.js | 40 ++- src/timeline/component/Linegraph.js | 73 ++++-- src/timeline/component/css/dataaxis.css | 4 +- src/timeline/component/css/pathStyles.css | 4 + src/timeline/component/legend.js | 14 +- 9 files changed, 410 insertions(+), 221 deletions(-) diff --git a/dist/vis.css b/dist/vis.css index a4b2c6a6..4913a206 100644 --- a/dist/vis.css +++ b/dist/vis.css @@ -341,7 +341,7 @@ .vis.timeline .dataaxis .yAxis.major { - font-size:10px; + font-size:12px; width: 100%; position: absolute; color: #4d4d4d; @@ -350,7 +350,7 @@ .vis.timeline .dataaxis .yAxis.minor{ - font-size:10px; + font-size:12px; position: absolute; width: 100%; color: #979797; @@ -453,6 +453,10 @@ stroke: #e5e5e5; } +.vis.timeline .iconFill { + fill-opacity:0.3; + stroke: none; +} diff --git a/dist/vis.js b/dist/vis.js index 144a8a22..2e13902e 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -5,7 +5,7 @@ * A dynamic, browser-based visualization library. * * @version 1.1.0 - * @date 2014-06-18 + * @date 2014-06-19 * * @license * Copyright (C) 2011-2014 Almende B.V, http://almende.com @@ -2581,7 +2581,7 @@ GraphGroup.prototype.setClass = function (className) { GraphGroup.prototype.setOptions = function(options) { if (options !== undefined) { var fields = ['yAxisOrientation']; - util.selectiveExtend(fields, options, this.options); + util.selectiveExtend(fields, this.options, options); this.linegraph._mergeOptions(this.options, options,'catmullRom'); this.linegraph._mergeOptions(this.options, options,'drawPoints'); this.linegraph._mergeOptions(this.options, options,'shaded'); @@ -2595,6 +2595,44 @@ GraphGroup.prototype.update = function(group) { this.setOptions(group.options); }; +GraphGroup.prototype.drawIcon = function(x,y,JSONcontainer, SVGcontainer, lineLength, iconHeight) { + var fillHeight = iconHeight * 0.5; + var path, fillPath, outline; + if (this.options.barGraph.enabled == false) { + outline = this.linegraph._getSVGElement("rect", JSONcontainer, SVGcontainer); + outline.setAttributeNS(null, "x", x); + outline.setAttributeNS(null, "y", y - fillHeight); + outline.setAttributeNS(null, "width", lineLength); + outline.setAttributeNS(null, "height", 2*fillHeight); + outline.setAttributeNS(null, "class", "outline"); + + path = this.linegraph._getSVGElement("path", JSONcontainer, SVGcontainer); + path.setAttributeNS(null, "class", this.className); + path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + lineLength) + ","+y+""); + if (this.options.shaded.enabled == true) { + fillPath = this.linegraph._getSVGElement("path", JSONcontainer, SVGcontainer); + if (this.options.shaded.orientation == 'top') { + fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) + + "L"+x+","+y+" L"+ (x + lineLength) + ","+y+" L"+ (x + lineLength) + "," + (y - fillHeight)); + } + else { + fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " + + "L"+x+"," + (y + fillHeight) + " " + + "L"+ (x + lineLength) + "," + (y + fillHeight) + + "L"+ (x + lineLength) + ","+y); + } + fillPath.setAttributeNS(null, "class", this.className + " iconFill"); + } + + if (this.options.drawPoints.enabled == true) { + this.linegraph.drawPoint(x + 0.5 * lineLength,y, this, JSONcontainer, SVGcontainer); + } + } + else { + //TODO: bars + } +} + /** * Created by Alex on 6/17/14. */ @@ -2610,7 +2648,7 @@ function Legend(body, options, linegraph) { this.svgElements = {}; this.dom = {}; this.classes; - this.graphs = {}; + this.groups = {}; this.setOptions(options); this.create(); @@ -2620,20 +2658,18 @@ Legend.prototype = new Component(); Legend.prototype.addGroup = function(label, graphOptions) { - if (!this.graphs.hasOwnProperty(label)) { - this.graphs[label] = graphOptions; - this.redraw(); + if (!this.groups.hasOwnProperty(label)) { + this.groups[label] = graphOptions; } }; Legend.prototype.updateGroup = function(label, graphOptions) { - this.graphs[label] = graphOptions; + this.groups[label] = graphOptions; }; Legend.prototype.deleteGroup = function(label) { - if (this.graphs.hasOwnProperty(label)) { - delete this.graphs[label]; - this.redraw(); + if (this.groups.hasOwnProperty(label)) { + delete this.groups[label]; } }; @@ -2763,26 +2799,24 @@ function DataAxis (body, options) { this.body = body; this.defaultOptions = { - orientation: 'left', // supported: 'left' + orientation: 'left', // supported: 'left', 'right' showMinorLabels: true, showMajorLabels: true, - majorLinesOffset: 25, - minorLinesOffset: 25, - width: '50px', + majorLinesOffset: 7, + minorLinesOffset: 4, + labelOffsetX: 9, + labelOffsetY: -6, + width: '60px', height: '300px' }; this.props = {}; this.dom = { - majorLines: [], - majorTexts: [], - minorLines: [], - minorTexts: [], + lines: [], + labels: [], redundant: { - majorLines: [], - majorTexts: [], - minorLines: [], - minorTexts: [] + lines: [], + labels: [] } }; @@ -2799,6 +2833,10 @@ function DataAxis (body, options) { this.stepPixelsForced = 25; this.lineOffset = 0; this.master = true; + this.svgElements = {}; + this.drawIcons = false; + + this.groups = {}; // create the HTML DOM this._create(); @@ -2806,6 +2844,25 @@ function DataAxis (body, options) { DataAxis.prototype = new Component(); + + +DataAxis.prototype.addGroup = function(label, graphOptions) { + if (!this.groups.hasOwnProperty(label)) { + this.groups[label] = graphOptions; + } +}; + +DataAxis.prototype.updateGroup = function(label, graphOptions) { + this.groups[label] = graphOptions; +}; + +DataAxis.prototype.deleteGroup = function(label) { + if (this.groups.hasOwnProperty(label)) { + delete this.groups[label]; + } +}; + + DataAxis.prototype.setOptions = function(options) { if (options) { var redraw = false; @@ -2834,8 +2891,38 @@ DataAxis.prototype._create = function() { this.dom.lineContainer = document.createElement('div'); this.dom.lineContainer.style.width = '100%'; this.dom.lineContainer.style.height = this.options.height; + + // create svg element for graph drawing. + this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg"); + this.svg.style.position = "absolute"; + this.svg.style.top = '0px'; + this.svg.style.height = '100%'; + this.svg.style.width = '100%'; + this.svg.style.display = "block"; + this.dom.frame.appendChild(this.svg); }; +DataAxis.prototype._redrawGroupIcons = function() { + var x; + var iconWidth = 20; + var iconHeight = 15; + var iconOffset = 4; + var y = iconOffset + 0.5 * iconHeight; + if (this.options.orientation == 'left') { + x = iconOffset; + } + else { + x = this.width - iconWidth - iconOffset; + } + + + for (var groupId in this.groups) { + if (this.groups.hasOwnProperty(groupId)) { + this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight); + y += iconHeight + iconOffset; + } + } +} /** * Create the HTML DOM for the DataAxis @@ -2924,6 +3011,9 @@ DataAxis.prototype.redraw = function () { } this._redrawLabels(); + if (this.drawIcons == true) { + this._redrawGroupIcons(); + } }; /** @@ -2944,14 +3034,10 @@ DataAxis.prototype._redrawLabels = function () { // can be picked for re-use, and clear the lists with lines and texts. // At the end of the function _redrawLabels, left over elements will be cleaned up var dom = this.dom; - dom.redundant.majorLines = dom.majorLines; - dom.redundant.majorTexts = dom.majorTexts; - dom.redundant.minorLines = dom.minorLines; - dom.redundant.minorTexts = dom.minorTexts; - dom.majorLines = []; - dom.majorTexts = []; - dom.minorLines = []; - dom.minorTexts = []; + dom.redundant.lines = dom.lines; + dom.redundant.labels = dom.labels; + dom.lines = []; + dom.labels = []; step.first(); var stepPixels = this.dom.frame.offsetHeight / ((step.marginRange / step.step) + 1); @@ -2972,7 +3058,11 @@ DataAxis.prototype._redrawLabels = function () { var xFirstMajorLabel = undefined; this.valueAtZero = step.marginEnd; var marginStartPos = 0; - var max = 0; + + // do not draw the first label + var max = 1; + step.next(); + var y = 0; while (max < amountOfSteps) { y = Math.round(max * stepPixels); @@ -2980,7 +3070,7 @@ DataAxis.prototype._redrawLabels = function () { var isMajor = step.isMajor(); if (this.options['showMinorLabels'] && isMajor == false) { - this._redrawMinorText(y - 2, step.getLabelMinor(), orientation); + this._redrawLabel(y - 2, step.getLabelMinor(), orientation, 'yAxis minor'); } if (isMajor && this.options['showMajorLabels']) { @@ -2988,7 +3078,7 @@ DataAxis.prototype._redrawLabels = function () { if (xFirstMajorLabel == undefined) { xFirstMajorLabel = y; } - this._redrawMajorText(y - 2, step.getLabelMajor(), orientation); + this._redrawLabel(y - 2, step.getLabelMajor(), orientation, 'yAxis major'); } this._redrawMajorLine(y, orientation); } @@ -3002,17 +3092,6 @@ DataAxis.prototype._redrawLabels = function () { this.conversionFactor = marginStartPos/((amountOfSteps-1) * step.step); - // create a major label on the left when needed - if (this.options['showMajorLabels']) { - var leftPoint = this._start; - var leftText = step.getLabelMajor(leftPoint); - var widthText = leftText.length * (this.props.majorCharWidth || 10) + 10; // upper bound estimation - - if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) { - this._redrawMajorText(0, leftText, orientation); - } - } - // Cleanup leftover DOM elements from the redundant list util.forEach(this.dom.redundant, function (arr) { while (arr.length) { @@ -3037,76 +3116,42 @@ DataAxis.prototype.convertValue = function(value) { return convertedValue; // the -2 is to compensate for the borders } -/** - * Create a minor label for the axis at position x - * @param {Number} x - * @param {String} text - * @param {String} orientation "top" or "bottom" (default) - * @private - */ -DataAxis.prototype._redrawMinorText = function (y, text, orientation) { - // reuse redundant label - var label = this.dom.redundant.minorTexts.shift(); - - if (!label) { - // create new label - var content = document.createTextNode(''); - label = document.createElement('div'); - label.appendChild(content); - label.className = 'yAxis minor'; - this.dom.frame.appendChild(label); - } - this.dom.minorTexts.push(label); - - label.childNodes[0].nodeValue = text; - - if (orientation == 'left') { - label.style.left = '-2px'; - label.style.textAlign = "right"; - } - else { - label.style.left = '2px'; - label.style.textAlign = "left"; - } - label.style.top = y + 'px'; - //label.title = title; // TODO: this is a heavy operation -}; /** - * Create a Major label for the axis at position x + * Create a label for the axis at position x * @param {Number} x * @param {String} text * @param {String} orientation "top" or "bottom" (default) * @private */ -DataAxis.prototype._redrawMajorText = function (y, text, orientation) { +DataAxis.prototype._redrawLabel = function (y, text, orientation, className) { // reuse redundant label - var label = this.dom.redundant.majorTexts.shift(); + var label = this.dom.redundant.labels.shift(); if (!label) { // create label var content = document.createTextNode(text); label = document.createElement('div'); - label.className = 'yAxis major'; + label.className = className; label.appendChild(content); this.dom.frame.appendChild(label); } - this.dom.majorTexts.push(label); + this.dom.labels.push(label); label.childNodes[0].nodeValue = text; //label.title = title; // TODO: this is a heavy operation if (orientation == 'left') { - label.style.left = '-2px'; + label.style.left = '-' + this.options.labelOffsetX + 'px'; label.style.textAlign = "right"; } else { - label.style.left = '2px'; + label.style.left = this.options.labelOffsetX + 'px'; label.style.textAlign = "left"; } - label.style.top = y + 'px'; + label.style.top = y + this.options.labelOffsetY + 'px'; }; /** @@ -3118,7 +3163,7 @@ DataAxis.prototype._redrawMajorText = function (y, text, orientation) { DataAxis.prototype._redrawMinorLine = function (y, orientation) { if (this.master == true) { // reuse redundant line - var line = this.dom.redundant.minorLines.shift(); + var line = this.dom.redundant.lines.shift(); if (!line) { // create vertical line @@ -3126,7 +3171,7 @@ DataAxis.prototype._redrawMinorLine = function (y, orientation) { line.className = 'grid horizontal minor'; this.dom.lineContainer.appendChild(line); } - this.dom.minorLines.push(line); + this.dom.lines.push(line); var props = this.props; if (orientation == 'left') { @@ -3150,7 +3195,7 @@ DataAxis.prototype._redrawMinorLine = function (y, orientation) { DataAxis.prototype._redrawMajorLine = function (y, orientation) { if (this.master == true) { // reuse redundant line - var line = this.dom.redundant.majorLines.shift(); + var line = this.dom.redundant.lines.shift(); if (!line) { // create vertical line @@ -3158,13 +3203,13 @@ DataAxis.prototype._redrawMajorLine = function (y, orientation) { line.className = 'grid horizontal major'; this.dom.lineContainer.appendChild(line); } - this.dom.majorLines.push(line); + this.dom.lines.push(line); if (orientation == 'left') { line.style.left = (this.width - this.options.majorLinesOffset) + 'px'; } else { - line.style.left = -1*(this.width - this.options.mayorLinesOffset) + 'px'; + line.style.left = -1*(this.width - this.options.majorLinesOffset) + 'px'; } line.style.top = y + 'px'; line.style.width = this.props.majorLineWidth + 'px'; @@ -3216,6 +3261,8 @@ DataAxis.prototype.snap = function(date) { return this.step.snap(date); }; + + var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items function Linegraph(body, options) { @@ -3229,7 +3276,7 @@ function Linegraph(body, options) { orientation: 'top' // top, bottom }, barGraph: { - enabled: true, + enabled: false, binSize: 'auto' }, drawPoints: { @@ -3241,6 +3288,24 @@ function Linegraph(body, options) { enabled: true, parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5) alpha: 0.5 + }, + dataAxis: { + showMinorLabels: true, + showMajorLabels: true, + majorLinesOffset: 27, + minorLinesOffset: 24, + labelOffsetX: 2, + labelOffsetY: 0, + width: '60px' + }, + dataAxisRight: { + showMinorLabels: true, + showMajorLabels: true, + majorLinesOffset: 7, + minorLinesOffset: 4, + labelOffsetX: 9, + labelOffsetY: -6, + width: '60px' } }; @@ -3335,21 +3400,11 @@ Linegraph.prototype._create = function(){ // panel with time axis this.yAxisLeft = new DataAxis(this.body, { orientation: 'left', - showMinorLabels: true, - showMajorLabels: true, - majorLinesOffset: 25, - minorLinesOffset: 25, - width: '50px', height: this.svg.style.height }); this.yAxisRight = new DataAxis(this.body, { orientation: 'right', - showMinorLabels: true, - showMajorLabels: true, - majorLinesOffset: 25, - minorLinesOffset: 25, - width: '50px', height: this.svg.style.height }); @@ -3547,13 +3602,24 @@ Linegraph.prototype._onUpdateGroups = function (groupIds) { if (!this.groups.hasOwnProperty(groupIds[i])) { this.groups[groupIds[i]] = new GraphGroup(group, this.options, this); this.legend.addGroup(groupIds[i],this.groups[groupIds[i]]); + if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') { + this.yAxisRight.addGroup(groupIds[i], this.groups[groupIds[i]]); + } + else { + this.yAxisLeft.addGroup(groupIds[i], this.groups[groupIds[i]]); + } } else { this.groups[groupIds[i]].update(group); this.legend.updateGroup(groupIds[i],this.groups[groupIds[i]]); + if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') { + this.yAxisRight.updateGroup(groupIds[i], this.groups[groupIds[i]]); + } + else { + this.yAxisLeft.updateGroup(groupIds[i], this.groups[groupIds[i]]); + } } } - this.updateGraph(); this.redraw(); }; @@ -3576,9 +3642,23 @@ Linegraph.prototype._updateUngrouped = function() { var group = {content: "graph"}; if (!this.groups.hasOwnProperty(UNGROUPED)) { this.groups[UNGROUPED] = new GraphGroup(group, this.options, this); + this.legend.addGroup(UNGROUPED,this.groups[UNGROUPED]); + if (this.groups[UNGROUPED].options.yAxisOrientation == 'right') { + this.yAxisRight.addGroup(UNGROUPED, this.groups[UNGROUPED]); + } + else { + this.yAxisLeft.addGroup(UNGROUPED, this.groups[UNGROUPED]); + } } else { this.groups[UNGROUPED].update(group); + this.legend.updateGroup(UNGROUPED,this.groups[UNGROUPED]); + if (this.groups[UNGROUPED].options.yAxisOrientation == 'right') { + this.yAxisRight.updateGroup(UNGROUPED, this.groups[UNGROUPED]); + } + else { + this.yAxisLeft.updateGroup(UNGROUPED, this.groups[UNGROUPED]); + } } if (this.itemsData != null) { @@ -3598,6 +3678,8 @@ Linegraph.prototype._updateUngrouped = function() { if (pointInUNGROUPED.length == 0) { this.legend.deleteGroup(UNGROUPED); delete this.groups[UNGROUPED]; + this.yAxisLeft.yAxisRight(UNGROUPED); + this.yAxisLeft.deleteGroup(UNGROUPED); } } }; @@ -3713,6 +3795,15 @@ Linegraph.prototype._updateYAxis = function(groupIds) { this.body.emitter.emit('change'); } + if (yAxisRightUsed == true && yAxisLeftUsed == true) { + this.yAxisLeft.drawIcons = true; + this.yAxisRight.drawIcons = true; + } + else { + this.yAxisLeft.drawIcons = false; + this.yAxisRight.drawIcons = false; + } + this.yAxisRight.master = !yAxisLeftUsed; if (this.yAxisRight.master == false) { @@ -3767,7 +3858,7 @@ Linegraph.prototype.drawGraph = function (groupId, groupIndex, amountOfGraphs) { // can be optimized, only has to be done once. var group = this.groups[groupId]; - if (this.options.style == 'bar') { + if (this.options.barGraph.enabled == 'true') { this.drawBarGraph(datapoints, group, amountOfGraphs); } else { @@ -3787,7 +3878,6 @@ Linegraph.prototype.drawBarGraph = function (datapoints, group, amountOfGraphs) var dataset = this._prepareData(datapoints); // draw points for (var i = 0; i < dataset.length; i++) { - this.drawBar(dataset[i].x, dataset[i].y, className); } } @@ -3888,7 +3978,7 @@ Linegraph.prototype.drawPoints = function (dataset, group, JSONcontainer, svg) { */ Linegraph.prototype.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { var point; - if (options.drawPoints.style == 'circle') { + if (group.options.drawPoints.style == 'circle') { point = this._getSVGElement('circle',JSONcontainer,svgContainer); point.setAttributeNS(null, "cx", x); point.setAttributeNS(null, "cy", y); diff --git a/examples/Graph2d/01_basic.html b/examples/Graph2d/01_basic.html index 9bbe4e5e..e17fd416 100644 --- a/examples/Graph2d/01_basic.html +++ b/examples/Graph2d/01_basic.html @@ -63,6 +63,7 @@ content: names[2], className: "graphGroup2", options: { +// yAxisOrientation: 'right', shaded: { enabled: false, orientation: 'top' // top, bottom @@ -71,12 +72,12 @@ var container = document.getElementById('visualization'); var items = [ - {x: '2014-06-11', y: 10 }, - {x: '2014-06-12', y: 25 }, - {x: '2014-06-13', y: 30, group: 0}, - {x: '2014-06-14', y: 10, group: 0}, - {x: '2014-06-15', y: 150, group: 1}, - {x: '2014-06-16', y: 300, group: 1}, + {x: '2014-06-11', y: 10000 }, + {x: '2014-06-12', y: 25000 }, + {x: '2014-06-13', y: 30000, group: 0}, + {x: '2014-06-14', y: 10000, group: 0}, + {x: '2014-06-15', y: 150000, group: 1}, + {x: '2014-06-16', y: 300000, group: 1}, {x: '2014-06-17', y: 100, group: 1}, {x: '2014-06-18', y: 150, group: 1}, {x: '2014-06-19', y: 520, group: 1}, diff --git a/src/timeline/component/DataAxis.js b/src/timeline/component/DataAxis.js index 39e82259..c7bd5957 100644 --- a/src/timeline/component/DataAxis.js +++ b/src/timeline/component/DataAxis.js @@ -10,26 +10,24 @@ function DataAxis (body, options) { this.body = body; this.defaultOptions = { - orientation: 'left', // supported: 'left' + orientation: 'left', // supported: 'left', 'right' showMinorLabels: true, showMajorLabels: true, - majorLinesOffset: 25, - minorLinesOffset: 25, - width: '50px', + majorLinesOffset: 7, + minorLinesOffset: 4, + labelOffsetX: 9, + labelOffsetY: -6, + width: '60px', height: '300px' }; this.props = {}; this.dom = { - majorLines: [], - majorTexts: [], - minorLines: [], - minorTexts: [], + lines: [], + labels: [], redundant: { - majorLines: [], - majorTexts: [], - minorLines: [], - minorTexts: [] + lines: [], + labels: [] } }; @@ -46,6 +44,10 @@ function DataAxis (body, options) { this.stepPixelsForced = 25; this.lineOffset = 0; this.master = true; + this.svgElements = {}; + this.drawIcons = false; + + this.groups = {}; // create the HTML DOM this._create(); @@ -53,6 +55,25 @@ function DataAxis (body, options) { DataAxis.prototype = new Component(); + + +DataAxis.prototype.addGroup = function(label, graphOptions) { + if (!this.groups.hasOwnProperty(label)) { + this.groups[label] = graphOptions; + } +}; + +DataAxis.prototype.updateGroup = function(label, graphOptions) { + this.groups[label] = graphOptions; +}; + +DataAxis.prototype.deleteGroup = function(label) { + if (this.groups.hasOwnProperty(label)) { + delete this.groups[label]; + } +}; + + DataAxis.prototype.setOptions = function(options) { if (options) { var redraw = false; @@ -81,8 +102,38 @@ DataAxis.prototype._create = function() { this.dom.lineContainer = document.createElement('div'); this.dom.lineContainer.style.width = '100%'; this.dom.lineContainer.style.height = this.options.height; + + // create svg element for graph drawing. + this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg"); + this.svg.style.position = "absolute"; + this.svg.style.top = '0px'; + this.svg.style.height = '100%'; + this.svg.style.width = '100%'; + this.svg.style.display = "block"; + this.dom.frame.appendChild(this.svg); }; +DataAxis.prototype._redrawGroupIcons = function() { + var x; + var iconWidth = 20; + var iconHeight = 15; + var iconOffset = 4; + var y = iconOffset + 0.5 * iconHeight; + if (this.options.orientation == 'left') { + x = iconOffset; + } + else { + x = this.width - iconWidth - iconOffset; + } + + + for (var groupId in this.groups) { + if (this.groups.hasOwnProperty(groupId)) { + this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight); + y += iconHeight + iconOffset; + } + } +} /** * Create the HTML DOM for the DataAxis @@ -171,6 +222,9 @@ DataAxis.prototype.redraw = function () { } this._redrawLabels(); + if (this.drawIcons == true) { + this._redrawGroupIcons(); + } }; /** @@ -191,14 +245,10 @@ DataAxis.prototype._redrawLabels = function () { // can be picked for re-use, and clear the lists with lines and texts. // At the end of the function _redrawLabels, left over elements will be cleaned up var dom = this.dom; - dom.redundant.majorLines = dom.majorLines; - dom.redundant.majorTexts = dom.majorTexts; - dom.redundant.minorLines = dom.minorLines; - dom.redundant.minorTexts = dom.minorTexts; - dom.majorLines = []; - dom.majorTexts = []; - dom.minorLines = []; - dom.minorTexts = []; + dom.redundant.lines = dom.lines; + dom.redundant.labels = dom.labels; + dom.lines = []; + dom.labels = []; step.first(); var stepPixels = this.dom.frame.offsetHeight / ((step.marginRange / step.step) + 1); @@ -219,7 +269,11 @@ DataAxis.prototype._redrawLabels = function () { var xFirstMajorLabel = undefined; this.valueAtZero = step.marginEnd; var marginStartPos = 0; - var max = 0; + + // do not draw the first label + var max = 1; + step.next(); + var y = 0; while (max < amountOfSteps) { y = Math.round(max * stepPixels); @@ -227,7 +281,7 @@ DataAxis.prototype._redrawLabels = function () { var isMajor = step.isMajor(); if (this.options['showMinorLabels'] && isMajor == false) { - this._redrawMinorText(y - 2, step.getLabelMinor(), orientation); + this._redrawLabel(y - 2, step.getLabelMinor(), orientation, 'yAxis minor'); } if (isMajor && this.options['showMajorLabels']) { @@ -235,7 +289,7 @@ DataAxis.prototype._redrawLabels = function () { if (xFirstMajorLabel == undefined) { xFirstMajorLabel = y; } - this._redrawMajorText(y - 2, step.getLabelMajor(), orientation); + this._redrawLabel(y - 2, step.getLabelMajor(), orientation, 'yAxis major'); } this._redrawMajorLine(y, orientation); } @@ -249,17 +303,6 @@ DataAxis.prototype._redrawLabels = function () { this.conversionFactor = marginStartPos/((amountOfSteps-1) * step.step); - // create a major label on the left when needed - if (this.options['showMajorLabels']) { - var leftPoint = this._start; - var leftText = step.getLabelMajor(leftPoint); - var widthText = leftText.length * (this.props.majorCharWidth || 10) + 10; // upper bound estimation - - if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) { - this._redrawMajorText(0, leftText, orientation); - } - } - // Cleanup leftover DOM elements from the redundant list util.forEach(this.dom.redundant, function (arr) { while (arr.length) { @@ -284,76 +327,42 @@ DataAxis.prototype.convertValue = function(value) { return convertedValue; // the -2 is to compensate for the borders } -/** - * Create a minor label for the axis at position x - * @param {Number} x - * @param {String} text - * @param {String} orientation "top" or "bottom" (default) - * @private - */ -DataAxis.prototype._redrawMinorText = function (y, text, orientation) { - // reuse redundant label - var label = this.dom.redundant.minorTexts.shift(); - - if (!label) { - // create new label - var content = document.createTextNode(''); - label = document.createElement('div'); - label.appendChild(content); - label.className = 'yAxis minor'; - this.dom.frame.appendChild(label); - } - this.dom.minorTexts.push(label); - - label.childNodes[0].nodeValue = text; - if (orientation == 'left') { - label.style.left = '-2px'; - label.style.textAlign = "right"; - } - else { - label.style.left = '2px'; - label.style.textAlign = "left"; - } - - label.style.top = y + 'px'; - //label.title = title; // TODO: this is a heavy operation -}; /** - * Create a Major label for the axis at position x + * Create a label for the axis at position x * @param {Number} x * @param {String} text * @param {String} orientation "top" or "bottom" (default) * @private */ -DataAxis.prototype._redrawMajorText = function (y, text, orientation) { +DataAxis.prototype._redrawLabel = function (y, text, orientation, className) { // reuse redundant label - var label = this.dom.redundant.majorTexts.shift(); + var label = this.dom.redundant.labels.shift(); if (!label) { // create label var content = document.createTextNode(text); label = document.createElement('div'); - label.className = 'yAxis major'; + label.className = className; label.appendChild(content); this.dom.frame.appendChild(label); } - this.dom.majorTexts.push(label); + this.dom.labels.push(label); label.childNodes[0].nodeValue = text; //label.title = title; // TODO: this is a heavy operation if (orientation == 'left') { - label.style.left = '-2px'; + label.style.left = '-' + this.options.labelOffsetX + 'px'; label.style.textAlign = "right"; } else { - label.style.left = '2px'; + label.style.left = this.options.labelOffsetX + 'px'; label.style.textAlign = "left"; } - label.style.top = y + 'px'; + label.style.top = y + this.options.labelOffsetY + 'px'; }; /** @@ -365,7 +374,7 @@ DataAxis.prototype._redrawMajorText = function (y, text, orientation) { DataAxis.prototype._redrawMinorLine = function (y, orientation) { if (this.master == true) { // reuse redundant line - var line = this.dom.redundant.minorLines.shift(); + var line = this.dom.redundant.lines.shift(); if (!line) { // create vertical line @@ -373,7 +382,7 @@ DataAxis.prototype._redrawMinorLine = function (y, orientation) { line.className = 'grid horizontal minor'; this.dom.lineContainer.appendChild(line); } - this.dom.minorLines.push(line); + this.dom.lines.push(line); var props = this.props; if (orientation == 'left') { @@ -397,7 +406,7 @@ DataAxis.prototype._redrawMinorLine = function (y, orientation) { DataAxis.prototype._redrawMajorLine = function (y, orientation) { if (this.master == true) { // reuse redundant line - var line = this.dom.redundant.majorLines.shift(); + var line = this.dom.redundant.lines.shift(); if (!line) { // create vertical line @@ -405,13 +414,13 @@ DataAxis.prototype._redrawMajorLine = function (y, orientation) { line.className = 'grid horizontal major'; this.dom.lineContainer.appendChild(line); } - this.dom.majorLines.push(line); + this.dom.lines.push(line); if (orientation == 'left') { line.style.left = (this.width - this.options.majorLinesOffset) + 'px'; } else { - line.style.left = -1*(this.width - this.options.mayorLinesOffset) + 'px'; + line.style.left = -1*(this.width - this.options.majorLinesOffset) + 'px'; } line.style.top = y + 'px'; line.style.width = this.props.majorLineWidth + 'px'; @@ -462,3 +471,5 @@ DataAxis.prototype._calculateCharSize = function () { DataAxis.prototype.snap = function(date) { return this.step.snap(date); }; + + diff --git a/src/timeline/component/GraphGroup.js b/src/timeline/component/GraphGroup.js index 9765d387..28b98f27 100644 --- a/src/timeline/component/GraphGroup.js +++ b/src/timeline/component/GraphGroup.js @@ -21,7 +21,7 @@ GraphGroup.prototype.setClass = function (className) { GraphGroup.prototype.setOptions = function(options) { if (options !== undefined) { var fields = ['yAxisOrientation']; - util.selectiveExtend(fields, options, this.options); + util.selectiveExtend(fields, this.options, options); this.linegraph._mergeOptions(this.options, options,'catmullRom'); this.linegraph._mergeOptions(this.options, options,'drawPoints'); this.linegraph._mergeOptions(this.options, options,'shaded'); @@ -34,3 +34,41 @@ GraphGroup.prototype.update = function(group) { this.className = group.className || this.className || "graphGroup" + this.linegraph.groupsUsingDefaultStyles; this.setOptions(group.options); }; + +GraphGroup.prototype.drawIcon = function(x,y,JSONcontainer, SVGcontainer, lineLength, iconHeight) { + var fillHeight = iconHeight * 0.5; + var path, fillPath, outline; + if (this.options.barGraph.enabled == false) { + outline = this.linegraph._getSVGElement("rect", JSONcontainer, SVGcontainer); + outline.setAttributeNS(null, "x", x); + outline.setAttributeNS(null, "y", y - fillHeight); + outline.setAttributeNS(null, "width", lineLength); + outline.setAttributeNS(null, "height", 2*fillHeight); + outline.setAttributeNS(null, "class", "outline"); + + path = this.linegraph._getSVGElement("path", JSONcontainer, SVGcontainer); + path.setAttributeNS(null, "class", this.className); + path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + lineLength) + ","+y+""); + if (this.options.shaded.enabled == true) { + fillPath = this.linegraph._getSVGElement("path", JSONcontainer, SVGcontainer); + if (this.options.shaded.orientation == 'top') { + fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) + + "L"+x+","+y+" L"+ (x + lineLength) + ","+y+" L"+ (x + lineLength) + "," + (y - fillHeight)); + } + else { + fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " + + "L"+x+"," + (y + fillHeight) + " " + + "L"+ (x + lineLength) + "," + (y + fillHeight) + + "L"+ (x + lineLength) + ","+y); + } + fillPath.setAttributeNS(null, "class", this.className + " iconFill"); + } + + if (this.options.drawPoints.enabled == true) { + this.linegraph.drawPoint(x + 0.5 * lineLength,y, this, JSONcontainer, SVGcontainer); + } + } + else { + //TODO: bars + } +} diff --git a/src/timeline/component/Linegraph.js b/src/timeline/component/Linegraph.js index f8cc0722..6b76077b 100644 --- a/src/timeline/component/Linegraph.js +++ b/src/timeline/component/Linegraph.js @@ -11,7 +11,7 @@ function Linegraph(body, options) { orientation: 'top' // top, bottom }, barGraph: { - enabled: true, + enabled: false, binSize: 'auto' }, drawPoints: { @@ -23,6 +23,24 @@ function Linegraph(body, options) { enabled: true, parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5) alpha: 0.5 + }, + dataAxis: { + showMinorLabels: true, + showMajorLabels: true, + majorLinesOffset: 27, + minorLinesOffset: 24, + labelOffsetX: 2, + labelOffsetY: 0, + width: '60px' + }, + dataAxisRight: { + showMinorLabels: true, + showMajorLabels: true, + majorLinesOffset: 7, + minorLinesOffset: 4, + labelOffsetX: 9, + labelOffsetY: -6, + width: '60px' } }; @@ -117,21 +135,11 @@ Linegraph.prototype._create = function(){ // panel with time axis this.yAxisLeft = new DataAxis(this.body, { orientation: 'left', - showMinorLabels: true, - showMajorLabels: true, - majorLinesOffset: 25, - minorLinesOffset: 25, - width: '50px', height: this.svg.style.height }); this.yAxisRight = new DataAxis(this.body, { orientation: 'right', - showMinorLabels: true, - showMajorLabels: true, - majorLinesOffset: 25, - minorLinesOffset: 25, - width: '50px', height: this.svg.style.height }); @@ -329,13 +337,24 @@ Linegraph.prototype._onUpdateGroups = function (groupIds) { if (!this.groups.hasOwnProperty(groupIds[i])) { this.groups[groupIds[i]] = new GraphGroup(group, this.options, this); this.legend.addGroup(groupIds[i],this.groups[groupIds[i]]); + if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') { + this.yAxisRight.addGroup(groupIds[i], this.groups[groupIds[i]]); + } + else { + this.yAxisLeft.addGroup(groupIds[i], this.groups[groupIds[i]]); + } } else { this.groups[groupIds[i]].update(group); this.legend.updateGroup(groupIds[i],this.groups[groupIds[i]]); + if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') { + this.yAxisRight.updateGroup(groupIds[i], this.groups[groupIds[i]]); + } + else { + this.yAxisLeft.updateGroup(groupIds[i], this.groups[groupIds[i]]); + } } } - this.updateGraph(); this.redraw(); }; @@ -358,9 +377,23 @@ Linegraph.prototype._updateUngrouped = function() { var group = {content: "graph"}; if (!this.groups.hasOwnProperty(UNGROUPED)) { this.groups[UNGROUPED] = new GraphGroup(group, this.options, this); + this.legend.addGroup(UNGROUPED,this.groups[UNGROUPED]); + if (this.groups[UNGROUPED].options.yAxisOrientation == 'right') { + this.yAxisRight.addGroup(UNGROUPED, this.groups[UNGROUPED]); + } + else { + this.yAxisLeft.addGroup(UNGROUPED, this.groups[UNGROUPED]); + } } else { this.groups[UNGROUPED].update(group); + this.legend.updateGroup(UNGROUPED,this.groups[UNGROUPED]); + if (this.groups[UNGROUPED].options.yAxisOrientation == 'right') { + this.yAxisRight.updateGroup(UNGROUPED, this.groups[UNGROUPED]); + } + else { + this.yAxisLeft.updateGroup(UNGROUPED, this.groups[UNGROUPED]); + } } if (this.itemsData != null) { @@ -380,6 +413,8 @@ Linegraph.prototype._updateUngrouped = function() { if (pointInUNGROUPED.length == 0) { this.legend.deleteGroup(UNGROUPED); delete this.groups[UNGROUPED]; + this.yAxisLeft.yAxisRight(UNGROUPED); + this.yAxisLeft.deleteGroup(UNGROUPED); } } }; @@ -495,6 +530,15 @@ Linegraph.prototype._updateYAxis = function(groupIds) { this.body.emitter.emit('change'); } + if (yAxisRightUsed == true && yAxisLeftUsed == true) { + this.yAxisLeft.drawIcons = true; + this.yAxisRight.drawIcons = true; + } + else { + this.yAxisLeft.drawIcons = false; + this.yAxisRight.drawIcons = false; + } + this.yAxisRight.master = !yAxisLeftUsed; if (this.yAxisRight.master == false) { @@ -549,7 +593,7 @@ Linegraph.prototype.drawGraph = function (groupId, groupIndex, amountOfGraphs) { // can be optimized, only has to be done once. var group = this.groups[groupId]; - if (this.options.style == 'bar') { + if (this.options.barGraph.enabled == 'true') { this.drawBarGraph(datapoints, group, amountOfGraphs); } else { @@ -569,7 +613,6 @@ Linegraph.prototype.drawBarGraph = function (datapoints, group, amountOfGraphs) var dataset = this._prepareData(datapoints); // draw points for (var i = 0; i < dataset.length; i++) { - this.drawBar(dataset[i].x, dataset[i].y, className); } } @@ -670,7 +713,7 @@ Linegraph.prototype.drawPoints = function (dataset, group, JSONcontainer, svg) { */ Linegraph.prototype.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { var point; - if (options.drawPoints.style == 'circle') { + if (group.options.drawPoints.style == 'circle') { point = this._getSVGElement('circle',JSONcontainer,svgContainer); point.setAttributeNS(null, "cx", x); point.setAttributeNS(null, "cy", y); diff --git a/src/timeline/component/css/dataaxis.css b/src/timeline/component/css/dataaxis.css index 30f20415..4f3fc446 100644 --- a/src/timeline/component/css/dataaxis.css +++ b/src/timeline/component/css/dataaxis.css @@ -17,7 +17,7 @@ .vis.timeline .dataaxis .yAxis.major { - font-size:10px; + font-size:12px; width: 100%; position: absolute; color: #4d4d4d; @@ -26,7 +26,7 @@ .vis.timeline .dataaxis .yAxis.minor{ - font-size:10px; + font-size:12px; position: absolute; width: 100%; color: #979797; diff --git a/src/timeline/component/css/pathStyles.css b/src/timeline/component/css/pathStyles.css index 75281848..95665ef1 100644 --- a/src/timeline/component/css/pathStyles.css +++ b/src/timeline/component/css/pathStyles.css @@ -94,5 +94,9 @@ stroke: #e5e5e5; } +.vis.timeline .iconFill { + fill-opacity:0.3; + stroke: none; +} diff --git a/src/timeline/component/legend.js b/src/timeline/component/legend.js index 95a952df..7fa0f920 100644 --- a/src/timeline/component/legend.js +++ b/src/timeline/component/legend.js @@ -13,7 +13,7 @@ function Legend(body, options, linegraph) { this.svgElements = {}; this.dom = {}; this.classes; - this.graphs = {}; + this.groups = {}; this.setOptions(options); this.create(); @@ -23,20 +23,18 @@ Legend.prototype = new Component(); Legend.prototype.addGroup = function(label, graphOptions) { - if (!this.graphs.hasOwnProperty(label)) { - this.graphs[label] = graphOptions; - this.redraw(); + if (!this.groups.hasOwnProperty(label)) { + this.groups[label] = graphOptions; } }; Legend.prototype.updateGroup = function(label, graphOptions) { - this.graphs[label] = graphOptions; + this.groups[label] = graphOptions; }; Legend.prototype.deleteGroup = function(label) { - if (this.graphs.hasOwnProperty(label)) { - delete this.graphs[label]; - this.redraw(); + if (this.groups.hasOwnProperty(label)) { + delete this.groups[label]; } };