diff --git a/docs/graph2d.html b/docs/graph2d.html index 5f8b8f0a..f14fb807 100644 --- a/docs/graph2d.html +++ b/docs/graph2d.html @@ -238,6 +238,15 @@ groups.add({ an individual css style. + + style + String + no + This field is optional. A style can be used to give groups + an individual css style, and any style tags specified in style will + override the definition in the className style defined in css. + + options JSON object @@ -330,6 +339,12 @@ The options colored in green can also be used as options for the groups. All opt 'bottom' This determines if the shaded area is at the bottom or at the top of the curve. The options are 'bottom' or 'top'. + + shaded.style + String + undefined + Set the style for the shading. This is a css string and it will override the attributes set in the class. + style String @@ -455,13 +470,53 @@ The options colored in green can also be used as options for the groups. All opt true Show or hide the data axis. + + dataAxis.title.left.text + String + undefined + Set the title for the left axis. + + + dataAxis.title.left.style + String + undefined + Set the title style for the left axis. This is a css string and it will override the attributes set in the class. + + + dataAxis.title.right.text + String + undefined + Set the title for the right axis. + + + dataAxis.title.right.style + String + undefined + Set the title style for the right axis. This is a css string and it will override the attributes set in the class. + + + dataAxis.format.left.decimals + Number + undefined + Set the number of decimal points used on the the left axis. If set, this will fix the number of decimal places + displayed after the decimal point. + If undefined, trailing zeros will be removed. + + + dataAxis.format.right.decimals + Number + undefined + Set the number of decimal points used on the the right axis. If set, this will fix the number of decimal places + displayed after the decimal point. + If undefined, trailing zeros will be removed. + groups.visibility Object You can use this to toggle the visibility of groups per graph2D instance. This is different from setting the visibility flag of the groups since this is not communicated across instances of graph2d. Take a look at Example 14 - for more explaination. + for more explanation. diff --git a/examples/graph2d/16_bothAxis_titles.html b/examples/graph2d/16_bothAxis_titles.html new file mode 100644 index 00000000..b8adff81 --- /dev/null +++ b/examples/graph2d/16_bothAxis_titles.html @@ -0,0 +1,215 @@ + + + + Graph2d | Axis Titles and Styling + + + + + + + +

Graph2d | Axis Titles and Styling

+
+ + + + + +
+ This example shows setting a title for the left and right axes. Optionally the example allows the user + to show icons and labels on the left and right axis. + + + + + + + + + + + + + + + + + + + + + + +
Left decimals +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/graph2d/17_dynamicStyling.html b/examples/graph2d/17_dynamicStyling.html new file mode 100644 index 00000000..fed71c38 --- /dev/null +++ b/examples/graph2d/17_dynamicStyling.html @@ -0,0 +1,256 @@ + + + + + + + Graph2d | Dynamic Styling + + + + + + + +

Graph2d | Dynamic Styling Example

+ +
+ This example shows how to programmatically change the styling of a group. While this can also + be done in CSS, this must be statically defined, and the programmatic interface allows the + user to define the look of the graph at runtime. +
+
+ + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Line Color + +
Line Style + +
Line thickness + +
Fill Position + +
Fill Color + +
Fill Opacity + +
Points Shape + +
Points Size + +
Points Color + +
Point Line Thickness + +
Points Fill Color + +
+
+ + + + + diff --git a/lib/DOMutil.js b/lib/DOMutil.js index 38b35c7e..5cc01f6f 100644 --- a/lib/DOMutil.js +++ b/lib/DOMutil.js @@ -139,7 +139,6 @@ exports.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { point.setAttributeNS(null, "cx", x); point.setAttributeNS(null, "cy", y); point.setAttributeNS(null, "r", 0.5 * group.options.drawPoints.size); - point.setAttributeNS(null, "class", group.className + " point"); } else { point = exports.getSVGElement('rect',JSONcontainer,svgContainer); @@ -147,8 +146,12 @@ exports.drawPoint = function(x, y, group, JSONcontainer, svgContainer) { point.setAttributeNS(null, "y", y - 0.5*group.options.drawPoints.size); point.setAttributeNS(null, "width", group.options.drawPoints.size); point.setAttributeNS(null, "height", group.options.drawPoints.size); - point.setAttributeNS(null, "class", group.className + " point"); } + + if(group.options.drawPoints.styles !== undefined) { + point.setAttributeNS(null, "style", group.group.options.drawPoints.styles); + } + point.setAttributeNS(null, "class", group.className + " point"); return point; }; diff --git a/lib/timeline/DataStep.js b/lib/timeline/DataStep.js index e5923aa4..14dc51f1 100644 --- a/lib/timeline/DataStep.js +++ b/lib/timeline/DataStep.js @@ -178,19 +178,59 @@ DataStep.prototype.previous = function() { * Get the current datetime * @return {String} current The current date */ -DataStep.prototype.getCurrent = function() { +DataStep.prototype.getCurrent = function(decimals) { var toPrecision = '' + Number(this.current).toPrecision(5); - if (toPrecision.indexOf(",") != -1 || toPrecision.indexOf(".") != -1) { - for (var i = toPrecision.length-1; i > 0; i--) { - if (toPrecision[i] == "0") { - toPrecision = toPrecision.slice(0,i); + // If decimals is specified, then limit or extend the string as required + if(decimals !== undefined && !isNaN(Number(decimals))) { + // If string includes exponent, then we need to add it to the end + var exp = ""; + var index = toPrecision.indexOf("e"); + if(index != -1) { + // Get the exponent + exp = toPrecision.slice(index); + // Remove the exponent in case we need to zero-extend + toPrecision = toPrecision.slice(0, index); + } + index = Math.max(toPrecision.indexOf(","), toPrecision.indexOf(".")); + if(index === -1) { + // No decimal found - if we want decimals, then we need to add it + if(decimals !== 0) { + toPrecision += '.'; } - else if (toPrecision[i] == "." || toPrecision[i] == ",") { - toPrecision = toPrecision.slice(0,i); - break; + // Calculate how long the string should be + index = toPrecision.length + decimals; + } + else if(decimals !== 0) { + // Calculate how long the string should be - accounting for the decimal place + index += decimals + 1; + } + if(index > toPrecision.length) { + // We need to add zeros! + for(var cnt = index - toPrecision.length; cnt > 0; cnt--) { + toPrecision += '0'; } - else{ - break; + } + else { + // we need to remove characters + toPrecision = toPrecision.slice(0, index); + } + // Add the exponent if there is one + toPrecision += exp; + } + else { + if (toPrecision.indexOf(",") != -1 || toPrecision.indexOf(".") != -1) { + // If no decimal is specified, and there are decimal places, remove trailing zeros + for (var i = toPrecision.length - 1; i > 0; i--) { + if (toPrecision[i] == "0") { + toPrecision = toPrecision.slice(0, i); + } + else if (toPrecision[i] == "." || toPrecision[i] == ",") { + toPrecision = toPrecision.slice(0, i); + break; + } + else { + break; + } } } } diff --git a/lib/timeline/component/DataAxis.js b/lib/timeline/component/DataAxis.js index 31c486f2..74db1a5e 100644 --- a/lib/timeline/component/DataAxis.js +++ b/lib/timeline/component/DataAxis.js @@ -30,6 +30,14 @@ function DataAxis (body, options, svg, linegraphOptions) { customRange: { left: {min:undefined, max:undefined}, right: {min:undefined, max:undefined} + }, + title: { + left: {text:undefined}, + right: {text:undefined} + }, + format: { + left: {decimals: undefined}, + right: {decimals: undefined} } }; @@ -38,7 +46,8 @@ function DataAxis (body, options, svg, linegraphOptions) { this.props = {}; this.DOMelements = { // dynamic elements lines: {}, - labels: {} + labels: {}, + title: {} }; this.dom = {}; @@ -108,7 +117,9 @@ DataAxis.prototype.setOptions = function (options) { 'iconWidth', 'width', 'visible', - 'customRange' + 'customRange', + 'title', + 'format' ]; util.selectiveExtend(fields, this.options, options); @@ -252,7 +263,7 @@ DataAxis.prototype.redraw = function () { var showMinorLabels = this.options.showMinorLabels; var showMajorLabels = this.options.showMajorLabels; - // determine the width and height of the elemens for the axis + // determine the width and height of the elements for the axis props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0; props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0; @@ -280,6 +291,8 @@ DataAxis.prototype.redraw = function () { if (this.options.icons == true) { this._redrawGroupIcons(); } + + this._redrawTitle(orientation); } return changeCalled; }; @@ -326,6 +339,12 @@ DataAxis.prototype._redrawLabels = function () { // do not draw the first label var max = 1; + // Get the number of decimal places + var decimals; + if(this.options.format[orientation] !== undefined) { + decimals = this.options.format[orientation].decimals; + } + this.maxLabelSize = 0; var y = 0; while (max < Math.round(amountOfSteps)) { @@ -335,13 +354,13 @@ DataAxis.prototype._redrawLabels = function () { var isMajor = step.isMajor(); if (this.options['showMinorLabels'] && isMajor == false || this.master == false && this.options['showMinorLabels'] == true) { - this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis minor', this.props.minorCharHeight); + this._redrawLabel(y - 2, step.getCurrent(decimals), orientation, 'yAxis minor', this.props.minorCharHeight); } if (isMajor && this.options['showMajorLabels'] && this.master == true || this.options['showMinorLabels'] == false && this.master == false && isMajor == true) { if (y >= 0) { - this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis major', this.props.majorCharHeight); + this._redrawLabel(y - 2, step.getCurrent(decimals), orientation, 'yAxis major', this.props.majorCharHeight); } this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth); } @@ -359,8 +378,14 @@ DataAxis.prototype._redrawLabels = function () { this.conversionFactor = this.dom.frame.offsetHeight / step.marginRange; } - var offset = this.options.icons == true ? this.options.iconWidth + this.options.labelOffsetX + 15 : this.options.labelOffsetX + 15; - // this will resize the yAxis to accomodate the labels. + // Note that title is rotated, so we're using the height, not width! + var titleWidth = 0; + if(this.options.title[orientation] !== undefined) { + titleWidth = this.props.titleCharHeight; + } + var offset = this.options.icons == true ? Math.max(this.options.iconWidth, titleWidth) + this.options.labelOffsetX + 15 : titleWidth + this.options.labelOffsetX + 15; + + // this will resize the yAxis to accommodate the labels. if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) { this.width = this.maxLabelSize + offset; this.options.width = this.width + "px"; @@ -450,6 +475,36 @@ DataAxis.prototype._redrawLine = function (y, orientation, className, offset, wi } }; +/** + * Create a title for the axis + * @private + * @param orientation + */ +DataAxis.prototype._redrawTitle = function (orientation) { + DOMutil.prepareElements(this.DOMelements.title); + + // Check if the title is defined for this axes + if(this.options.title[orientation] == undefined || this.options.title[orientation].text === undefined) { + return; + } + var title = DOMutil.getDOMElement('div',this.DOMelements.title, this.dom.frame); + title.className = 'yAxis title ' + orientation; + title.innerHTML = this.options.title[orientation].text; + + // Add style - if provided + if(this.options.title[orientation].style !== undefined) { + util.addCssText(title, this.options.title[orientation].style); + } + + if (orientation == 'left') { + title.style.left = this.props.titleCharHeight + 'px'; + } + else { + title.style.right = this.props.titleCharHeight + 'px'; + } + + title.style.width = this.height + 'px'; +}; @@ -486,6 +541,19 @@ DataAxis.prototype._calculateCharSize = function () { this.dom.frame.removeChild(measureCharMajor); } + + if (!('titleCharHeight' in this.props)) { + var textTitle = document.createTextNode('0'); + var measureCharTitle = document.createElement('DIV'); + measureCharTitle.className = 'yAxis title measure'; + measureCharTitle.appendChild(textTitle); + this.dom.frame.appendChild(measureCharTitle); + + this.props.titleCharHeight = measureCharTitle.clientHeight; + this.props.titleCharWidth = measureCharTitle.clientWidth; + + this.dom.frame.removeChild(measureCharTitle); + } }; /** diff --git a/lib/timeline/component/GraphGroup.js b/lib/timeline/component/GraphGroup.js index 5917c79b..f70cd3dd 100644 --- a/lib/timeline/component/GraphGroup.js +++ b/lib/timeline/component/GraphGroup.js @@ -71,6 +71,7 @@ GraphGroup.prototype.update = function(group) { this.content = group.content || 'graph'; this.className = group.className || this.className || "graphGroup" + this.groupsUsingDefaultStyles[0] % 10; this.visible = group.visible === undefined ? true : group.visible; + this.style = group.style; this.setOptions(group.options); }; @@ -88,6 +89,10 @@ GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, icon if (this.options.style == 'line') { path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer); path.setAttributeNS(null, "class", this.className); + if(this.style !== undefined) { + path.setAttributeNS(null, "style", this.style); + } + path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+""); if (this.options.shaded.enabled == true) { fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer); diff --git a/lib/timeline/component/LineGraph.js b/lib/timeline/component/LineGraph.js index 2736079a..54144f0c 100644 --- a/lib/timeline/component/LineGraph.js +++ b/lib/timeline/component/LineGraph.js @@ -1047,6 +1047,9 @@ LineGraph.prototype._drawLineGraph = function (dataset, group) { var svgHeight = Number(this.svg.style.height.replace("px","")); path = DOMutil.getSVGElement('path', this.svgElements, this.svg); path.setAttributeNS(null, "class", group.className); + if(group.style !== undefined) { + path.setAttributeNS(null, "style", group.style); + } // construct path from dataset if (group.options.catmullRom.enabled == true) { @@ -1067,6 +1070,9 @@ LineGraph.prototype._drawLineGraph = function (dataset, group) { dFill = "M" + dataset[0].x + "," + svgHeight + " " + d + "L" + dataset[dataset.length - 1].x + "," + svgHeight; } fillPath.setAttributeNS(null, "class", group.className + " fill"); + if(group.options.shaded.style !== undefined) { + fillPath.setAttributeNS(null, "style", group.options.shaded.style); + } fillPath.setAttributeNS(null, "d", dFill); } // copy properties to path for drawing. diff --git a/lib/timeline/component/css/dataaxis.css b/lib/timeline/component/css/dataaxis.css index 1fe4d71d..279899d5 100644 --- a/lib/timeline/component/css/dataaxis.css +++ b/lib/timeline/component/css/dataaxis.css @@ -44,6 +44,48 @@ width: auto; } +.vis.timeline .dataaxis .yAxis.title{ + position: absolute; + color: #4d4d4d; + white-space: nowrap; + bottom: 20px; + text-align: center; +} + +.vis.timeline .dataaxis .yAxis.title.measure{ + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; + visibility: hidden; + width: auto; +} + +.vis.timeline .dataaxis .yAxis.title.left { + bottom: 0px; + -webkit-transform-origin: left top; + -moz-transform-origin: left top; + -ms-transform-origin: left top; + -o-transform-origin: left top; + transform-origin: left bottom; + -webkit-transform: rotate(-90deg); + -moz-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + -o-transform: rotate(-90deg); + transform: rotate(-90deg); +} + +.vis.timeline .dataaxis .yAxis.title.right { + bottom: 0px; + -webkit-transform-origin: right bottom; + -moz-transform-origin: right bottom; + -ms-transform-origin: right bottom; + -o-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -ms-transform: rotate(90deg); + -o-transform: rotate(90deg); + transform: rotate(90deg); +} .vis.timeline .legend { background-color: rgba(247, 252, 255, 0.65);