Issue with x-axis ticks in dc.js - reactjs

I am using d3 v5.9.2, dc v3.0.12 to render a line chart but the ticks in x-axis seems to be having some issue. The data supplied has to be plotted with date along x Axis and value along y-axis since it's a timeline graph.
const line = lineChart(divRef);
const crossfilterDimension = dx.dimension(dimension);
const firstRecord = crossfilterDimension.bottom(1)[0];
const lastRecord = crossfilterDimension.top(1)[0];
line
.dimension(crossfilterDimension)
.x(
scaleTime().domain([
firstRecord[Object.keys(firstRecord)[0]],
lastRecord[Object.keys(lastRecord)[0]]
])
)
.xUnits(timeYear)
.xAxisLabel(xAxis.label)
.yAxisLabel(yAxis.label)
.renderDataPoints({ radius: 2, fillOpacity: 0.8, strokeOpacity: 0.0 })
.group(
count
? crossfilterDimension.group().reduceCount()
: crossfilterDimension.group().reduceSum(group)
);
line
.yAxis()
.ticks(yAxis.ticks)
.tickFormat(yAxis.tickFormat);
return line;

Related

Give different colors to area under and over a specific point on a D3 line chart

I've recently started working with D3 and I am moving all my existing charts over from Chartjs and so far my attempts have been successful. There is this one chart however that I am unable to produce exactly the same way in D3.
So with Chartjs, there's properties built in to the library that we can use to set the colors for values above and below a certain point on a Line chart. Here's what I had used to get the intended chart with Chartjs:
...config,
fill: {
above: '#4E4AFF20',
below: '#FF515114',
target: 'origin'
},
...config
And this is what the chart in Chartjs ended up looking like:
But D3 doesn't seem to have such a thing as far as I can tell. There's only gradients. So here's what I was able to build in D3:
As you can see, this looks way different from what I had earlier with Chartjs. Also notice how the gradient exists in both the line and the colored area underneath. I know it's there because I added it but that's not what I want and everywhere I look, that's the only way people are doing it. I have done countless attempts to fix this to no avail hence now I'm here asking for your help. Here's the D3 code I have right now:
import * as d3 from 'd3';
import { useEffect, useRef } from 'react';
interface Data {
x: number;
y: number;
}
const width = 350;
const height = 117;
const zeroPoint = 0;
const data: Data[] = [
{ x: 0, y: -20 },
{ x: 10, y: -20 },
{ x: 20, y: -20 },
{ x: 40, y: -20 },
{ x: 50, y: -20 },
{ x: 60, y: -20 },
{ x: 70, y: -20 },
{ x: 80, y: 0 },
{ x: 90, y: 20 },
{ x: 100, y: 20 },
{ x: 110, y: 20 },
{ x: 120, y: 20 },
{ x: 130, y: 20 },
{ x: 140, y: 20 },
{ x: 150, y: 20 }
];
export const Chart: React.FC = () => {
const ref = useRef<SVGSVGElement>(null);
const generateLinePath = (
element: d3.Selection<SVGSVGElement, unknown, null, undefined>,
data: Data[],
xScale: d3.ScaleLinear<number, number>,
yScale: d3.ScaleLinear<number, number>
) => {
const lineGenerator = d3
.line<Data>()
.x(d => xScale(d.x))
.y(d => yScale(d.y));
element.append('path').attr('d', lineGenerator(data));
};
const drawZeroLine = (element: d3.Selection<SVGSVGElement, unknown, null, undefined>, yScale: d3.ScaleLinear<number, number>) => {
element
.append('line')
.attr('x1', '0')
.attr('y1', yScale(zeroPoint))
.attr('x2', width)
.attr('y2', yScale(zeroPoint))
.attr('stroke', '#c4c4c4');
};
const createChart = (data: Data[]) => {
const svg = d3.select(ref.current!).attr('viewBox', `0 0 ${width} ${height}`);
svg.selectAll('*').remove();
const [minX, maxX] = d3.extent(data, d => d.x);
const [minY, maxY] = d3.extent(data, d => d.y);
const xScale = d3.scaleLinear().domain([minX!, maxX!]).range([0, width]);
const yScale = d3
.scaleLinear()
.domain([minY!, maxY!])
.range([height, 0]);
svg
.append('linearGradient')
.attr('id', 'line-gradient')
.attr('gradientUnits', 'userSpaceOnUse')
.attr('x1', 0)
.attr('x2', width)
.selectAll('stop')
.data(data)
.join('stop')
.attr('offset', d => xScale(d.x) / width)
.attr('stop-color', d => (d.y < zeroPoint ? '#FF5151' : '#4E4AFF'));
svg
.append('linearGradient')
.attr('id', 'area-gradient')
.attr('gradientUnits', 'userSpaceOnUse')
.attr('x1', xScale(data[0].x))
.attr('x2', xScale(data[data.length - 1].x))
.selectAll('stop')
.data([
{ color: '#FF515110', offset: '0%' },
{ color: '#4E4AFF20', offset: '100%' }
])
.enter()
.append('stop')
.attr('offset', function (d) {
return d.offset;
})
.attr('stop-color', function (d) {
return d.color;
});
svg.attr('stroke', 'url(#line-gradient)').attr('fill', 'url(#area-gradient)');
generateLinePath(svg, data, xScale, yScale);
drawZeroLine(svg, yScale);
};
useEffect(() => {
createChart(data);
}, []);
return <svg ref={ref} />;
};
So there's two problems I am looking to get solved with your help. The more important one is to give different colors to areas under and above the zero line in D3 the way I was able to do with Chartjs and the other one is moving away from gradients and get solid colors without any smooth transitions on both the line and the colored areas underneath.
Alright I managed to recreate the same chart in D3 using a workaround.
So it's not as straightforward as it's in Chartjs but it works pretty well. The idea is to create polygons under and over the line using the same data used to generate the line.
So my chart works like this. The grey line is a straight zero line and the values below zero go under that line with a red color and the ones above are purple. And here's what the Chart data looks like:
data = [
{ x: 0, y: 0 },
{ x: 1, y: 1 },
...
]
Anyways, here's the steps
Generate the scales
const [minX, maxX] = d3.extent(data, d => d.x);
const [minY, maxY] = d3.extent(data, d => d.y);
const xScale = d3.scaleLinear().domain([minX, maxX]).range([0, width]);
const yScale = d3.scaleLinear().domain([minY, maxY]).range([height, 0]);
Generate the line chart using D3's line() function. And don't give any stroke to the generated line.
const lineGenerator = d3.line().x(d => xScale(d.x)).y(d => yScale(d.y));
element.append('path').attr('d', lineGenerator(data));
Add a red SVG polygon that starts from the left side at zero line, then goes to the left bottom, then to where the value starts becoming negative and then finally to where the line reaches the zero line again.
svg.append('polygon').attr(
'points',
`
${xScale(minX - 1)},${yScale(0)} // top left point
${xScale(minX - 1)},${yScale(minY)} // bottom left point
${xScale(data[indexWhereRedPointStartsBecomingPositive].x)},${yScale(data[indexWhereRedPointStartsBecomingPositive].y)} // bottom right point
${xScale(data[indexWhereXReachesZeroLine].x)},${yScale(0)} // top right point
`
)
.attr('fill', 'lightRed')
.attr('stroke', 'darkRed');
Notice how we gave the red stroke to the polygon? That's the reason why we got rid of the stroke from the line and gave it here instead. This is because we need two separate colors (red for below and purple for above) for the chart. The reason why we do minX - 1 is because the stroke is applied to all four sides of the polygon and we want to hide it from the left side so we subtract 1px from the left.
Add another purple SVG polygon that starts from the left side at zero line (where the purple area starts somewhere in the middle), then goes all the way to the right end of the chart and then goes up to the top.
svg.append('polygon').attr(
'points',
`
${xScale(data[indexWhereValueStartsGoingPositive].x)},${yScale(0)}
${width + 1},${yScale(data[data.length - 1].y)}
${width + 1},${yScale(0)}
`
)
.attr('fill', 'lightPurple')
.attr('stroke', 'darkPurple');
Here we do width + 1 to hide the stroke of this purple polygon on the right side the same way we did minX - 1 with the left side of the red box.
So in conclusion, instead of giving stroke to the line generated using d3.line(), give strokes to the two polygons created using the same data that was used to generate the line chart and create the polygons 1px larger than the chart data so the strokes don't appear on the left and right side of the charts.
That's quite a lot I know but I couldn't think of any other way to get the chart to look like this. Anyways, I hope this helps anyone else experiencing a similar problem.

how to align d3 graph x axis tick label with the lower end of y axis?

Could some one please help with d3 ticks alignment?
I want the x axis ticks label in the screenshot below to be aligned to bottom location (-2.00) in this case. I have tried quite a few options, but could not figure out.
Any help is appreciated! Thanks
My code is given at the bottom.
const xScale = d3
.scaleLinear()
.domain([
data && data[0] ? Math.min(...data[0].items.map(d => d.hour)) : 0,
data && data[0] ? Math.max(...data[0].items.map(d => d.hour)) : 0
])
.range([0, width]);
const yScale = d3
.scaleLinear()
.domain([
data && data[0] ? getDataYMin() - 2 * (Math.ceil(Math.abs(getDataYMin())/10)) : 0,
data && data[0] ? getDataYMax() + 4 * (Math.ceil(getDataYMax()/10)): 0
]).range([height, 0]);
const svgEl = d3.select(svgRef.current);
svgEl.selectAll("*").remove(); // Clear svg content before adding new elements
const svg = svgEl
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// Add X grid lines with labels
const xAxis = d3.axisBottom(xScale).ticks(24).tickSize(-height + margin.bottom).tickPadding(10);
const xAxisGroup = svg.append("g").attr("transform", `translate(0, ${height - margin.bottom})`).call(xAxis);
xAxisGroup.select(".domain").remove();
xAxisGroup.selectAll(".tick:first-of-type line").attr("class", "axis_bar").attr("stroke", "black");
xAxisGroup.selectAll(".tick:not(:first-of-type) line").attr("class", "axis_y_tick").attr("stroke", "rgba(155, 155, 155, 0.5)").style("stroke-dasharray", "5 5");
xAxisGroup.selectAll("text").attr("opacity", 0.5).attr("color", "black").attr("font-size", "0.75rem");
// Add Y grid lines with labels
const yAxis = d3.axisLeft(yScale).ticks(10).tickSize(-width).tickFormat(d3.format(".2f"));
const yAxisGroup = svg.append("g").call(yAxis);
yAxisGroup.select(".domain").remove();
yAxisGroup.selectAll(".tick:first-of-type line").attr("class", "axis_bar").attr("stroke", "black");
yAxisGroup.selectAll(".tick:not(:first-of-type) line").attr("class", "axis_y_tick").attr("stroke", "rgba(155, 155, 155, 0.5)").style("stroke-dasharray", "5 5");
yAxisGroup.selectAll("text").attr("opacity", 0.5).attr("color", "black").attr("font-size", "0.75rem");
The dimensions are defined constants and are passed through property to the chart component (this is a React application)
const marginValue: Margin = {
top: 30,
right: 30,
bottom: 30,
left: 60
};
const dimensionsInput : Dimensions = {
width: 1040,
height: 400,
margin: marginValue
};

Moving an object by changing its position in useFrame does not work

I have a code as below. Although I update the sphere position with every frame (useFrame), it does not reflect on my scene. Can someone please help me understand why it will not work.
PS : I am new to this and am trying to do some quick proof of concepts.
function Marble() {
const controls = useControls()
const [sphereRef, sphereApi] = useSphere(() => ({
type: "Dynamic",
mass: 1,
position: [0, 2, 0]
}));
//sphereApi.position.set([0,0,0])
//console.log("SHM sphereAPI position", sphereRef.current.position);
useFrame(() => {
const { forward, backward, left, right, brake, reset } = controls.current
if (forward == true) {
console.log("sphereRef position", sphereRef.current.position);
console.log("sphereAPI position", sphereApi.position);
//console.log("model position", model.current.position)
// sphereApi.velocity.set(2,2,2);
sphereApi.position.set(5,0,0)
// sphereRef.current.position.set(5,0,0);
}
})
return (
<mesh ref={sphereRef} castShadow>
<sphereBufferGeometry attach="geometry" args={[1, 32, 32]}></sphereBufferGeometry>
<meshStandardMaterial color="white" />
</mesh>
);
})
( See it online: Stackblitz )
Doing sphereApi.position.set(5,0,0) on every frame, just sets the sphere position to x=5 on every frame.
So you should create a state first to store the x position, then update it on every frame to +=5, and then set the sphere position to it:
const [sphereX, setSphereX] = useState(0);
useFrame(() => {
setSphereX((sphereX) => sphereX + 0.05); // set state x position to +0.05 (5 is fast)
sphereApi.position.set(sphereX, 0, 0); // apply the state to the sphere position
});
Also make sure to use allowSleep={false} since changing position directly isn't a physical movement, so the physical scene may get sleep.
Online: Stackblitz

React Leaflet - Amend X/Y LatLng to work from center rather than bottom left using CRS

Hey has anybody managed to change the X/Y axis start points in react leaflet? I need my co-ordinates to work from the middle of the map rather than the bottom left using CRS. Below is what I have so far, any point in the right direction would be great!
componentDidMount() {
if ( __CLIENT__ ) {
Leaflet = require('leaflet');
LeafletCRS = Leaflet.CRS.Simple;
this.setState({
leafletLoaded: true
});
}
}
render() {
const bounds = [[0, 0], [600, 600]];
return (
<div className="columns small-12 medium-6">
<Map
style={ { width:'600px',height: '600px' } }
minZoom={ 1 }
center= { [0, 0] }
maxZoom={ 2 }
bounds={ bounds }
crs={ LeafletCRS }
>
<ImageOverlay
url="IMG HERE"
bounds={ bounds }
/>
<Circle center= { [0, 0] } radius={5} fillColor="blue" />
</Map>
</div>
)
Best example of this is - http://plnkr.co/edit/5SQqp7SP4nf8muPM5iso?p=preview&preview
L.CRS.MySimple = L.extend({}, L.CRS.Simple, {
// At zoom 0, tile 268x268px should represent the entire "world" of size 8576x8576.
// scale is therefore 8576 / 268 = 32 (use the reverse in transformation, i.e. 1/32).
// We want the center of tile 0/0/0 to be coordinates [0, 0], so offset is 8576 * 1/32 / 2 = 268 / 2 = 134.
transformation: new L.Transformation(1 / 32, 134, -1 / 32, 134)
});
You just need to amend the maths scale to suit you

OpenLayer 4 draw arrows on map

I have a map with multiple points I want to draw arrows that point to these locations from the border of the map. The arrow should dynamically update its position on the screen when to users pan or zoom the map.
How can one draw an arrow on a map that point to location?
You can draw regular lines to the points and apply arrow style to them as shown in this example.
You just need to place the arrow at the end coordinate instead of applying it on each segment.
var styleFunction = function (feature) {
var geometry = feature.getGeometry();
var styles = [
// Linestring
new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#ffcc33',
width: 2
})
})
];
var coordinates = geometry.getCoordinates();
var length = coordinates.length;
// Last two coordinates for calculating rotation
var end = coordinates[length - 1];
var start = coordinates[length - 2];
var dx = end[0] - start[0];
var dy = end[1] - start[1];
var rotation = Math.atan2(dy, dx);
// Arrow
styles.push(new ol.style.Style({
geometry: new ol.geom.Point(end),
image: new ol.style.Icon({
src: 'https://openlayers.org/en/v4.6.5/examples/data/arrow.png',
anchor: [0.75, 0.5],
rotateWithView: true,
rotation: -rotation
})
}));
return styles;
};

Resources