Related
So i am traying to make a cycle that gives different sankey diagram the thing is due to the plotly optimization the node are in different positions. I will like to set the standard order to be [Formal, Informal, Unemployed, Inactive]
import matplotlib.pyplot as plt
import pandas as pd
import plotly.graph_objects as go
df = pd.read_csv(path, delimiter=",")
Lista_Paises = df["code"].unique().tolist()
Lista_DF = []
for x in Lista_Paises:
DF_x = df[df["code"] == x]
Lista_DF.append(DF_x)
def grafico(df):
df = df.astype({"Source": "category", "Value": "float", "Target": "category"})
def category(i):
if i == "Formal":
return 0
if i == "Informal":
return 1
if i == "Unemployed":
return 2
if i == "Inactive":
return 3
def color(i):
if i == "Formal":
return "#9FB5D5"
if i == "Informal":
return "#E3EEF9"
if i == "Unemployed":
return "#E298AE"
if i == "Inactive":
return "#FCEFBC"
df['Source_cat'] = df["Source"].apply(category).astype("int")
df['Target_cat'] = df["Target"].apply(category).astype("int")
# df['Source_cat'] = LabelEncoder().fit_transform(df.Source)
# df['Target_cat'] = LabelEncoder().fit_transform(df.Target)
df["Color"] = df["Source"].apply(color).astype("str")
df = df.sort_values(by=["Source_cat", "Target_cat"])
Lista_Para_Sumar = df["Source_cat"].nunique()
Lista_Para_Tags = df["Source"].unique().tolist()
Suma = Lista_Para_Sumar
df["out"] = df["Target_cat"] + Suma
TAGS = Lista_Para_Tags + Lista_Para_Tags
Origen = df['Source_cat'].tolist()
Destino = df["out"].tolist()
Valor = df["Value"].tolist()
Color = df["Color"].tolist()
return (TAGS, Origen, Destino, Valor, Color)
def Sankey(TAGS: object, Origen: object, Destino: object, Valor: object, Color: object, titulo: str) -> object:
label = TAGS
source = Origen
target = Destino
value = Valor
link = dict(source=source, target=target, value=value,
color=Color)
node = dict(x=[0, 0, 0, 0, 1, 1, 1, 1], y=[1, 0.75, 0.5, 0.25, 0, 1, 0.75, 0.5, 0.25, 0], label=label, pad=35,
thickness=10,
color=["#305CA3", "#C1DAF1", "#C9304E", "#F7DC70", "#305CA3", "#C1DAF1", "#C9304E", "#F7DC70"])
data = go.Sankey(link=link, node=node, arrangement='snap')
fig = go.Figure(data)
fig.update_layout(title_text=titulo + "-" + "Mujeres", font_size=10, )
plt.plot(alpha=0.01)
titulo_guardar = (str(titulo) + ".png")
fig.write_image("/Users/agudelo/Desktop/GRAFICOS PNUD/Graficas/MUJERES/" + titulo_guardar, engine="kaleido")
for y in Lista_DF:
TAGS, Origen, Destino, Valor, Color = grafico(y)
titulo = str(y["code"].unique())
titulo = titulo.replace("[", "")
titulo = titulo.replace("]", "")
titulo = titulo.replace("'", "")
Sankey(TAGS, Origen, Destino, Valor, Color, titulo)
The expected result should be.
The expected result due to the correct order:
The real result i am getting is:
I had a similar problem earlier. I hope this will work for you. As I did not have your data, I created some dummy data. Sorry about the looooong explanation. Here are the steps that should help you reach your goal...
This is what I did:
Order the data and sort it - used pd.Categorical to set the order and then df.sort to sort the data so that the input is sorted by source and then destination.
For the sankey node, you need to set the x and y positions. x=0, y=0 starts at top left. This is important as you are telling plotly the order you want the nodes. One weird thing is that it sometimes errors if x or y is at 0 or 1. Keep it very close, but not the same number... wish I knew why
For the other x and y entries, I used ratios as my total adds up to 285. For eg. Source-Informal starts at x = 0.001 and y = 75/285 as Source-Formal = 75 and this will start right after that
Based on step 1, the link -> source and destination should also be sorted. But, pls do check.
Note: I didn't color the links, but think you already have achieved that...
Hope this helps resolve your issue...
My data - sankey.csv
source,destination,value
Formal,Formal,20
Formal,Informal, 10
Formal,Unemployed,30
Formal,Inactive,15
Informal,Formal,20
Informal,Informal,15
Informal,Unemployed,25
Informal,Inactive,25
Unemployed,Formal,5
Unemployed,Informal,10
Unemployed,Unemployed,10
Unemployed,Inactive,5
Inactive,Formal,30
Inactive,Informal,20
Inactive,Unemployed,20
Inactive,Inactive,25
The code
import plotly.graph_objects as go
import pandas as pd
df = pd.read_csv('sankey.csv') #Read above CSV
#Sort by Source and then Destination
df['source'] = pd.Categorical(df['source'], ['Formal','Informal', 'Unemployed', 'Inactive'])
df['destination'] = pd.Categorical(df['destination'], ['Formal','Informal', 'Unemployed', 'Inactive'])
df.sort_values(['source', 'destination'], inplace = True)
df.reset_index(drop=True)
mynode = dict(
pad = 15,
thickness = 20,
line = dict(color = "black", width = 0.5),
label = ['Formal', 'Informal', 'Unemployed', 'Inactive', 'Formal', 'Informal', 'Unemployed', 'Inactive'],
x = [0.001, 0.001, 0.001, 0.001, 0.999, 0.999, 0.999, 0.999],
y = [0.001, 75/285, 160/285, 190/285, 0.001, 75/285, 130/285, 215/285],
color = ["#305CA3", "#C1DAF1", "#C9304E", "#F7DC70", "#305CA3", "#C1DAF1", "#C9304E", "#F7DC70"])
mylink = dict(
source = [ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 ],
target = [ 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7 ],
value = df.value.to_list())
fig = go.Figure(data=[go.Sankey(
arrangement='snap',
node = mynode,
link = mylink
)])
fig.update_layout(title_text="Basic Sankey Diagram", font_size=20)
fig.show()
The output
i would like to print a real time 3D Plot of the mediapipe 3D Landmarks.
I am able to get the holistic as shown below in real time from a Video File:
Now i would like to plot the real time 3D Plot like this:
The code i used to plot the real time holistic is shown below. How i am able to plot the real time Plot only from the holistic part ?
import cv2
import mediapipe as mp
import urllib.request
import numpy as np
import pickle
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import animation
import PyQt5
from PIL import Image
from IPython.display import Video
import nb_helpers
mp_drawing = mp.solutions.drawing_utils
mp_holistic = mp.solutions.holistic
file = 'walking.mp4'
cap = cv2.VideoCapture(file)
# Initiate holistic model
with mp_holistic.Holistic(min_detection_confidence=0.5, min_tracking_confidence=0.5) as holistic:
while cap.isOpened():
ret, frame = cap.read()
# Recolor Feed
image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
image.flags.writeable = False
# Make Detections
results = holistic.process(image)
# print(results.face_landmarks)
# face_landmarks, pose_landmarks, left_hand_landmarks, right_hand_landmarks
# Recolor image back to BGR for rendering
image.flags.writeable = True
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
# 1. Draw face landmarks
mp_drawing.draw_landmarks(image, results.face_landmarks, mp_holistic.FACEMESH_TESSELATION,
mp_drawing.DrawingSpec(color=(80,110,10), thickness=1, circle_radius=1),
mp_drawing.DrawingSpec(color=(80,256,121), thickness=1, circle_radius=1)
)
# 2. Right hand
mp_drawing.draw_landmarks(image, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS,
mp_drawing.DrawingSpec(color=(80,22,10), thickness=2, circle_radius=4),
mp_drawing.DrawingSpec(color=(80,44,121), thickness=2, circle_radius=2)
)
# 3. Left Hand
mp_drawing.draw_landmarks(image, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS,
mp_drawing.DrawingSpec(color=(121,22,76), thickness=2, circle_radius=4),
mp_drawing.DrawingSpec(color=(121,44,250), thickness=2, circle_radius=2)
)
# 4. Pose Detections
mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_holistic.POSE_CONNECTIONS,
mp_drawing.DrawingSpec(color=(245,117,66), thickness=2, circle_radius=4),
mp_drawing.DrawingSpec(color=(245,66,230), thickness=2, circle_radius=2)
)
cv2.imshow('Raw Webcam Feed', image)
if cv2.waitKey(10) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
I would be grateful for any help !
Greets!
You can try the below code , which I got from https://github.com/Kazuhito00/mediapipe-python-sample/blob/main/sample_pose.py
Below is the function you can include to plot the realtime 3d landmarks
def plot_world_landmarks(
plt,
ax,
landmarks,
visibility_th=0.5,
):
landmark_point = []
for index, landmark in enumerate(landmarks.landmark):
landmark_point.append(
[landmark.visibility, (landmark.x, landmark.y, landmark.z)])
face_index_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
right_arm_index_list = [11, 13, 15, 17, 19, 21]
left_arm_index_list = [12, 14, 16, 18, 20, 22]
right_body_side_index_list = [11, 23, 25, 27, 29, 31]
left_body_side_index_list = [12, 24, 26, 28, 30, 32]
shoulder_index_list = [11, 12]
waist_index_list = [23, 24]
# 顔
face_x, face_y, face_z = [], [], []
for index in face_index_list:
point = landmark_point[index][1]
face_x.append(point[0])
face_y.append(point[2])
face_z.append(point[1] * (-1))
# 右腕
right_arm_x, right_arm_y, right_arm_z = [], [], []
for index in right_arm_index_list:
point = landmark_point[index][1]
right_arm_x.append(point[0])
right_arm_y.append(point[2])
right_arm_z.append(point[1] * (-1))
# 左腕
left_arm_x, left_arm_y, left_arm_z = [], [], []
for index in left_arm_index_list:
point = landmark_point[index][1]
left_arm_x.append(point[0])
left_arm_y.append(point[2])
left_arm_z.append(point[1] * (-1))
# 右半身
right_body_side_x, right_body_side_y, right_body_side_z = [], [], []
for index in right_body_side_index_list:
point = landmark_point[index][1]
right_body_side_x.append(point[0])
right_body_side_y.append(point[2])
right_body_side_z.append(point[1] * (-1))
# 左半身
left_body_side_x, left_body_side_y, left_body_side_z = [], [], []
for index in left_body_side_index_list:
point = landmark_point[index][1]
left_body_side_x.append(point[0])
left_body_side_y.append(point[2])
left_body_side_z.append(point[1] * (-1))
# 肩
shoulder_x, shoulder_y, shoulder_z = [], [], []
for index in shoulder_index_list:
point = landmark_point[index][1]
shoulder_x.append(point[0])
shoulder_y.append(point[2])
shoulder_z.append(point[1] * (-1))
# 腰
waist_x, waist_y, waist_z = [], [], []
for index in waist_index_list:
point = landmark_point[index][1]
waist_x.append(point[0])
waist_y.append(point[2])
waist_z.append(point[1] * (-1))
ax.cla()
ax.set_xlim3d(-1, 1)
ax.set_ylim3d(-1, 1)
ax.set_zlim3d(-1, 1)
ax.scatter(face_x, face_y, face_z)
ax.plot(right_arm_x, right_arm_y, right_arm_z)
ax.plot(left_arm_x, left_arm_y, left_arm_z)
ax.plot(right_body_side_x, right_body_side_y, right_body_side_z)
ax.plot(left_body_side_x, left_body_side_y, left_body_side_z)
ax.plot(shoulder_x, shoulder_y, shoulder_z)
ax.plot(waist_x, waist_y, waist_z)
plt.pause(.001)
return
I am attempting to find a way to visualize the separate regions/phases of the MJO. I believe one way to do so would be by plotting the longitude lines that separate each phase region (at roughly 60E, 80E, 100E, 120E, 140E, 160E, 180), but I am unsure if it is possible to add to my existing plots.
I am using GRID-Sat B1 data from NCEI. Here is what my current code looks like:
import matplotlib.pyplot as plt
from metpy.plots import declarative, colortables
import cartopy.crs as ccrs
import xarray as xr
file = "GRIDSAT-B1.2003.11.23.00.v02r01.nc"
dataset = xr.open_dataset(file)
vtime = dataset.time.values.astype('datetime64[s]').astype('O')
date_long = vtime[0]
date = date_long.strftime("%d-%b-%Y-%HZ")
# Create water vapor image
img = declarative.ImagePlot()
img.data = dataset
img.field = 'irwvp'
img.colormap = 'WVCIMSS_r'
img.image_range = (180, 280)
panel = declarative.MapPanel()
panel.layers = ['coastline', 'borders']
panel.title = f'GridSat-B1 (Water Vapor Imagery): {date}'
panel.projection = (ccrs.Mollweide(central_longitude=-240))
panel.area = ([-370, -140, -30, 30])
panel.layout = (2, 1, 2)
panel.plots = [img]
# Create the IR image
img2 = declarative.ImagePlot()
img2.data = dataset
img2.field = 'irwin_cdr'
img2.colormap = 'turbo_r' #maybe use cubehelix instead?
img2.image_range = (180, 300)
panel2 = declarative.MapPanel()
panel2.layers = ['coastline', 'borders']
panel2.title = f'GridSat-B1 (Infrared Imagery): {date}'
panel2.projection = (ccrs.Mollweide(central_longitude=-240))
panel2.area = ([-370, -140, -30, 30])
panel2.layout = (2, 1, 1)
panel2.plots = [img2]
# Plot both panels in one figure
pc = declarative.PanelContainer()
pc.size = (20, 14)
pc.panels = [panel, panel2]
pc.show()
Here is the current output that is created when I run the script:
Nov03.png
Any help/suggestions are appreciated - thanks in advance!
There's nothing built into MetPy's declarative interface, but fortunately the MapPanel objects expose a .ax attribute that gives you a Matplotlib Axes object and all its plotting methods:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import metpy.plots as mpplots
import numpy as np
import xarray as xr
file = "/Users/rmay/Downloads/GRIDSAT-B1.2003.11.23.00.v02r01.nc"
dataset = xr.open_dataset(file)
vtime = dataset.time.values.astype('datetime64[s]').astype('O')
date_long = vtime[0]
date = date_long.strftime("%d-%b-%Y-%HZ")
# Create water vapor image
img = mpplots.ImagePlot()
img.data = dataset
img.field = 'irwvp'
img.colormap = 'WVCIMSS_r'
img.image_range = (180, 280)
panel = mpplots.MapPanel()
panel.layers = ['coastline', 'borders']
panel.title = f'GridSat-B1 (Water Vapor Imagery): {date}'
panel.projection = ccrs.Mollweide(central_longitude=-240)
panel.area = (-370, -140, -30, 30)
panel.layout = (2, 1, 2)
panel.plots = [img]
# Create the IR image
img2 = mpplots.ImagePlot()
img2.data = dataset
img2.field = 'irwin_cdr'
img2.colormap = 'turbo_r' #maybe use cubehelix instead?
img2.image_range = (180, 300)
panel2 = mpplots.MapPanel()
panel2.layers = ['coastline', 'borders']
panel2.title = f'GridSat-B1 (Infrared Imagery): {date}'
panel2.projection = ccrs.Mollweide(central_longitude=-240)
panel2.area = (-370, -140, -30, 30)
panel2.layout = (2, 1, 1)
panel2.plots = [img2]
# Plot both panels in one figure
pc = mpplots.PanelContainer()
pc.size = (20, 14)
pc.panels = [panel, panel2]
lons = np.array([60, 80, 100, 120, 140, 160, 180]).reshape(1, -1)
lats = np.linspace(-90, 90).reshape(-1, 1)
# Match up the arrays into 2xN arrays fit to plot in call
lons, lats = np.broadcast_arrays(lons, lats)
# Needs to be *after* the panels are assigned to a PanelContainer
# Using Geodetic gives lines interpolated on the curved globe
panel.ax.plot(lons, lats, transform=ccrs.Geodetic(), color='black', linewidth=3)
panel2.ax.plot(lons, lats, transform=ccrs.Geodetic(), color='black', linewidth=3)
pc.show()
(Note: it's not recommended to import from metpy's declarative module directly since that's an implementation detail subject to change--just get things from metpy.plots). So this is using Matplotlib's standard call to plot to draw the lines. Another option would be to use CartoPy's Gridliner.
I am trying to efficiently and evenly distribute events over a year. For each year I have about 10^6 events. Each event should be assigned a date. Below I posted my approach which is quite slow. Do you see a way of speeding this up?
for year in range(start_year, end_year + 1):
for evt in range(events_this_year):
event_date = (datetime.datetime.strptime(str(year) + "-01-01", "%Y-%m-%d") + datetime.timedelta(days=365 * evt / events_this_year)).strftime("%Y-%m-%d")
You transition back and forth from datetime to str, that's the heavy part.
First of all, you can create the year datetime once in the outer loop and not everytime inside, this will already improves performance by ~3.5x (on my machine).
keeping the results as datetime values instead of strings (if that's OK for you) give a 110x(!!) performance boost:
import datetime
from timeit import timeit
start_year = 2010
end_year = 2020
events_this_year = 10 ** 5
def using_strptime():
result = []
for year in range(start_year, end_year + 1):
for evt in range(events_this_year):
event_date = (datetime.datetime.strptime(str(year) + "-01-01", "%Y-%m-%d") + datetime.timedelta(
days=365 * evt / events_this_year)).strftime("%Y-%m-%d")
result.append(event_date)
return result
def using_delta():
result = []
sec_per_event = datetime.timedelta(seconds=365 * 24 * 60 * 60 // events_this_year)
for year in range(start_year, end_year + 1):
year_dt = datetime.datetime(year=year, month=1, day=1)
cur_dt = year_dt
for evt in range(events_this_year):
cur_dt += sec_per_event
result.append(cur_dt.strftime("%Y-%m-%d"))
return result
def using_delta_nostring():
result = []
sec_per_event = datetime.timedelta(seconds=365 * 24 * 60 * 60 // events_this_year)
for year in range(start_year, end_year + 1):
year_dt = datetime.datetime(year=year, month=1, day=1)
cur_dt = year_dt
for evt in range(events_this_year):
cur_dt += sec_per_event
result.append(cur_dt) # no strftime
return result
t1 = timeit('using_strptime()', globals=globals(), number=1)
t2 = timeit('using_delta()', globals=globals(), number=1)
t3 = timeit('using_delta_nostring()', globals=globals(), number=1)
print(t1)
print(t2)
print(t3)
print("Ratios:")
print(t1 / t2)
print(t1 / t3)
Output on my machine:
22.7066284
6.213773400000001
0.20198889999999992
Ratios:
3.654241463005393
112.4152287576199
There is no need to ever create dates by strptime.
Compute the delta between elements and create a list of events based on the enumeration of your events_this_year times the position in this range:
import datetime
start_year = 2021
end_year = 2021
events_this_year = 10**6
# using this to compute the available time handles leap years
day_first = datetime.datetime(start_year,1,1,0,0,0,0)
day_last = datetime.datetime(start_year,12,31,23,59,59,999)
# delta time between events if spaced equally is
# whole available time divided by number of events
ticks_per_event = (day_last - day_first) / events_this_year
# enumerate range of events and use index (i) to multiply the delta
# and add that to the 1st day
events = [(day_first + ticks_per_event * i, f"Event: {e}")
for i,e in enumerate(range(events_this_year))]
print(events[:5], "...", events[-5:], sep="\n")
Output:
[(datetime.datetime(2021, 1, 1, 0, 0), 'Event: 0'),
(datetime.datetime(2021, 1, 1, 0, 0, 31, 535999), 'Event: 1'),
(datetime.datetime(2021, 1, 1, 0, 1, 3, 71998), 'Event: 2'),
(datetime.datetime(2021, 1, 1, 0, 1, 34, 607997), 'Event: 3'),
(datetime.datetime(2021, 1, 1, 0, 2, 6, 143996), 'Event: 4'),
(datetime.datetime(2021, 1, 1, 0, 2, 37, 679995), 'Event: 5')]
...
[(datetime.datetime(2021, 12, 31, 23, 57, 21, 320005), 'Event: 999995'),
(datetime.datetime(2021, 12, 31, 23, 57, 52, 856004), 'Event: 999996'),
(datetime.datetime(2021, 12, 31, 23, 58, 24, 392003), 'Event: 999997'),
(datetime.datetime(2021, 12, 31, 23, 58, 55, 928002), 'Event: 999998'),
(datetime.datetime(2021, 12, 31, 23, 59, 27, 464001), 'Event: 999999')]
Just collecting the dates (you can zip() them back together them with your eventlist) for
from timeit import timeit
k ="""
e = []
for year in range(start_year, end_year + 1):
for evt in range(events_this_year):
e.append( (datetime.datetime.strptime(str(year) + "-01-01", "%Y-%m-%d") + datetime.timedelta(days=365 * evt / events_this_year)).strftime("%Y-%m-%d"))
"""
print(timeit(k, number=2, globals=globals()))
k = '[day_first + ticks_per_event*i for i,e in enumerate(range(events_this_year))]'
print(timeit(k, number=2, globals=globals()))
takes
100.682846539 # yours
1.786883751 # mine
I've the following code creating a simple excel sheet and a chart using openpyxl (code is from the documentation - edited to explain the need)
from datetime import date
from openpyxl import Workbook
from openpyxl.chart import (
LineChart,
Reference,
)
from openpyxl.chart.axis import DateAxis
from openpyxl.chart.label import DataLabelList
wb = Workbook()
ws = wb.active
rows = [
['Date', 'Batch 1', 'Batch 2', 'Batch 3'],
[date(2015,9, 1), 41, 30, 25],
[date(2015,9, 2), 41, 25, 30],
[date(2015,9, 3), 41, 30, 45],
[date(2015,9, 4), 41, 25, 40],
[date(2015,9, 5), 41, 35, 30],
[date(2015,9, 6), 41, 40, 35],
]
for row in rows:
ws.append(row)
c1 = LineChart()
c1.title = "Line Chart"
c1.style = 13
c1.y_axis.title = 'Size'
c1.x_axis.title = 'Test Number'
data = Reference(ws, min_col=2, min_row=1, max_col=4, max_row=7)
c1.add_data(data, titles_from_data=True)
s2 = c1.series[2]
s2.smooth = True # Make the line smooth
c1.dataLabels = DataLabelList()
###########################################################
#Display data label and series name
#I need this to be displayed only for the first data point
#I can do this in excel by displaying the label only for the
#data point required
c1.dataLabels.showVal = True
c1.dataLabels.showSerName = True
ws.add_chart(c1, "A10")
wb.save("line.xlsx")
Chart I'm getting
Chart I want - how can I get the chart like this?
Displaying label (series name and value) only for one data point...
I got this working and here is the code with explanation:
from datetime import date
from openpyxl import Workbook
from openpyxl.chart import (
LineChart,
Reference,
)
from openpyxl.chart.axis import DateAxis
from openpyxl.chart.label import DataLabelList
from openpyxl.chart.label import DataLabel
wb = Workbook()
ws = wb.active
rows = [
['Date', 'Batch 1', 'Batch 2', 'Batch 3'],
[date(2015,9, 1), 41, 30, 25],
[date(2015,9, 2), 41, 25, 30],
[date(2015,9, 3), 41, 30, 45],
[date(2015,9, 4), 41, 25, 40],
[date(2015,9, 5), 41, 35, 30],
[date(2015,9, 6), 41, 40, 35],
]
for row in rows:
ws.append(row)
c1 = LineChart()
c1.title = "Line Chart"
c1.style = 13
c1.y_axis.title = 'Size'
c1.x_axis.title = 'Test Number'
data = Reference(ws, min_col=2, min_row=1, max_col=4, max_row=7)
c1.add_data(data, titles_from_data=True)
#Get the first series
s1 = c1.series[0]
#Initialize data lables
s1.dLbls = DataLabelList()
#Initialize data label
dl = DataLabel()
#Set properties
dl.showVal = True
dl.showSerName = True
#position t for top
dl.position = "t"
#Append data label to data lebels
s1.dLbls.dLbl.append(dl)
#This produces expected result
ws.add_chart(c1, "A10")
wb.save("line.xlsx")
Result
Still I couldn't get the label text properties set!