score:1

Accepted answer

without paying attention to the readability of the chart, one solution might be to first add a function:

function find_max_date(max,values)
{
for (i=0;i<values.length;i++)
{
if (values[i].temperature==max)
{
return i;
break;
}
}
}

and to use this code:

city.append("text")
      .datum(function(d) {return {name: d.name, max: d3.max(d.values,function(d){return d.temperature}), max_date: d.values[find_max_date(d3.max(d.values,function(d){return d.temperature}),d.values)].date };})
      .attr("transform", function(d) {return "translate(" + x(d.max_date) + "," + y(d.max) + ")"; })
      .attr("x", 3)
      .attr("dy", ".35em")
      .text(function(d) { return d.name; }).attr("fill",function(d) {return color(d.name);});

so basically, this adds max and max_data to the data-object - max by simply using the d3.max-method to find the highest temperature, and max_data by looping through the original data per city and returning the position of the maximum temperature in the array, which is used to access the corresponding date. the color is added the same way as it is added to the lines.

edit: not sure about the "bonus"-question, what should the result look like? lines pointing at the maximum value for each city?

edit2: a possible solution for the bonus-question as i was curious if it was doable, but there might be better ways to do it. i changed different parts of the code, so you can delete the function find_max_date.

at the start of the script i added a global variable

var offset=20;

which serves as variable to control the distance of the labels between each other and the border. then, between the cities-color-domain-part and the scale-domains, i added the following code:

for (i=0;i<cities.length;i++)
{
  cities[i].max=0;
  cities[i].label_off=0;
  for (j=0;j<cities[i].values.length;j++)
  {
    if (cities[i].values[j].temperature>cities[i].max) 
    {
      cities[i].max=cities[i].values[j].temperature;
      cities[i].max_date=cities[i].values[j].date;
    }
  }
}

this does basically the same as we did before with d3.max and the custom function to find the date of the maximum temperature, only this time i would suggest to add the values directly into the cities-variable. cities.label_off will be a value to put some space between labels which would otherwise be overlapping.

next, i added another loop to compare values on the x-scale to check if they overlap. simply put, if the distance between two x-values is smaller than 100 pixels, the label_off-value is set to the offset-value (in this case 20 pixel), otherwise the label_off-value remains at 0. you might want to adjust that, depending on how short/long your labels are.

for (i=0;i<cities.length;i++)
{
  for (j=i+1;j<cities.length;j++)
  {
    if (Math.abs(x(cities[i].max_date)-x(cities[j].max_date))>0 && Math.abs(x(cities[i].max_date)-x(cities[j].max_date))<100) cities[j].label_off=offset;
  }
}

next, i added the offset-variable to the y-domain, just to have a little more space to put the labels in place:

y.domain([
    d3.min(cities, function(c) { return d3.min(c.values, function(v) { return v.temperature; }); }),
    d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.temperature; })+offset; })  
  ]);

and finally the part to add the labels and the lines. as the cities-variable already contains all the necessary information you don't need to use another datum and can just adress them using the property name:

city.append("text")
  .attr("transform", function(d) {return "translate(" + (x(d.max_date)+30+d.label_off) + "," + (offset+d.label_off) + ")"; })
  .attr("x", 3)
  .attr("dy", ".35em")
  .text(function(d) { return d.name; })
  .attr("fill",function(d) {return color(d.name);});

city.append("line")
  .attr("class","line_con")
  .attr("x1",function(d){return x(d.max_date);})
  .attr("y1",function(d){return y(d.max);})
  .attr("x2",function(d){return (x(d.max_date)+30+d.label_off);})
  .attr("y2",function(d){return (offset+d.label_off);})
  .attr("stroke",function(d) {return color(d.name);});

as you can see, the transform just puts the labels on the x-scale at the position of the date of the maximum temperature. then it adds 30, which is an arbitrary value just to move them slightly to the right so they don't sit directly above the value. d.label_off adds 0 if there is no overlapping, and in our case 20 if there is.

on the y-scale i positioned them at the top of the chart with some space between them and the border using the offset. again, if there is overlapping, they are moved down a little bit.

for the lines, it's just connecting the maximum value and the starting point of the label. they use a different css-class to allow for the correspondning color:

.line_con {
  fill: none;
  stroke: none;
  stroke-width: 1.5px;

again, there might be better ways to accomplish it and i'm not sure if this works fine on all sorts of data, but it might give you an idea how to handle it.


Related Query