How to rotate x-axis text in dimple.js? - javascript

This is my dimple bar chart (powered by d3):
var sidechart = new dimple.chart(chart_svg, data);
sidechart.setBounds(60, 30, 200, 300)
var x = sidechart.addCategoryAxis("x", "Long name");
sidechart.addMeasureAxis("y", "Measure");
sidechart.addSeries(["Short name", "Long name"], dimple.plot.bar);
sidechart.draw();
Text on x-axis is quite long and by default dimple rotates it so it is displayed vertically. I want to rotate it by 45 degrees instead. Using d3 this would be done by doing this:
myAxis.attr("transform", "rotate(-45)");
Unfortunately I am unable to find a similar way of doing that in dimple. Any help will be appreciated.

You can get hold of the shapes once the draw method has been called and set a transform:
...
sidechart.draw();
x.shapes.selectAll("text").attr("transform", "rotate(-45)");
However dimple already uses the transform to move the labels between the ticks and do the rotate, so you might want to append the transform like this:
...
sidechart.draw();
x.shapes.selectAll("text").attr("transform",
function (d) {
return d3.select(this).attr("transform") + " rotate(-45)";
});
I tried this and it offset the labels from where I was expecting, so you may need to add a translate in, but you'll probably need to find an appropriate offset for your own chart, I've used 20 here as an example:
...
sidechart.draw();
x.shapes.selectAll("text").attr("transform",
function (d) {
return d3.select(this).attr("transform") + " translate(0, 20) rotate(-45)";
});

Related

Data label not rendered on sapui5 viz dual y-axis graph when line and column intersect

I have a requirement to show data labels of two graphs on the same axes.
Only when they intersect, one of the two labels won't show. This can be demonstrated below:
As you can see on the 2nd, 5th and 6th columns from the left with values 0%, 7% and 8% respectively
only the orange line values are shown but the blue column values are missing.
This is the final html of the graph after rendering:
So data-datapoint-id 142, 145 and 146 are missing from the html.
I tried using the plotArea.dataLabel.renderer function as a manipulation of what was proposed here but nothing changed, still not rendering.
Anyone encountered a similar problem? Is that a sapui5 issue or can it be fixed by manually inserting the labels into the HTML if so how?
Thanks,
Ori
Using SVG text and jQuery I managed to manually insert the labels into the top middle of the blue rectangle columns.
This is the result, not perfect but works:
and this is the code:
chart.setVizProperties({
plotArea: {
dataLabel: {
formatString: {'פחת כללי': FIORI_PERCENTAGE_FORMAT_2},
renderer: function (oLabel) {
// Create empty text node to be returned by the function such that the label won't be rendered automatically
var node = document.createElement("text");
if (oLabel.ctx.measureNames === "כמות פחת כללי") {
var kamutLabelIdx = oLabel.ctx._context_row_number;
// Use jQuery and SVG to manipulate the HTML
var kamutLabelQuery = '[data-id=\"' + kamutLabelIdx + '\"]';
var kamutColumn = $(kamutLabelQuery)[0];
// Create text element as child of the column
kamutColumn.innerHTML += "<text>" + oLabel.text + "</text>";
var labelNode = $(kamutLabelQuery += ' text');
// Set the label position to be at the middle of the column
const labelLength = 60;
const xPos = (labelLength + oLabel.dataPointWidth) / 2;
const yPos = oLabel.styles['font-size'];
labelNode.attr({
"textLength" : labelLength,
"x" : xPos,
"y" : yPos,
"font-size" : yPos
});
return node;
}
}
}
}
});
The oLabel parameter of the renderer function provides useful info about the data label to be created:
I still wonder if that's a bug with sapui5 vizframe and if there is a simpler way to do this.
Please let me know of your thoughts

'Click anywhere' event in plotly

I am trying to implement a 'click anywhere' feature in plotly so that I can get the coordinates on user clicks anywhere on plotly charts. The current "official" plotly functionality only works when users click on a plotted data point, but I want to register clicks e.g. on the background white canvas.
Shiny click events for plots can do that, but surprisingly this doesn't seem to exist yet in plotly.
I made some research and found the following codepen implementation which comes close: https://codepen.io/tim-logan/pen/yLXgpyp
#JS
var d3 = Plotly.d3;
var gd = document.getElementById('graph');
Plotly.plot('graph', [{
y: [2, 1, 2]
}])
.then(attach);
function attach() {
var xaxis = gd._fullLayout.xaxis;
var yaxis = gd._fullLayout.yaxis;
var l = gd._fullLayout.margin.l;
var t = gd._fullLayout.margin.t;
gd.addEventListener('mousemove', function(evt) {
var xInDataCoord = xaxis.p2c(evt.x - l);
var yInDataCoord = yaxis.p2c(evt.y - t);
console.log(evt.x, l)
Plotly.relayout(gd, 'title', ['x: ' + xInDataCoord, 'y : ' + yInDataCoord].join('<br>'));
});
}
# HTML
<head>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<div style="padding-left: 100px; margin-left: 100px">
<div id="graph"></div>
</div>
</body>
However, as the creator of the codepen pointed out, the click coordinates are not precises as they don't take into account the padding / margin:
One issue with the example workaround is that it doesn't account for padding/margin in parent objects.
I've taken the example and added a parent div that has both padding and margin on the left side, this then skews the value of the x-axis value, and is clearly something that would need to be handled in such a solution as is suggested here.
According to the flot documentation this should be possible for plot objects by subtracting the plot offset from the coordinates:
https://github.com/flot/flot/blob/master/API.md#plot-methods
See e.g. the following excerpt:
Various things are stuffed inside an axis object, e.g. you could use getAxes().xaxis.ticks to find out what the ticks are for the xaxis. Two other useful attributes are p2c and c2p, functions for transforming from data point space to the canvas plot space and back. Both returns values that are offset with the plot offset.
or:
offset()
Returns the offset of the plotting area inside the grid relative to the document, useful for instance for calculating mouse positions (event.pageX/Y minus this offset is the pixel position inside the plot).
I tried to implement a workaround based on offset(), but since my js knownledge is not too good I couldn't get a working version of the code.
Would somebody be able to provide a workaround to fix the offset problem?
I managed to remove the offset by getting the parent box's dimensions. See following example which fixed the above codepen:
var d3 = Plotly.d3;
var gd = document.getElementById('graph');
Plotly.plot('graph', [{
y: [2, 1, 2]
}])
.then(attach);
function attach() {
gd.addEventListener('mousemove', function(evt) {
var bb = evt.target.getBoundingClientRect();
var x = gd._fullLayout.xaxis.p2d(evt.clientX - bb.left);
var y = gd._fullLayout.yaxis.p2d(evt.clientY - bb.top);
Plotly.relayout(gd, 'title', ['x: ' + x, 'y : ' + y].join('<br>'));
});
}
Fixed codepen here: https://codepen.io/flaviofusero/pen/BaZRgzO
Adapted from sleighsoft's implementation here: plotly click events from anywhere on the plot

D3js button onclick function to zoom the graph for specific timeframe

Expectation
the initial graph showed the full data of csv and the expected output is when i clicked on the button below the x-axis, let's say i click on the '1 hour' button, then the graph will rescale according to data for the first 1 hour, same goes for other buttons.
Problem
The current problem is when i clicked on the button, the graph will rescale to middle position(random data) instead of specific position( specific data, exp( first 1 hour, 3 hours, and etc) that stated on the button. Here is the code for me click on the button to zoom the graph to specific timeframe.
function transition(zoomLevel) {
svg.transition()
.call(zoom.scaleTo, zoomLevel);
}
function timezoom(){
x.domain([new Date(2017,1,11),new Date(2017,1,11)]).range([0,60]);
y.domain([95000,d3.max(data, function(d) { return d.AGV_Mileage; })]);
x2.domain(x.domain());
y2.domain(y.domain());
var svg = d3.select("body").transition();
//
}
<input type="button" id='1hr' onclick="timezoom()" value="1 Hour" />
<input type="button" id='3hr' onclick="transition(1.8)" value="3 Hours"/>
The zoom's behavior is demonstrated in the plunker editor - https://plnkr.co/edit/MY1nP4Mnv4WeCpHCxLvD?p=preview
Please enlighten me and give me a rough idea on this.
You have to specify the translate as well the scale on the transform call.
function transition(newRange) {
var rgh = d3.timeHour.count(initialDomain[0], initialDomain[1]);
var k = rgh/newRange;
var t = d3.zoomIdentity.translate(0,0).scale(k);
svg.transition()
.call(zoom.transform, t);
}
You should also save the initial domain in order to calculate the new scale:
initialDomain = d3.extent(data, function(d) { return d.Timestamp; });
Here is the updated plnkr

nvd3 line chart issue

Here, I have used line chart from nvd3. I am trying to fix a few issues with this chart
1. This is an interactive chart, but even if i hover over a point where there are no points from the data, it still shows up the x and y-points.
2. x-axis and y-axis lines do NOT show up. Also the interactive guide doesn't come up in a rectangular box outlined.
3. The x-axis gets cut off at the end (see 14:29:19 being cut off at the end) no matter how much i increase the width to.
4. I am trying to remove the fill that we see above the line graph but styling changes made by me resulted in no success.
code that i use to fetch the line chart
var redraw = function(testData) {
var dataT = [{
key : 'Character Count',
values : testData,
color : '#00AEEF',
area : false
}];
var margin = {
top : 80,
right : 20,
bottom : 80,
left : 50
}, width = 1000 - (margin.left + margin.right), height = 600 - (margin.top + margin.bottom), x = d3.time.scale().range([0, width]), y = d3.scale.linear().range([height, 0]);
x.domain(d3.extent(dataT[0].values, function(d) {
return d3.time.format('%X')(new Date(d.TimeStamp));
}));
y.domain([0, d3.max(dataT[0].values, function(d) {
return d.CharCount;
})]);
nv.addGraph(function() {
chart = nv.models.lineChart().transitionDuration(350).useInteractiveGuideline(true)
.showLegend(true).showYAxis(true).showXAxis(true).x(function(d) {
return new Date(d.TimeStamp);
}).y(function(d) {
return d.CharCount;
});
chart.xAxis.axisLabel('Time').tickFormat(function(d) {
return d3.time.format('%X')(new Date(d));
}).scale(x).orient("bottom");
chart.yAxis.axisLabel('Character Count').tickFormat(d3.format(',')).scale(y);
chart.lines.forceY([0]);
d3.select('#chart2 svg')//.append("g")
.datum(dataT).attr('height', height).attr('width', width).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
};
redraw(data)--data fetched froma service ith dateTimeStamp in x-axis and integer in y-axis points.
Please help!
This is an interactive chart, but even if i hover over a point where there are no points from the data, it still shows up the x and
y-points. 2. x-axis and y-axis lines do NOT show up. Also the
interactive guide doesn't come up in a rectangular box outlined.
Adding corresponding javascript file in nvd3 zip file should solve the problem(line.js, linechart.js and corresponding css file)
3.The x-axis gets cut off at the end (see 14:29:19 being cut off at the end) no matter how much i increase the width to.
This is because you have not set the width and height of the chart in your codes to generate the chart
you should do
chart.width(width).height(height)
4.I am trying to remove the fill that we see above the line graph but styling changes made by me resulted in no success
This I am not sure how to solve as I do not have your dataset, so I not able to reproduce the problem
Here is my test code

d3.js svg image: scale from center of rectangle

I'm using d3 to populate a cartesian plane with a bunch of svg:image elements spread out over different coordinates.
I'd like to add mouserover and mouseout logic that zooms the image the mouse is over in and lightens the opacity of the others. I'm filtering my selection on mouseover to only select the desired element and everything is working great, except my scaling logic doesn't seem to get the desired effect. The images expand downward and to the right rather than in the outward from the diagonal center.
Here's what I've tried:
transform: scale(1.5) Which expands, but also totally shifts the image's position
transform: translate(-(width/2), -(height/2)) combined with scale, which does the same but from a different starting position
Changing the x and y coords to ones adjusted for half widths and heights, which has the same effect.
Is there no text-anchor equivalent for image elements with which I could set an "anchor point" to scale from? I'm not sure what the html svg parlance is, but I guess I'm thinking of something similar to the anchor points a lot of vector editors have.
Current approach, mouseover handler:
function fade(dir){
return function(d){
var others = svg.selectAll("image.movie_cover")
.filter(function(g,i){
return g != d
})
.transition().duration(800)
.style("opacity",.3);
var single = svg.selectAll("image.movie_cover")
.filter(function(g,i){
return g === d;
})
.transition().duration(900)
.attr("transform", "translate(-40,-40) scale(1.4)")
var title = keys[coords.indexOf(d)];
var url = "/static/eshk/"+hash+"_images/" + title + ".jpg";
tt.transition()
.duration(200)
.style("opacity", .9);
tt.html(title)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}
}
Using this method, the images move inconsistent distances despite all being the same size.
Set up: A 50 x 50 box at 200, 200. It needs to transition to a 100 x 100. It is 50 larger and wider, so needs to move back and up 25, eg 175, 175. Replace hard coded values with functions that look up the current width on mouse hover to calculate the exact values.
d3.select('svg').append('rect');
rect = d3.select('rect');
rect.attr({
height: 50,
width: 50,
x: 200,
y: 200,
color: 'steelblue'
})
.transition()
.attr({
width: 100,
height: 100,
x: 175,
y: 175
});
This could also be done without modifying width or position attributes:
images.on("mouseover", function(d, i) {
var selection = d3.select(this);
var offsetX = parseFloat(selection.attr("x"))+
parseFloat(selection.attr("width")/2.0);
var offsetY = parseFloat(selection.attr("y"))+
parseFloat(selection.attr("height")/2.0);
selection.attr({
transform:"translate("+offsetX+ ","+offsetY+") "+
"scale(1.2) "+
"translate(-"+offsetX+",-"+offsetY+ ")"
});
});
And on mouse out, you'd just set the transform to null to remove it.
Basically, this is just translating the center point to the origin, scaling around that, and translating back to the correct position. Remember that transforms are applied in reverse order (right to left).
I think you were on the right track with the use of translate with scale, but translating back and forth from the origin is what allows it to work while remaining centered at the original location.

Categories